// App shell sidebar — SHOC design system port (ADR 0003, mirrors app-sidebar.tsx). // Expanded 244px / collapsed 76px; brand header row; grouped nav with SHOC // active treatment (white card bg + shadow + 3px left accent bar). import { useLocation, useNavigate } from 'react-router-dom'; import { Drawer, Box, Tooltip, useMediaQuery, useTheme } from '@mui/material'; import { LayoutDashboard, FileText, CirclePlus, ClipboardList, FolderOpen, Building2, BookOpen, Users, type LucideIcon, } from 'lucide-react'; import { useAuth } from '../hooks/useAuth'; import { DRAWER_WIDTH } from '../constants'; import BrandLockup, { BrandMark } from './brand/BrandLockup'; const COLLAPSED_WIDTH = 76; // --spacing-sidebar-collapsed type NavEntry = { label: string; path: string; icon: LucideIcon }; const dispatcherNav: NavEntry[] = [ { label: 'Dashboard', path: '/', icon: LayoutDashboard }, { label: 'My Proposals', path: '/proposals', icon: FileText }, { label: 'New Proposal', path: '/proposals/new', icon: CirclePlus }, ]; const adminNav: NavEntry[] = [ { label: 'Admin Queue', path: '/admin', icon: ClipboardList }, { label: 'All Proposals', path: '/admin/proposals', icon: FolderOpen }, { label: 'Customers', path: '/admin/customers', icon: Building2 }, { label: 'Pricing Library', path: '/admin/pricing-library', icon: BookOpen }, ]; const sysadminNav: NavEntry[] = [{ label: 'Users', path: '/admin/users', icon: Users }]; const ALL_NAV_PATHS = new Set([...dispatcherNav, ...adminNav, ...sysadminNav].map((n) => n.path)); function isNavActive(pathname: string, itemPath: string): boolean { if (pathname === itemPath) return true; if (ALL_NAV_PATHS.has(pathname)) return false; if (itemPath === '/' || itemPath === '/admin') return false; return pathname.startsWith(itemPath + '/'); } function NavItem({ item, active, collapsed, onClick, }: { item: NavEntry; active: boolean; collapsed: boolean; onClick: () => void; }) { const Icon = item.icon; const row = ( {active && ( )} {!collapsed && ( {item.label} )} ); if (collapsed) { return ( {row} ); } return row; } export default function Sidebar({ open, onOpenChange, }: { open: boolean; onOpenChange: (open: boolean) => void; }) { const navigate = useNavigate(); const location = useLocation(); const theme = useTheme(); const { user } = useAuth(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); // <900px const isAdmin = user?.role === 'Admin' || user?.role === 'SysAdmin'; const isSysAdmin = user?.role === 'SysAdmin'; // Desktop: hamburger toggles expanded (244) ↔ collapsed (76). // Mobile: temporary drawer, hamburger opens/closes it. const collapsed = !isMobile && !open; const width = collapsed ? COLLAPSED_WIDTH : DRAWER_WIDTH; const handleNavClick = (path: string) => { navigate(path); if (isMobile) { onOpenChange(false); } }; const groupLabel = (label: string) => ( {label} ); const groupDivider = ( ); const renderGroup = (label: string, items: NavEntry[], first: boolean) => ( <> {collapsed ? (first ? null : groupDivider) : groupLabel(label)} {items.map((item) => ( handleNavClick(item.path)} /> ))} ); return ( onOpenChange(false)} sx={{ width: isMobile ? 0 : width, flexShrink: 0, '& .MuiDrawer-paper': { width: isMobile ? DRAWER_WIDTH : width, boxSizing: 'border-box', backgroundColor: 'var(--color-sidebar-bg)', borderRight: '1px solid var(--color-sidebar-border)', borderRadius: 0, overflowX: 'hidden', transition: 'width 200ms var(--ease-default, ease)', }, }} > {/* Brand header row — 64px, white, aligns with the topbar */} {collapsed ? : } {isAdmin && renderGroup('Admin', [...adminNav, ...(isSysAdmin ? sysadminNav : [])], true)} {renderGroup('Proposals', dispatcherNav, !isAdmin)} ); }