// App chrome — sidebar, top bar, persona switcher, search, notifications. const NAV = [ { id: 'home', label: 'Home', icon: 'home' }, { id: 'portfolio', label: 'Portfolio', icon: 'portfolio' }, { id: 'initiatives', label: 'Initiatives', icon: 'initiatives' }, { id: 'projects', label: 'Projects', icon: 'projects' }, { id: 'ideas', label: 'Ideas', icon: 'ideas' }, { id: 'components', label: 'Components', icon: 'components' }, { id: 'tickets', label: 'Tickets', icon: 'tickets' }, { id: 'messages', label: 'Messages', icon: 'bell' }, { id: 'help', label: 'Help Board', icon: 'help' }, { id: 'admin', label: 'Admin', icon: 'admin' }, ]; // ── Sidebar ────────────────────────────────────────────────────────── const Sidebar = ({ route, onNavigate, persona, counts = {} }) => { const [menuOpen, setMenuOpen] = React.useState(false); const menuRef = React.useRef(null); React.useEffect(() => { if (!menuOpen) return; const handleClick = (e) => { if (menuRef.current && !menuRef.current.contains(e.target)) { setMenuOpen(false); } }; document.addEventListener('mousedown', handleClick); return () => document.removeEventListener('mousedown', handleClick); }, [menuOpen]); return ( ); }; // ── Top bar ────────────────────────────────────────────────────────── const TopBar = ({ breadcrumbs = [], onSearch, onChat, onNew, onMessages, onProfile, persona, msgCount = 0 }) => { return (
{/* breadcrumbs */}
{breadcrumbs.map((b, i) => ( {i > 0 && } ))}
{/* search */} {/* chat */} {/* new */} {/* notifications / messages */} {/* user profile */}
); }; // ── Page shell (used by every page) ───────────────────────────────── const Page = ({ children, max = 1280, pad = true }) => (
{children}
); const PageHeader = ({ kicker, title, subtitle, action, meta }) => (
{kicker && (
{kicker}
)}

{title}

{subtitle && (
{subtitle}
)} {meta &&
{meta}
}
{action &&
{action}
}
); const AppShell = ({ persona, route, goto, openChat, openCompose, setTweak, children }) => { const crumbs = (() => { if (!route || route.name === 'home') return [{ label: 'Home' }]; const cap = s => s[0].toUpperCase() + s.slice(1); const out = [{ label: cap(route.name), onClick: () => goto(route.name) }]; if (route.id) { let nm = route.id; if (route.name === 'project' || route.name === 'projects') nm = (PROJ_BY_ID[route.id]||{}).name; else if (route.name === 'initiative' || route.name === 'initiatives') nm = (INI_BY_ID[route.id]||{}).name; else if (route.name === 'components') nm = (COMP_BY_ID[route.id]||{}).name; else if (route.name === 'ideas') nm = (APM_IDEAS.find(i => i.id === route.id)||{}).title; if (nm) out.push({ label: nm }); } return out; })(); const counts = { tickets: APM_TICKETS.filter(t => t.status === 'open').length, ideas: APM_IDEAS.filter(i => i.status === 'new').length, help: APM_HELP.filter(h => h.status === 'open').length, messages: APM_TICKETS.filter(t => t.assignee === persona.id).length + (persona.role === 'owner' ? 3 : 0), }; return (
{}} onChat={openChat} onNew={openCompose} onMessages={() => goto('messages')} onProfile={() => goto('profile')} persona={persona} msgCount={counts.messages} setTweak={setTweak} />
{children}
); }; Object.assign(window, { Sidebar, TopBar, Page, PageHeader, NAV, AppShell });