// ============================================================ // LAYOUT — sidebar + main content shell shared across every page. // Structure mirrors the vanilla app so a user moving between the two // trees during migration sees consistent navigation. // ============================================================ import { NavLink, Outlet } from 'react-router-dom'; import type { ReactNode } from 'react'; import { useQuery } from '@tanstack/react-query'; import { api } from '@/lib/api'; import type { MeOk } from '@/shared/types'; interface NavItem { to: string; label: string; available?: boolean; // false = rendered as "coming soon" stub adminOnly?: boolean; // renders only when me.user.role === 'admin' } interface NavGroup { label: string; items: NavItem[]; } const NAV: NavGroup[] = [ { label: 'Encounters', items: [ { to: '/encounter', label: 'Encounter HPI', available: true }, { to: '/dictation', label: 'Dictation HPI', available: true }, ], }, { label: 'Notes', items: [ { to: '/hospital', label: 'Hospital Course', available: true }, { to: '/chart', label: 'Chart Review', available: true }, { to: '/soap', label: 'SOAP Note', available: true }, { to: '/wellvisit', label: 'Well Visit', available: true }, { to: '/sickvisit', label: 'Sick Visit', available: true }, ], }, { label: 'Clinical Tools', items: [ { to: '/vaxschedule', label: 'Vaccine Schedule', available: true }, { to: '/catchup', label: 'Catch-Up Schedule', available: true }, { to: '/peguide', label: 'Physical Exam Guide', available: true }, { to: '/bedside', label: 'Bedside', available: true }, { to: '/calculators', label: 'Calculators', available: true }, { to: '/extensions', label: 'Pagers & Extensions', available: true }, { to: '/learning', label: 'Learning Hub', available: true }, ], }, { label: 'Account', items: [ { to: '/settings', label: 'Settings', available: true }, { to: '/faq', label: 'FAQ', available: true }, ], }, { label: 'Admin', items: [ { to: '/admin', label: 'Admin Panel', available: true, adminOnly: true }, ], }, ]; function SidebarLink({ item }: { item: NavItem }) { if (!item.available) { return (
{item.label} · pending
); } return ( 'block px-3 py-2 text-sm rounded-md transition-colors ' + (isActive ? 'bg-primary text-primary-foreground' : 'hover:bg-muted text-foreground') } > {item.label} ); } export default function Layout({ children }: { children?: ReactNode }) { // One-shot /me fetch shared across the app via React Query cache. // Settings already uses this queryKey, so the Layout gets it for free // after the first Settings visit — and vice versa. const { data: me } = useQuery({ queryKey: ['auth-me'], queryFn: () => api.get('/api/auth/me'), staleTime: 5 * 60_000, }); const isAdmin = me?.user.role === 'admin'; return (
{/* Sidebar */} {/* Main */}
{children ?? }
); }