import { useMemo } from 'react';
import { Link } from 'react-router-dom';
import { useActivation } from '../hooks/useActivation';
import { useTrades } from '../hooks/useTrades';
import { useWatchlist } from '../hooks/useWatchlist';
import { usePerformance } from '../hooks/usePerformance';
import { useMarketRegime } from '../hooks/useMarketRegime';
import { regimeColor, regimeDot, regimeHeadline } from '../lib/regime';
import { Callout } from '../components/ui/Callout';
import { Section } from '../components/ui/Section';
import { OpenTradesPanel } from '../components/dashboard/OpenTradesPanel';
import { SkeletonCard, SkeletonTable } from '../components/ui/Skeleton';
import { formatPrice } from '../lib/format';
import { recommendationActionLabel } from '../lib/recommendation';
import { qualifiesSetup, activationSummary, primaryTargetProbability, expectedValueR } from '../lib/qualification';
import type { TradeSetup } from '../lib/types';
function fmtR(value: number | null): string {
if (value === null) return '—';
return `${value > 0 ? '+' : ''}${value.toFixed(2)}R`;
}
function rColor(value: number | null): string {
if (value === null) return 'text-gray-400';
if (value > 0) return 'text-emerald-400';
if (value < 0) return 'text-red-400';
return 'text-gray-300';
}
function Metric({ label, value, sub, valueClass = 'text-gray-100' }: {
label: string;
value: string;
sub?: string;
valueClass?: string;
}) {
return (
{label}
{value}
{sub &&
{sub}
}
);
}
function DirectionTag({ direction }: { direction: string }) {
const isLong = direction === 'long';
return (
{direction}
);
}
export default function DashboardPage() {
const trades = useTrades();
const watchlist = useWatchlist();
const activation = useActivation();
const performance = usePerformance();
const regime = useMarketRegime();
const qualifiedSetups = useMemo(
() =>
activation.data
? (trades.data ?? []).filter((t) => qualifiesSetup(t, activation.data!))
: [],
[trades.data, activation.data],
);
// Show qualified setups first; fall back to the full list when none qualify.
// Rank by expected value (R) so the best opportunity sits at the top.
const showingQualified = qualifiedSetups.length > 0;
const topSetups: TradeSetup[] = useMemo(() => {
const pool = showingQualified ? qualifiedSetups : trades.data ?? [];
return [...pool]
.sort((a, b) => (expectedValueR(b) ?? -Infinity) - (expectedValueR(a) ?? -Infinity))
.slice(0, 5);
}, [showingQualified, qualifiedSetups, trades.data]);
const topWatchlist = useMemo(
() =>
[...(watchlist.data ?? [])]
.sort((a, b) => (b.composite_score ?? -1) - (a.composite_score ?? -1))
.slice(0, 10),
[watchlist.data],
);
const today = new Date().toLocaleDateString('en-US', {
weekday: 'long', month: 'long', day: 'numeric',
});
const stats = performance.data?.overall;
return (
{/* Hero */}
{/* Market regime banner */}
{regime.data && (
Market regime:
{regimeHeadline(regime.data)}
{regime.data.label === 'bearish' && (
— be cautious on new longs
)}
{regime.data.label === 'bullish' && (
— shorts are counter-trend
)}
)}
{/* Metric strip */}
{(trades.isLoading || performance.isLoading) ? (
) : (
0 ? 'text-blue-300' : 'text-gray-100'}
/>
)}
{/* Open paper trades */}
{/* Top setups */}
{trades.isLoading && }
{trades.isError && Failed to load setups }
{trades.data && topSetups.length === 0 && (
No active setups. Run the scanner from the Signals page.
)}
{topSetups.length > 0 && (
Ticker
Dir
Entry
R:R
Target Prob
Exp. Value
Action
{topSetups.map((setup, i) => {
const ev = expectedValueR(setup);
const isTopPick = i === 0;
return (
{setup.symbol}
{isTopPick && (
Top pick
)}
{formatPrice(setup.entry_price)}
{setup.rr_ratio.toFixed(1)}:1
{(() => {
const p = primaryTargetProbability(setup);
return p != null ? `${Math.round(p)}%` : '—';
})()}
{fmtR(ev)}
{recommendationActionLabel(setup.recommended_action)}
);
})}
Exp. Value = probability-weighted payoff per unit of risk
All setups →
)}
{/* My watchlist */}
{watchlist.isLoading && }
{watchlist.isError && Failed to load watchlist }
{watchlist.data && topWatchlist.length === 0 && (
Your watchlist is empty — open any ticker and tap “☆ Add to watchlist”.
)}
{topWatchlist.length > 0 && (
{topWatchlist.map((entry) => (
{entry.symbol}
{entry.composite_score != null && (
score {entry.composite_score.toFixed(0)}
)}
{entry.last_close != null ? formatPrice(entry.last_close) : '—'}
{entry.change_pct != null
? `${entry.change_pct >= 0 ? '+' : ''}${entry.change_pct.toFixed(2)}%`
: '—'}
))}
Full watchlist →
)}
);
}