feat: confluence autocomplete + explanatory text

- Replace plain text inputs with SymbolAutocomplete (existing component)
  in both CorridorPanel and ConfluencePanel
- Add explanatory text to both panels describing what the visualization
  shows and how to interpret results
- Fix symbol reset bug: panels now sync with global activeSymbol store
  so symbol persists across tab switches and page reloads
- Add 'Run evaluation now' button when no corridor snapshot exists,
  wired to existing runEvaluationNow mutation
This commit is contained in:
Investor Flow Build
2026-08-11 22:49:30 -04:00
parent 9b41890d33
commit 0afc5b6f6e
2 changed files with 86 additions and 17 deletions
+44 -8
View File
@@ -1,8 +1,9 @@
'use client';
import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { api } from '@/lib/trpc';
import { useActiveSymbol } from '@/stores/active-symbol-store';
import { CollapsibleSection } from '@/components/CollapsibleSection';
import { SymbolAutocomplete } from '@/components/SymbolAutocomplete';
// ---------------------------------------------------------------------------
// Types (subset of server types for display)
@@ -44,9 +45,15 @@ const BODY_BADGE: Record<string, string> = {
export function ConfluencePanel() {
const activeSymbol = useActiveSymbol((s) => s.activeSymbol);
const setActiveSymbol = useActiveSymbol((s) => s.setActive);
const [symbol, setSymbol] = useState(activeSymbol);
const [selectedRackId, setSelectedRackId] = useState<string | null>(null);
const pickSymbol = useCallback((sym: string) => {
setSymbol(sym);
setActiveSymbol(sym);
}, [setActiveSymbol]);
// Racks
const [racks, setRacks] = useState<{ system: Array<{ id: string; name: string }>; user: Array<{ id: string; name: string }> }>({ system: [], user: [] });
useEffect(() => {
@@ -80,6 +87,18 @@ export function ConfluencePanel() {
.catch(() => {});
}, []);
const [evaluating, setEvaluating] = useState(false);
const runEvaluation = async () => {
setEvaluating(true);
try {
await api.confluence.runEvaluationNow({ symbol });
window.location.reload();
} finally {
setEvaluating(false);
}
};
const latest = (evaluation?.latest as Record<string, unknown>) ?? null;
const quality = (latest?.quality as Quality) ?? null;
const assessments = (latest?.assessments as Array<{ id: string; state: string; note?: string }>) ?? [];
@@ -89,16 +108,24 @@ export function ConfluencePanel() {
return (
<div className="space-y-6">
{/* Explanatory text */}
<div className="text-xs text-fg-muted leading-relaxed">
Slot Picture combines technical, seasonal, and valuation signals into a single
directional read. Each slot fires (green dot) when its condition is met, or stays
dormant. The overall quality grade reflects how many slots fired and in which direction.
Use the rack selector to switch between different signal subsets.
</div>
{/* Header */}
<div className="flex flex-wrap items-end gap-4">
<div>
<div className="w-48">
<label className="block text-xs text-fg-muted mb-1">Symbol</label>
<input
type="text"
<SymbolAutocomplete
onPick={pickSymbol}
onSubmitRaw={(raw) => pickSymbol(raw.toUpperCase())}
value={symbol}
onChange={(e) => setSymbol(e.target.value.toUpperCase().trim())}
className="w-24 rounded bg-bg-secondary border border-fg-muted/20 px-2 py-1.5 text-sm text-fg"
maxLength={12}
placeholder="Search symbol…"
ariaLabel="Select symbol for slot picture"
/>
</div>
<div>
@@ -140,7 +167,16 @@ export function ConfluencePanel() {
)}
</div>
) : evaluation !== null ? (
<div className="text-sm text-fg-muted">No evaluations yet for {symbol}. Data will appear once the confluence evaluator runs.</div>
<div className="flex items-center gap-3 text-sm text-fg-muted">
<span>No evaluations yet for {symbol}.</span>
<button
onClick={runEvaluation}
disabled={evaluating}
className="rounded bg-bg-secondary border border-fg-muted/30 px-3 py-1.5 text-xs text-fg hover:border-fg-muted/60 disabled:opacity-50"
>
{evaluating ? 'Evaluating…' : 'Run evaluation now'}
</button>
</div>
) : null}
{/* Slot evidence grid */}