feat: tab bar
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
||||
import { BrowserRouter, Routes, Route, useLocation } 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'
|
||||
@@ -34,57 +38,112 @@ import CausalLab from './pages/CausalLab'
|
||||
import { Navigate } from 'react-router-dom'
|
||||
import { useCycleWatcher } from './hooks/useApi'
|
||||
|
||||
// ── Keep-alive component definitions ─────────────────────────────────────────
|
||||
// KEEP_ALIVE_META provides metadata (path/label/icon); components added here.
|
||||
// Routes listed here are REMOVED from <Routes> below to avoid double-render.
|
||||
|
||||
const KEEP_ALIVE_DEFS: { path: string; component: ComponentType }[] = [
|
||||
{ path: '/market-events', component: MarketEvents },
|
||||
{ path: '/causal-lab', component: CausalLab },
|
||||
]
|
||||
|
||||
const KEEP_ALIVE_PATHS = new Set(KEEP_ALIVE_DEFS.map(d => d.path))
|
||||
|
||||
// ── KeepAlivePage — mounts once, hides/shows on route change ─────────────────
|
||||
|
||||
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])
|
||||
|
||||
// Don't mount until the user visits for the first time
|
||||
if (!kept.has(path) && !isActive) return null
|
||||
|
||||
return (
|
||||
<div className={isActive ? 'flex-1 min-h-0 overflow-hidden' : 'hidden'}>
|
||||
<Component />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ── NormalRoutes — handles all non-keep-alive routes ─────────────────────────
|
||||
|
||||
function NormalRoutes() {
|
||||
const location = useLocation()
|
||||
// Yield to KeepAlive when on a keep-alive route
|
||||
if (KEEP_ALIVE_PATHS.has(location.pathname)) return null
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-auto">
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/geo" element={<GeoRadar />} />
|
||||
<Route path="/markets" element={<Markets />} />
|
||||
<Route path="/macro" element={<MacroRegime />} />
|
||||
<Route path="/options" element={<OptionsLab />} />
|
||||
<Route path="/patterns" element={<PatternExplorer />} />
|
||||
<Route path="/patterns/edit" element={<PatternEditor />} />
|
||||
<Route path="/pattern-lab" element={<PatternLab />} />
|
||||
<Route path="/portfolio" element={<Portfolio />} />
|
||||
<Route path="/backtest" element={<Backtest />} />
|
||||
<Route path="/calendar" element={<CalendarPage />} />
|
||||
<Route path="/macro-series" element={<MacroSeriesPage />} />
|
||||
<Route path="/journal" element={<JournalDeBord />} />
|
||||
<Route path="/rapport" element={<RapportIA />} />
|
||||
<Route path="/super-contexte" element={<SuperContexte />} />
|
||||
<Route path="/config" element={<Config />} />
|
||||
<Route path="/analytics" element={<Analytics />} />
|
||||
<Route path="/analytics-advanced" element={<AnalyticsAdvanced />} />
|
||||
<Route path="/risk" element={<RiskDashboard />} />
|
||||
<Route path="/var" element={<VaRAnalysis />} />
|
||||
<Route path="/position-history" element={<PositionHistory />} />
|
||||
<Route path="/logs" element={<SystemLogs />} />
|
||||
<Route path="/institutional" element={<InstitutionalReports />} />
|
||||
<Route path="/specialist-desks" element={<SpecialistDesks />} />
|
||||
<Route path="/snapshot" element={<ExternalSnapshot />} />
|
||||
<Route path="/instruments" element={<Navigate to="/instruments/SPY" replace />} />
|
||||
<Route path="/instruments/:id" element={<InstrumentDashboard />} />
|
||||
<Route path="/impact" element={<Navigate to="/market-events" replace />} />
|
||||
<Route path="/timeline" element={<Navigate to="/market-events" replace />} />
|
||||
<Route path="/cycle-actions" element={<CycleActions />} />
|
||||
<Route path="/ai-desks" element={<AIDesks />} />
|
||||
<Route path="/simulator" element={<EuroSimulator />} />
|
||||
</Routes>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ── GlobalWatcher ─────────────────────────────────────────────────────────────
|
||||
|
||||
function GlobalWatcher() {
|
||||
useCycleWatcher()
|
||||
return null
|
||||
}
|
||||
|
||||
// ── App ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div className="flex min-h-screen">
|
||||
<GlobalWatcher />
|
||||
<Sidebar />
|
||||
<main className="flex-1 overflow-auto">
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/geo" element={<GeoRadar />} />
|
||||
<Route path="/markets" element={<Markets />} />
|
||||
<Route path="/macro" element={<MacroRegime />} />
|
||||
<Route path="/options" element={<OptionsLab />} />
|
||||
<Route path="/patterns" element={<PatternExplorer />} />
|
||||
<Route path="/patterns/edit" element={<PatternEditor />} />
|
||||
<Route path="/pattern-lab" element={<PatternLab />} />
|
||||
<Route path="/portfolio" element={<Portfolio />} />
|
||||
<Route path="/backtest" element={<Backtest />} />
|
||||
<Route path="/calendar" element={<CalendarPage />} />
|
||||
<Route path="/macro-series" element={<MacroSeriesPage />} />
|
||||
<Route path="/journal" element={<JournalDeBord />} />
|
||||
<Route path="/rapport" element={<RapportIA />} />
|
||||
<Route path="/super-contexte" element={<SuperContexte />} />
|
||||
<Route path="/config" element={<Config />} />
|
||||
<Route path="/analytics" element={<Analytics />} />
|
||||
<Route path="/analytics-advanced" element={<AnalyticsAdvanced />} />
|
||||
<Route path="/risk" element={<RiskDashboard />} />
|
||||
<Route path="/var" element={<VaRAnalysis />} />
|
||||
<Route path="/position-history" element={<PositionHistory />} />
|
||||
<Route path="/logs" element={<SystemLogs />} />
|
||||
<Route path="/institutional" element={<InstitutionalReports />} />
|
||||
<Route path="/specialist-desks" element={<SpecialistDesks />} />
|
||||
<Route path="/snapshot" element={<ExternalSnapshot />} />
|
||||
<Route path="/instruments" element={<Navigate to="/instruments/SPY" replace />} />
|
||||
<Route path="/instruments/:id" element={<InstrumentDashboard />} />
|
||||
{/* Legacy redirects */}
|
||||
<Route path="/impact" element={<Navigate to="/market-events" replace />} />
|
||||
<Route path="/timeline" element={<Navigate to="/market-events" replace />} />
|
||||
<Route path="/market-events" element={<MarketEvents />} />
|
||||
<Route path="/cycle-actions" element={<CycleActions />} />
|
||||
<Route path="/ai-desks" element={<AIDesks />} />
|
||||
<Route path="/simulator" element={<EuroSimulator />} />
|
||||
<Route path="/causal-lab" element={<CausalLab />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
<TabsProvider>
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
<GlobalWatcher />
|
||||
<Sidebar />
|
||||
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
|
||||
<TabBar />
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
{KEEP_ALIVE_DEFS.map(def => (
|
||||
<KeepAlivePage key={def.path} path={def.path} component={def.component} />
|
||||
))}
|
||||
<NormalRoutes />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user