diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 69f8e53..f144f4e 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -169,7 +169,7 @@ function TradeCard({ item, onAdd, macroInfo, addedInfo, profiles }: { item: TradeItem onAdd: (item: TradeItem) => void macroInfo?: { dominant: string; label: string; color: string; emoji: string; assetBias: Record } | null - addedInfo?: { entry_date: string } | null + addedInfo?: { entry_date: string; expiry_days?: number } | null profiles?: any[] }) { const [expanded, setExpanded] = useState(false) @@ -392,7 +392,7 @@ function TradeRow({ item, onAdd, macroInfo, addedInfo, profiles, rank }: { item: TradeItem onAdd: (item: TradeItem) => void macroInfo?: { dominant: string; label: string; color: string; emoji: string; assetBias: Record } | null - addedInfo?: { entry_date: string } | null + addedInfo?: { entry_date: string; expiry_days?: number } | null profiles?: any[] rank: number }) { @@ -414,6 +414,17 @@ function TradeRow({ item, onAdd, macroInfo, addedInfo, profiles, rank }: { ? Math.round(Math.abs(maxLoss) * gainPct / 100) : (trade.target_gain_eur ?? scoreInfo?.recommended_trade?.target_gain_eur) + const daysHeld = addedInfo?.entry_date + ? Math.floor((Date.now() - new Date(addedInfo.entry_date).getTime()) / 86400000) + : null + const horizon = addedInfo?.expiry_days ?? null + const maturityPct = daysHeld !== null && horizon ? Math.min(100, Math.round(daysHeld / horizon * 100)) : null + const maturityColor = maturityPct === null ? 'text-slate-700' + : maturityPct >= 75 ? 'text-orange-400' + : maturityPct >= 35 ? 'text-emerald-400' + : maturityPct >= 10 ? 'text-yellow-400' + : 'text-slate-500' + return ( {/* Macro */} {bd.label} + {/* Date entrée */} + + {addedInfo?.entry_date + ? {format(new Date(addedInfo.entry_date), "d MMM", { locale: fr })} + : } + + {/* Durée / maturité */} + + {daysHeld !== null ? ( +
+ + {daysHeld}j{horizon ? `/${horizon}j` : ''} + + {maturityPct !== null && ( +
+
= 75 ? 'bg-orange-500' : maturityPct >= 35 ? 'bg-emerald-500' : 'bg-yellow-500')} + style={{ width: `${maturityPct}%` }} /> +
+ )} +
+ ) : } + {/* Expand chevron */} {expanded ? : } @@ -495,7 +528,7 @@ function TradeRow({ item, onAdd, macroInfo, addedInfo, profiles, rank }: { {expanded && ( - +
{/* Col 1: score justification */}
@@ -603,16 +636,17 @@ export default function Dashboard() { // key1 = geo_trigger (pattern name) + strategy → survives underlying normalization // key2 = underlying + strategy → direct ticker match fallback const addedMap = useMemo(() => { - const map: Record = {} - const upsert = (key: string, entry_date: string) => { - if (!map[key] || entry_date > map[key].entry_date) map[key] = { entry_date } + const map: Record = {} + const upsert = (key: string, entry_date: string, expiry_days?: number) => { + if (!map[key] || entry_date > map[key].entry_date) map[key] = { entry_date, expiry_days } } for (const pos of (positions as any[] ?? [])) { - const strategy = (pos.strategy ?? '').toLowerCase() - const trigger = (pos.geo_trigger ?? '').toLowerCase() - const underly = (pos.underlying ?? '').toLowerCase() - if (trigger) upsert(`trigger:${trigger}:${strategy}`, pos.entry_date ?? '') - if (underly) upsert(`ticker:${underly}:${strategy}`, pos.entry_date ?? '') + const strategy = (pos.strategy ?? '').toLowerCase() + const trigger = (pos.geo_trigger ?? '').toLowerCase() + const underly = (pos.underlying ?? '').toLowerCase() + const expiry_days = pos.expiry_days ?? undefined + if (trigger) upsert(`trigger:${trigger}:${strategy}`, pos.entry_date ?? '', expiry_days) + if (underly) upsert(`ticker:${underly}:${strategy}`, pos.entry_date ?? '', expiry_days) } return map }, [positions]) @@ -985,6 +1019,8 @@ export default function Dashboard() { Max/Cible Profil Macro + Entrée + Durée