fix: add Entrée date and Durée/maturité columns to Dashboard grid view
This commit is contained in:
@@ -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<string, string> } | 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<string, string> } | 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 (
|
||||
<Fragment>
|
||||
<tr
|
||||
@@ -488,6 +499,28 @@ function TradeRow({ item, onAdd, macroInfo, addedInfo, profiles, rank }: {
|
||||
</td>
|
||||
{/* Macro */}
|
||||
<td className="px-2 py-2 text-[10px]" style={{ color: bd.color }}>{bd.label}</td>
|
||||
{/* Date entrée */}
|
||||
<td className="px-2 py-2 text-right text-[10px] whitespace-nowrap">
|
||||
{addedInfo?.entry_date
|
||||
? <span className="text-slate-500">{format(new Date(addedInfo.entry_date), "d MMM", { locale: fr })}</span>
|
||||
: <span className="text-slate-800">—</span>}
|
||||
</td>
|
||||
{/* Durée / maturité */}
|
||||
<td className="px-2 py-2 text-right text-[10px]">
|
||||
{daysHeld !== null ? (
|
||||
<div className="flex flex-col items-end gap-0.5">
|
||||
<span className={clsx('font-mono font-semibold', maturityColor)}>
|
||||
{daysHeld}j{horizon ? `/${horizon}j` : ''}
|
||||
</span>
|
||||
{maturityPct !== null && (
|
||||
<div className="w-12 h-1 bg-slate-800 rounded-full overflow-hidden">
|
||||
<div className={clsx('h-full rounded-full', maturityPct >= 75 ? 'bg-orange-500' : maturityPct >= 35 ? 'bg-emerald-500' : 'bg-yellow-500')}
|
||||
style={{ width: `${maturityPct}%` }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : <span className="text-slate-800">—</span>}
|
||||
</td>
|
||||
{/* Expand chevron */}
|
||||
<td className="pr-3 py-2 text-slate-600 text-right">
|
||||
{expanded ? <ChevronUp className="w-3.5 h-3.5 inline" /> : <ChevronDown className="w-3.5 h-3.5 inline" />}
|
||||
@@ -495,7 +528,7 @@ function TradeRow({ item, onAdd, macroInfo, addedInfo, profiles, rank }: {
|
||||
</tr>
|
||||
{expanded && (
|
||||
<tr className="bg-dark-900/60">
|
||||
<td colSpan={11} className="px-0 py-0 border-b border-blue-700/20">
|
||||
<td colSpan={13} className="px-0 py-0 border-b border-blue-700/20">
|
||||
<div className="p-4 grid grid-cols-3 gap-4 text-xs">
|
||||
{/* Col 1: score justification */}
|
||||
<div className="col-span-2">
|
||||
@@ -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<string, { entry_date: string }> = {}
|
||||
const upsert = (key: string, entry_date: string) => {
|
||||
if (!map[key] || entry_date > map[key].entry_date) map[key] = { entry_date }
|
||||
const map: Record<string, { entry_date: string; expiry_days?: number }> = {}
|
||||
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() {
|
||||
<th className="px-2 py-2 text-right">Max/Cible</th>
|
||||
<th className="px-2 py-2">Profil</th>
|
||||
<th className="px-2 py-2">Macro</th>
|
||||
<th className="px-2 py-2 text-right">Entrée</th>
|
||||
<th className="px-2 py-2 text-right">Durée</th>
|
||||
<th className="pr-3 py-2 w-6"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
Reference in New Issue
Block a user