// 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)}
);
}