import { AppShell, Avatar, Box, Divider, Group, Menu, NavLink, ScrollArea, Stack, Text, UnstyledButton, useComputedColorScheme, useMantineColorScheme, useMantineTheme, } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; import { Bell, ChevronDown, FileSignature, LogOut, Menu as MenuIcon, Moon, Plus, Search, Settings, Sun, User, X, } from "lucide-react"; import { type CSSProperties, Fragment, type ReactNode } from "react"; export interface SidebarItem { label: string; href: string; icon?: ReactNode; children?: SidebarItem[]; section?: string; } export interface AppLayoutProps { title?: string; sidebarItems: SidebarItem[]; activeHref?: string; onNavigate?: (href: string) => void; enableThemeToggle?: boolean; userName?: string; userEmail?: string; children: ReactNode; } function getInitials(name: string): string { return name .split(" ") .filter(Boolean) .slice(0, 2) .map((n) => n[0].toUpperCase()) .join(""); } function getActivePage( items: SidebarItem[], activePath: string, ): { label: string } | null { const path = activePath.toLowerCase(); for (const item of items) { if ( path === item.href.toLowerCase() || path.startsWith(item.href.toLowerCase() + "/") ) { return { label: item.label }; } if (item.children) { const childMatch = item.children.find( (c) => path === c.href.toLowerCase() || path.startsWith(c.href.toLowerCase() + "/"), ); if (childMatch) return { label: childMatch.label }; } } return null; } const navClassNames = (active: boolean) => { if (active) { return { root: `rounded-[10px] font-medium transition-all duration-150 bg-[#ECF6F1]! ring-1 ring-inset ring-[#0EA371]/15`, label: `text-[#0A6F4D]! font-bold!`, section: `text-[#0A6F4D]!`, }; } return { root: `rounded-[10px] font-medium transition-all duration-150 hover:bg-[#F1F4F7]!`, label: `text-edr-text! font-semibold! hover:text-[#0C1A2B]!`, section: `text-edr-text! hover:text-[#0C1A2B]!`, }; }; export function AppLayout({ title = "EDR Freight", sidebarItems, activeHref = "", onNavigate, enableThemeToggle = false, userName = "User", userEmail, children, }: AppLayoutProps) { const [mobileOpen, { toggle: toggleMobile }] = useDisclosure(); const theme = useMantineTheme(); const { setColorScheme } = useMantineColorScheme(); const computedColorScheme = useComputedColorScheme("light"); const borderColor = theme.colors["edr-border"][6]; const mutedColor = theme.colors["edr-muted"][6]; const textColor = theme.colors["edr-text"][6]; const accentColor = theme.colors["edr-accent"][6]; const bgColor = theme.colors["edr-bg"][6]; const primaryColor = theme.colors["edr-green"][5]; const primaryDarkColor = theme.colors["edr-green"][7]; const activePath = activeHref.toLowerCase(); const navigate = (href: string) => onNavigate?.(href); const toggleTheme = () => { setColorScheme(computedColorScheme === "dark" ? "light" : "dark"); }; const initials = getInitials(userName); const activePage = getActivePage(sidebarItems, activePath); const isItemActive = (item: SidebarItem) => activePath === item.href.toLowerCase() || activePath.startsWith(item.href.toLowerCase() + "/"); // Shared header "island" styles — every control is a consistent 36px chip. const islandStyle: CSSProperties = { width: 36, height: 36, borderRadius: 999, border: `1px solid ${borderColor}`, backgroundColor: "#fff", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, cursor: "pointer", }; return ( {/* ── Header (frosted glass; compact floating islands, mirrors the pen) ── */} {/* Left: sidebar toggle + page title */} {activePage ? activePage.label : title} {/* Right: search + bell + avatar */} {/* Search pill */} Search shipments, bookings… {/* Bell */} {enableThemeToggle && ( {computedColorScheme === "dark" ? ( ) : ( )} )} {/* Avatar pill */} {initials} {userName} {userEmail && ( {userEmail} )} } onClick={() => navigate("/profile")} > Profile } onClick={() => navigate("/signature")} > My signature } onClick={() => navigate("/settings")} > Settings } color="edr-green" onClick={() => navigate("/bookings/new")} > New Booking } color="red" onClick={() => navigate("/logout")} > Logout {/* ── Sidebar ── */} {/* Brand */} EDR EDR FREIGHT Ethio–Djibouti Railway {/* Nav */} {sidebarItems.map((item, i) => { const active = isItemActive(item); const hasChildren = !!item.children?.length; const childActive = item.children?.some((c) => activePath.startsWith(c.href.toLowerCase()), ) ?? false; const prevSection = sidebarItems[i - 1]?.section; const sectionLabel = item.section && item.section !== prevSection ? ( {item.section} ) : null; if (hasChildren) { return ( {sectionLabel} {item.children!.map((child) => { const cActive = activePath === child.href.toLowerCase(); return ( navigate(child.href)} classNames={navClassNames(cActive)} /> ); })} ); } return ( {sectionLabel} navigate(item.href)} classNames={navClassNames(active)} /> ); })} {/* Promo card */} {/* Train image fills the full card */} {/* Diagonal green overlay: lower-left → upper-right cut */} {/* Text sits on top of the green overlay */} Moving Africa Forward Reliable. Efficient. Connected. {/* Learn More — visible on the image area */} Learn More {/* Profile */} {initials} {userName} {userEmail && ( {userEmail} )} {/* ── Main ── */} {children} ); } export default AppLayout;