import { type MouseEvent, type ReactNode, useCallback, useEffect, useMemo, useState, } from "react"; import { ChevronDown, Train } from "lucide-react"; import { Box, Stack, Text } from "@mantine/core"; import type { SidebarItem, SidebarSection } from "./types"; import "./FreightSidebar.css"; export interface FreightSidebarProps { sections: SidebarSection[]; activeHref?: string; onNavigate?: (href: string) => void; } const sidebarItemKey = (item: SidebarItem, parentKey: string) => item.href ?? `${parentKey}::${item.label}`; const collectSidebarHrefs = (items: SidebarItem[]): string[] => items.flatMap((item) => { const hrefs: string[] = []; if (item.href) hrefs.push(item.href.toLowerCase()); if (item.children?.length) hrefs.push(...collectSidebarHrefs(item.children)); return hrefs; }); const flattenSectionItems = (sections: SidebarSection[]) => sections.flatMap((section) => section.items); const FreightSidebar = ({ sections, activeHref, onNavigate, }: FreightSidebarProps) => { const items = useMemo(() => flattenSectionItems(sections), [sections]); const activePath = activeHref?.toLowerCase() ?? ""; const isHrefActive = useCallback( (href: string) => { const normalized = href.toLowerCase(); return ( activePath === normalized || activePath.startsWith(`${normalized}/`) ); }, [activePath], ); const branchContainsActive = useCallback( (branch: SidebarItem[]) => collectSidebarHrefs(branch).some((href) => isHrefActive(href)), [isHrefActive], ); const defaultExpanded = useMemo(() => { const acc: Record = {}; const walk = (entries: SidebarItem[], parentKey: string) => { for (const entry of entries) { if (!entry.children?.length) continue; const key = sidebarItemKey(entry, parentKey); acc[key] = branchContainsActive(entry.children) || (entry.href ? isHrefActive(entry.href) : false); walk(entry.children, key); } }; for (const item of items) { if (!item.children?.length) continue; const key = item.href ?? item.label; acc[key] = activePath === key.toLowerCase() || activePath.startsWith(`${key.toLowerCase()}/`) || branchContainsActive(item.children); walk(item.children, key); } return acc; }, [activePath, branchContainsActive, isHrefActive, items]); const [expanded, setExpanded] = useState>(defaultExpanded); useEffect(() => { setExpanded((current) => ({ ...defaultExpanded, ...current })); }, [defaultExpanded]); const navigateTo = (event: MouseEvent, href: string) => { if (onNavigate) { event.preventDefault(); onNavigate(href); } }; const toggleExpanded = (key: string) => { setExpanded((current) => ({ ...current, [key]: !current[key] })); }; const renderNavBranch = ( children: SidebarItem[], depth: number, parentKey: string, ): ReactNode => children.map((child) => { const key = sidebarItemKey(child, parentKey); const isGroup = Boolean(child.children?.length) && !child.href; if (isGroup) { const isOpen = expanded[key] ?? false; const groupActive = branchContainsActive(child.children!); return (
{isOpen && (
{renderNavBranch(child.children!, depth + 1, key)}
)}
); } if (!child.href) return null; const childActive = isHrefActive(child.href); return ( navigateTo(e, child.href!)} > {child.label} ); }); const renderTopLevelItem = (item: SidebarItem) => { if (!item.href) return null; const hasChildren = Boolean(item.children?.length); const itemHref = item.href.toLowerCase(); const childActive = hasChildren ? branchContainsActive(item.children!) : false; const isCurrentItem = hasChildren ? activePath === itemHref : isHrefActive(itemHref); const isActive = isCurrentItem || childActive; const isOpen = expanded[item.href] ?? false; return ( { if (hasChildren) { setExpanded((current) => ({ ...current, [item.href!]: true, })); } navigateTo(e, item.href!); }} > {item.icon && {item.icon}} {item.label} {hasChildren && ( )} {hasChildren && isOpen && (
{renderNavBranch(item.children!, 0, item.href)}
)}
); }; return (
EDR Freight Backoffice Console
All systems operational EDR Platform ยท v1.0
); }; export default FreightSidebar;