"use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { type ReactNode, useEffect, useState } from "react"; import { useLedgerSession } from "@/context/LedgerSessionContext"; import { useThemePreference, type ThemePreference } from "@/context/ThemeContext"; const SIDEBAR_COLLAPSED_KEY = "ql-sidebar-collapsed"; /** Sidebar order: executive mockup core (Strategy → Quantum → Simulations) + product routes + Settings stub */ const NAV_ITEMS = [ { href: "/dashboard", label: "Executive Dashboard", icon: "dashboard" }, { href: "/portfolio", label: "Portfolio Lab", icon: "science" }, { href: "/strategy", label: "Strategy Builder", icon: "architecture" }, { href: "/quantum", label: "Quantum Engine", icon: "memory" }, { href: "/simulations", label: "Simulations", icon: "query_stats" }, { href: "/reports", label: "Reports", icon: "description" }, { href: "/settings", label: "Settings", icon: "settings" }, ]; function NavItem({ href, label, icon, active, collapsed, }: { href: string; label: string; icon: string; active: boolean; collapsed: boolean; }) { const base = "flex items-center rounded-lg text-sm font-medium transition-all no-underline shrink-0"; const expandedPad = "gap-3 px-4 py-3 border-l-4"; const iconOnlyPad = "justify-center px-2 py-3 min-w-0"; const activeExpanded = "bg-ql-surface-container text-ql-primary border-ql-primary-container"; const activeCollapsed = "bg-ql-surface-container text-ql-primary ring-2 ring-inset ring-ql-primary-container/80 border-transparent"; const idleExpanded = "text-ql-on-surface-variant hover:bg-ql-surface-container hover:text-ql-on-surface border-transparent"; const idleCollapsed = "text-ql-on-surface-variant hover:bg-ql-surface-container hover:text-ql-on-surface border-transparent"; return ( {icon} {!collapsed ? {label} : null} ); } const THEME_META: Record = { dark: { icon: "dark_mode", label: "Dark" }, light: { icon: "light_mode", label: "Light" }, system: { icon: "contrast", label: "System" }, }; function objectiveLabel(obj: string): string { return obj.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); } function sessionsDiffer( last: { objective: string; tickers: string[] }, current: { objective: string; tickers: string[] } ): boolean { if (last.objective !== current.objective) return true; if (last.tickers.length !== current.tickers.length) return true; const a = [...last.tickers].sort().join("\0"); const b = [...current.tickers].sort().join("\0"); return a !== b; } export default function AppLayout({ children }: { children: ReactNode }) { const pathname = usePathname(); const { session } = useLedgerSession(); const { preference, cycle } = useThemePreference(); const themeMeta = THEME_META[preference]; const [sidebarCollapsed, setSidebarCollapsed] = useState(false); useEffect(() => { try { if (typeof window !== "undefined" && localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === "1") { setSidebarCollapsed(true); } } catch { /* ignore */ } }, []); const toggleSidebar = () => { setSidebarCollapsed((c) => { const next = !c; try { localStorage.setItem(SIDEBAR_COLLAPSED_KEY, next ? "1" : "0"); } catch { /* ignore */ } return next; }); }; const lo = session.lastOptimize; const displayObjective = lo ? lo.objective : session.objective; const displayTickerCount = lo ? lo.tickers.length : session.tickers.length; const currentDiffersFromLast = lo != null && sessionsDiffer(lo, session); const sessionTooltip = [ `Session · ${objectiveLabel(displayObjective)}`, `${displayTickerCount} ticker${displayTickerCount === 1 ? "" : "s"}`, lo ? `Last run ${new Date(lo.at).toLocaleString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" })}` : null, currentDiffersFromLast ? `Current: ${objectiveLabel(session.objective)} · ${session.tickers.length} tickers` : null, ] .filter(Boolean) .join(" · "); return ( <> {/* Material Symbols: loaded in root layout (not here — body breaks layout/CSS ordering in some browsers). */} {/* Sidebar */} {/* Mobile top bar */}
QL
{/* Main — light mode negative space: see globals.css html.light .app-shell-main */}
{children}
{/* Mobile bottom nav */} ); }