feat: new cockpit

This commit is contained in:
OpenSquared
2026-07-14 13:43:34 +02:00
parent 922bb76567
commit 7d348d5c25

View File

@@ -1,4 +1,4 @@
import { useMemo, useState } from 'react' import { useMemo, useState, useRef, useLayoutEffect } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { import {
useGeoRiskScore, useAllQuotes, useGeoRiskScore, useAllQuotes,
@@ -144,6 +144,34 @@ export default function Dashboard() {
setRiskView(v); localStorage.setItem('dash_risk_view', v) setRiskView(v); localStorage.setItem('dash_risk_view', v)
} }
// Row height is dictated by the config-driven watchlist cards (Watchlist Radar for row 1,
// Options Lab for row 2) — the other cards in each row are capped to match, with internal scroll.
const watchlistCardRef = useRef<HTMLDivElement>(null)
const optionsLabCardRef = useRef<HTMLAnchorElement>(null)
const [row1Height, setRow1Height] = useState<number | undefined>(undefined)
const [row2Height, setRow2Height] = useState<number | undefined>(undefined)
// Floors avoid the row collapsing to near-zero while the master card is still loading
useLayoutEffect(() => {
const el = watchlistCardRef.current
if (!el) return
const measure = () => setRow1Height(Math.max(el.offsetHeight, 220))
measure()
const ro = new ResizeObserver(measure)
ro.observe(el)
return () => ro.disconnect()
}, [])
useLayoutEffect(() => {
const el = optionsLabCardRef.current
if (!el) return
const measure = () => setRow2Height(Math.max(el.offsetHeight, 180))
measure()
const ro = new ResizeObserver(measure)
ro.observe(el)
return () => ro.disconnect()
}, [])
const allPatterns: any[] = allPatternsData ?? [] const allPatterns: any[] = allPatternsData ?? []
const scoreMap = useMemo(() => { const scoreMap = useMemo(() => {
@@ -251,10 +279,10 @@ export default function Dashboard() {
</div> </div>
)} )}
{/* Top row */} {/* Top row — height capped to Watchlist Radar's natural size (the only card driven by user config) */}
<div className="grid grid-cols-4 gap-4"> <div className="grid grid-cols-4 gap-4 items-start">
{/* Geo Risk */} {/* Geo Risk */}
<div className="card col-span-1 flex flex-col"> <div className="card col-span-1 flex flex-col overflow-hidden" style={row1Height ? { height: row1Height } : undefined}>
<div className="flex items-center justify-between mb-2 shrink-0"> <div className="flex items-center justify-between mb-2 shrink-0">
<div className="section-title flex items-center gap-1 mb-0"> <div className="section-title flex items-center gap-1 mb-0">
<Globe className="w-3 h-3" /> Geopolitical Risk <Globe className="w-3 h-3" /> Geopolitical Risk
@@ -309,8 +337,8 @@ export default function Dashboard() {
) : <div className="text-slate-500 text-xs">Backend required</div>} ) : <div className="text-slate-500 text-xs">Backend required</div>}
</div> </div>
{/* Watchlist Radar — its natural height (no cap) drives row 1's height */} {/* Watchlist Radar — natural height (config-driven), measured and used to cap the other row-1 cards */}
<div className="card col-span-1"> <div ref={watchlistCardRef} className="card col-span-1">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className="section-title mb-0">📡 Watchlist Radar</div> <div className="section-title mb-0">📡 Watchlist Radar</div>
<Link to="/config" className="flex items-center gap-0.5 text-[10px] text-slate-600 hover:text-slate-300 transition-colors"> <Link to="/config" className="flex items-center gap-0.5 text-[10px] text-slate-600 hover:text-slate-300 transition-colors">
@@ -398,7 +426,8 @@ export default function Dashboard() {
] ]
return ( return (
<Link to="/macro" className="card col-span-1 flex flex-col hover:border-slate-600/60 transition-all cursor-pointer"> <Link to="/macro" className="card col-span-1 flex flex-col overflow-y-auto hover:border-slate-600/60 transition-all cursor-pointer"
style={row1Height ? { height: row1Height } : undefined}>
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<span className="section-title mb-0 text-[10px]">🌐 Macro Regime</span> <span className="section-title mb-0 text-[10px]">🌐 Macro Regime</span>
<ArrowUpRight className="w-3 h-3 text-slate-600" /> <ArrowUpRight className="w-3 h-3 text-slate-600" />
@@ -470,7 +499,7 @@ export default function Dashboard() {
})()} })()}
{/* Economic Events — real Forex Factory feed, grouped by date */} {/* Economic Events — real Forex Factory feed, grouped by date */}
<div className="card col-span-1 flex flex-col"> <div className="card col-span-1 flex flex-col overflow-hidden" style={row1Height ? { height: row1Height } : undefined}>
<div className="section-title flex items-center gap-1 shrink-0"><Clock className="w-3 h-3" /> Economic Events</div> <div className="section-title flex items-center gap-1 shrink-0"><Clock className="w-3 h-3" /> Economic Events</div>
{upcomingEventsGrouped.length > 0 ? ( {upcomingEventsGrouped.length > 0 ? (
<div className="mt-2 space-y-2 overflow-y-auto flex-1 min-h-0"> <div className="mt-2 space-y-2 overflow-y-auto flex-1 min-h-0">
@@ -497,8 +526,8 @@ export default function Dashboard() {
</div> </div>
</div> </div>
{/* ── Command Center: Résumé Opérationnel ── */} {/* ── Command Center: Résumé Opérationnel ── height capped to Options Lab's natural size (config-driven) ── */}
<div className="grid grid-cols-4 gap-3"> <div className="grid grid-cols-4 gap-3 items-start">
{/* PnL */} {/* PnL */}
{(() => { {(() => {
@@ -539,7 +568,8 @@ export default function Dashboard() {
const pfNet = pf?.net_pnl ?? null const pfNet = pf?.net_pnl ?? null
return ( return (
<Link to="/journal" className="card flex flex-col hover:border-slate-600/60 transition-all cursor-pointer"> <Link to="/journal" className="card flex flex-col overflow-y-auto hover:border-slate-600/60 transition-all cursor-pointer"
style={row2Height ? { height: row2Height } : undefined}>
<div className="flex items-center justify-between mb-1.5"> <div className="flex items-center justify-between mb-1.5">
<span className="section-title mb-0 text-[10px]">📊 P&L</span> <span className="section-title mb-0 text-[10px]">📊 P&L</span>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
@@ -706,7 +736,8 @@ export default function Dashboard() {
.sort((a, b) => b.value - a.value) .sort((a, b) => b.value - a.value)
return ( return (
<Link to="/risk" className="card flex flex-col hover:border-slate-600/60 transition-all cursor-pointer"> <Link to="/risk" className="card flex flex-col overflow-y-auto hover:border-slate-600/60 transition-all cursor-pointer"
style={row2Height ? { height: row2Height } : undefined}>
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<span className="section-title mb-0 text-[10px]">🛡 Risk</span> <span className="section-title mb-0 text-[10px]">🛡 Risk</span>
<ArrowUpRight className="w-3 h-3 text-slate-600" /> <ArrowUpRight className="w-3 h-3 text-slate-600" />
@@ -776,7 +807,8 @@ export default function Dashboard() {
const rollingVar: any[] = (snap?.full_result?.rolling_var ?? []).slice(-30) const rollingVar: any[] = (snap?.full_result?.rolling_var ?? []).slice(-30)
return ( return (
<Link to="/var" className="card flex flex-col hover:border-slate-600/60 transition-all cursor-pointer"> <Link to="/var" className="card flex flex-col overflow-y-auto hover:border-slate-600/60 transition-all cursor-pointer"
style={row2Height ? { height: row2Height } : undefined}>
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<span className="section-title mb-0 text-[10px]">📉 VaR</span> <span className="section-title mb-0 text-[10px]">📉 VaR</span>
<ArrowUpRight className="w-3 h-3 text-slate-600" /> <ArrowUpRight className="w-3 h-3 text-slate-600" />
@@ -850,7 +882,7 @@ export default function Dashboard() {
.sort((a: any, b: any) => Math.abs((b.iv_rank ?? 50) - 50) - Math.abs((a.iv_rank ?? 50) - 50)) .sort((a: any, b: any) => Math.abs((b.iv_rank ?? 50) - 50) - Math.abs((a.iv_rank ?? 50) - 50))
return ( return (
<Link to="/options" className="card flex flex-col hover:border-slate-600/60 transition-all cursor-pointer"> <Link to="/options" ref={optionsLabCardRef} className="card flex flex-col hover:border-slate-600/60 transition-all cursor-pointer">
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<span className="section-title mb-0 text-[10px]">🧪 Options Lab</span> <span className="section-title mb-0 text-[10px]">🧪 Options Lab</span>
<ArrowUpRight className="w-3 h-3 text-slate-600" /> <ArrowUpRight className="w-3 h-3 text-slate-600" />