import { BrowserRouter, Routes, Route, useLocation, useNavigate, useParams, Navigate } from 'react-router-dom' import { useEffect, ComponentType } from 'react' import Sidebar from './components/layout/Sidebar' import TabBar from './components/layout/TabBar' import { TabsProvider, useTabs } from './context/TabsContext' import { KEEP_ALIVE_META } from './config/keepAliveMeta' import Dashboard from './pages/Dashboard' import GeoRadar from './pages/GeoRadar' import Markets from './pages/Markets' import MacroRegime from './pages/MacroRegime' import OptionsLab from './pages/OptionsLab' import Backtest from './pages/Backtest' import CalendarPage from './pages/CalendarPage' import Portfolio from './pages/Portfolio' import PatternEditor from './pages/PatternEditor' import PatternExplorer from './pages/PatternExplorer' import PatternLab from './pages/PatternLab' import JournalDeBord from './pages/JournalDeBord' import RapportIA from './pages/RapportIA' import SuperContexte from './pages/SuperContexte' import Config from './pages/Config' import Analytics from './pages/Analytics' import AnalyticsAdvanced from './pages/AnalyticsAdvanced' import RiskDashboard from './pages/RiskDashboard' import SystemLogs from './pages/SystemLogs' import VaRAnalysis from './pages/VaRAnalysis' import PositionHistory from './pages/PositionHistory' import InstitutionalReports from './pages/InstitutionalReports' import SpecialistDesks from './pages/SpecialistDesks' import ExternalSnapshot from './pages/ExternalSnapshot' import InstrumentDashboard from './pages/InstrumentDashboard' import CycleActions from './pages/CycleActions' import MarketEvents from './pages/MarketEvents' import AIDesks from './pages/AIDesks' import MacroSeriesPage from './pages/MacroSeriesPage' import EuroSimulator from './pages/EuroSimulator' import CausalLab from './pages/CausalLab' import { useCycleWatcher } from './hooks/useApi' // ── Keep-alive pages ────────────────────────────────────────────────────────── // All pages except InstrumentDashboard (uses URL :id param — would break when hidden). // Order must match KEEP_ALIVE_META in keepAliveMeta.ts. const KEEP_ALIVE_DEFS: { path: string; component: ComponentType }[] = [ { path: '/', component: Dashboard }, { path: '/geo', component: GeoRadar }, { path: '/markets', component: Markets }, { path: '/macro', component: MacroRegime }, { path: '/options', component: OptionsLab }, { path: '/patterns', component: PatternExplorer }, { path: '/pattern-lab', component: PatternLab }, { path: '/portfolio', component: Portfolio }, { path: '/journal', component: JournalDeBord }, { path: '/rapport', component: RapportIA }, { path: '/super-contexte', component: SuperContexte }, { path: '/analytics', component: Analytics }, { path: '/analytics-advanced', component: AnalyticsAdvanced }, { path: '/risk', component: RiskDashboard }, { path: '/var', component: VaRAnalysis }, { path: '/position-history', component: PositionHistory }, { path: '/backtest', component: Backtest }, { path: '/calendar', component: CalendarPage }, { path: '/simulator', component: EuroSimulator }, { path: '/causal-lab', component: CausalLab }, { path: '/macro-series', component: MacroSeriesPage }, { path: '/institutional', component: InstitutionalReports }, { path: '/specialist-desks', component: SpecialistDesks }, { path: '/market-events', component: MarketEvents }, { path: '/ai-desks', component: AIDesks }, { path: '/cycle-actions', component: CycleActions }, { path: '/snapshot', component: ExternalSnapshot }, { path: '/logs', component: SystemLogs }, { path: '/config', component: Config }, { path: '/patterns/edit', component: PatternEditor }, ] const KEEP_ALIVE_PATHS = new Set(KEEP_ALIVE_DEFS.map(d => d.path)) // ── KeepAlivePage ───────────────────────────────────────────────────────────── function KeepAlivePage({ path, component: Component }: { path: string; component: ComponentType }) { const { kept, keepRoute } = useTabs() const location = useLocation() const isActive = location.pathname === path || location.pathname.startsWith(path + '/') useEffect(() => { if (isActive) keepRoute(path) }, [isActive, path, keepRoute]) if (!kept.has(path) && !isActive) return null return (
) } // ── Instrument keep-alive tabs ──────────────────────────────────────────────── // Registers the instrument tab on navigation (renders nothing itself) function InstrumentRoute() { const { id = localStorage.getItem('last_instrument') || 'EURUSD=X' } = useParams<{ id: string }>() const { openInstrument } = useTabs() useEffect(() => { openInstrument(id.toUpperCase()) }, [id, openInstrument]) return null } // Keeps one InstrumentDashboard mounted per open instrument tab function InstrumentKeepAlive({ id }: { id: string }) { const location = useLocation() const isActive = location.pathname.toLowerCase() === `/instruments/${id.toLowerCase()}` return (
) } // ── NormalRoutes — legacy redirects + instrument tab registration ───────────── function NormalRoutes() { const location = useLocation() if (KEEP_ALIVE_PATHS.has(location.pathname)) return null // On instrument paths the keep-alive layer renders the UI; this Routes just registers the tab const isInstrument = /^\/instruments\/\w/.test(location.pathname) return (
} /> } /> {/* Legacy redirects */} } /> } />
) } // ── GlobalWatcher ───────────────────────────────────────────────────────────── function GlobalWatcher() { useCycleWatcher() return null } // ── App ─────────────────────────────────────────────────────────────────────── function AppInner() { const { instrumentIds } = useTabs() return (
{KEEP_ALIVE_DEFS.map(def => ( ))} {instrumentIds.map(id => ( ))}
) } export default function App() { return ( ) }