import { ArrowLeftRight, Boxes, Building2, BarChart3, Container, FileSignature, FileText, Hammer, History, LayoutDashboard, LayoutGrid, MapPin, Network, Package, PackageCheck, PackageOpen, Paperclip, Receipt, ScrollText, Send, Settings, ShieldCheck, Ship, SlidersHorizontal, Train, Truck, Users, Wallet, LifeBuoy, TrainFront, XCircle, } from "lucide-react"; import type { AuthUser } from "@/auth/types"; import type { SidebarItem, SidebarSection } from "./types"; import { FREIGHT_PERMS, hasPermission as hasFreightPermission, isDjiboutiGl, isEthiopianGl, isSuperAdmin, } from "@/lib/permissions"; import { getCategorySidebarChildren } from "@/pages/ruleEngine/config/resources"; /** * Backoffice sidebar model: the nav tree, its permission filter, and the * flatten/lookup helpers. Lives outside App.tsx so `lib/landing.ts` can resolve * a user's first reachable route without importing the route tree (App.tsx * imports RequirePermission, which imports landing — that would cycle). */ export const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [ { title: "Main menu", items: [ { label: "Overview", href: "/dashboard/overview", icon: , permission: FREIGHT_PERMS.overview.view, }, { label: "Reports", href: "/dashboard/reports", icon: , permission: FREIGHT_PERMS.reports.view, }, { label: "Customers", href: "/dashboard/customers", icon: , permission: FREIGHT_PERMS.customers.view, }, { label: "Contracts", href: "/dashboard/contract-requests", icon: , permission: FREIGHT_PERMS.contracts.view, }, { label: "Bookings", href: "/dashboard/booking-requests", icon: , permission: FREIGHT_PERMS.bookings.view, }, { label: "Wagon cancellations", href: "/dashboard/wagon-cancellations", icon: , permission: FREIGHT_PERMS.bookings.wagonCancellationView, }, // Operations hub: per-shipment clearance-document review for services // WITHOUT customs clearing (self-clearance) — bookings only. { label: "Clearance Documents", href: "/dashboard/contracts/clearance-documents", icon: , permission: FREIGHT_PERMS.contracts.opsClearanceReview, }, { label: "Payments", href: "/dashboard/payments", icon: , permission: FREIGHT_PERMS.payments.view, }, { label: "Invoices", href: "/dashboard/invoices", icon: , permission: FREIGHT_PERMS.invoices.view, }, { label: "Support", href: "/dashboard/support", icon: , permission: FREIGHT_PERMS.support.agentView, }, ...demoItems, ], }, { // title: "Port & Terminal", items: [ { label: "Operations", icon: , children: [ { label: "Clearance", href: "/dashboard/contracts/clearance", icon: , permission: [ FREIGHT_PERMS.contracts.clearanceReview, FREIGHT_PERMS.contracts.clearanceEtActions, ], }, // { // label: "Shipment Requests", // href: "/dashboard/shipment-requests", // icon: , // permission: FREIGHT_PERMS.contracts.createBooking, // }, // Operations Path A queue: per-booking self-clearance review for // GENERAL non-customs booking instances (and legacy self-clear bookings). // { // label: "Self-Clearance Review", // href: "/dashboard/contracts/ops-clearance", // icon: , // permission: FREIGHT_PERMS.contracts.opsClearanceReview, // }, { label: "GL Djibouti Clearance", href: "/dashboard/gl-djibouti/clearance", icon: , permission: FREIGHT_PERMS.contracts.clearanceDjActions, }, { label: "Train Schedules", href: "/dashboard/operations/train-scheduling-v2", icon: , permission: FREIGHT_PERMS.trainScheduling.view, }, { label: "Batch Board", href: "/dashboard/operations/batch-board", icon: , permission: FREIGHT_PERMS.trainScheduling.view, }, { label: "First Mile", href: "/dashboard/operations/first-mile", icon: , permission: FREIGHT_PERMS.firstMile.view, }, { label: "Last Mile", href: "/dashboard/operations/last-mile", icon: , permission: FREIGHT_PERMS.lastMile.view, }, ], }, { label: "Fleet Management", icon: , children: [ { label: "Fleet Dashboard", href: "/dashboard/fleet-dashboard", icon: , permission: FREIGHT_PERMS.fleetDashboard.view, }, { label: "Routes", href: "/dashboard/routes", icon: , permission: [FREIGHT_PERMS.routes.view, FREIGHT_PERMS.fleet.view], }, { label: "Locomotives", href: "/dashboard/locomotives", icon: , permission: [ FREIGHT_PERMS.locomotives.view, FREIGHT_PERMS.fleet.view, ], }, { label: "Train Builder", href: "/dashboard/train-builder", icon: , permission: [FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view], }, // { // label: "Wagon types", // href: "/dashboard/wagon-types", // icon: , // }, { label: "Wagons", href: "/dashboard/wagons", icon: , permission: [FREIGHT_PERMS.wagons.view, FREIGHT_PERMS.fleet.view], }, { label: "Wagon Transfers", href: "/dashboard/wagon-transfers", icon: , permission: [ FREIGHT_PERMS.wagons.transferView, FREIGHT_PERMS.wagons.view, ], }, { label: "Vehicles", href: "/dashboard/vehicles", icon: , permission: FREIGHT_PERMS.vehicles.view, }, { label: "Drivers", href: "/dashboard/drivers", icon: , permission: FREIGHT_PERMS.drivers.view, }, { label: "Track Vehicles", href: "/dashboard/tracking", icon: , permission: FREIGHT_PERMS.tracking.view, }, { label: "Fuel Purchases", href: "/dashboard/fuel-purchases", icon: , permission: FREIGHT_PERMS.fuel.view, }, { label: "Fuel Analytics", href: "/dashboard/fuel-stats", icon: , permission: FREIGHT_PERMS.fuel.view, }, { label: "Maintenance", href: "/dashboard/maintenance", icon: , permission: FREIGHT_PERMS.maintenance.view, }, { label: "Work Orders", href: "/dashboard/work-orders", icon: , permission: FREIGHT_PERMS.maintenance.view, }, { label: "Compliance & Alerts", href: "/dashboard/compliance", icon: , permission: FREIGHT_PERMS.fleet.view, }, { label: "Incidents", href: "/dashboard/incidents", icon: , permission: FREIGHT_PERMS.fleet.view, }, { label: "Procurement", href: "/dashboard/procurement", icon: , permission: FREIGHT_PERMS.fleet.view, }, { label: "Financial Reports", href: "/dashboard/financial-reports", icon: , permission: FREIGHT_PERMS.fleetReports.view, }, // { // label: "Containers", // href: "/dashboard/containers", // icon: , // }, // { // label: "Cargoes", // href: "/dashboard/cargoes", // icon: , // }, ], }, { label: "Imports", href: "/dashboard/import-warehouse", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, children: [ { label: "Import Overview", href: "/dashboard/import-warehouse", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "Arrival Queue", href: "/dashboard/arrival-queue", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "Import Trucks", href: "/dashboard/import-trucks", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "EDR Last Mile Returns", href: "/dashboard/edr-last-mile-returns", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "Container Returns", href: "/dashboard/container-returns", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "Terminal Inventory", href: "/dashboard/warehouse-inventory?direction=IMPORT", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "Inventory Inquiry", href: "/dashboard/inventory-inquiry", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, ], }, { label: "Exports", href: "/dashboard/export-warehouse", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, children: [ { label: "Export Overview", href: "/dashboard/export-warehouse", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "Loading Queue", href: "/dashboard/loading-queue", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "Loaded Inventory", href: "/dashboard/loaded-inventory", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "Dispatch Queue", href: "/dashboard/dispatch-queue", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "Djibouti Unloading", href: "/dashboard/export-djibouti-unloading", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "Interchange Documents", href: "/dashboard/interchange-documents", icon: , permission: FREIGHT_PERMS.interchangeDocuments.view, }, { label: "Terminal Inventory", href: "/dashboard/warehouse-inventory?direction=EXPORT", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, ], }, { label: "Intercity", href: "/dashboard/intercity", icon: , permission: FREIGHT_PERMS.trainScheduling.view, children: [ { label: "Intercity Cargo", href: "/dashboard/intercity", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, ], }, { label: "Warehouse Management", icon: , children: [ { label: "Warehouse Dashboard", href: "/dashboard/warehouse-dashboard", icon: , permission: FREIGHT_PERMS.warehouseDashboard.view, }, { // Yard-wide, not per-direction: the gate sees import and export // trucks at the same barrier. label: "Trucks on Site", href: "/dashboard/trucks-on-site", icon: , permission: FREIGHT_PERMS.warehouseInventory.view, }, { label: "Warehouses", href: "/dashboard/warehouses", icon: , permission: FREIGHT_PERMS.warehouses.view, }, { label: "Allocation & Fees", href: "/dashboard/warehouse-rules", icon: , permission: [ FREIGHT_PERMS.warehouseAllocationRules.view, FREIGHT_PERMS.warehouseFeeRules.view, ], }, { label: "Fee Invoices", href: "/dashboard/warehouse-fee-invoices", icon: , permission: FREIGHT_PERMS.warehouseFeeInvoices.view, }, ], }, ], }, { title: "Freight configuration", mutedTitle: true, items: [ { label: "File settings", href: "/dashboard/file-settings", icon: , permission: FREIGHT_PERMS.admin, }, { label: "Dropdown settings", href: "/dashboard/dropdown-settings", icon: , permission: FREIGHT_PERMS.admin, }, { label: "Contract templates", href: "/dashboard/contract-templates", icon: , permission: FREIGHT_PERMS.admin, }, { label: "Audit logs", href: "/dashboard/audit-logs", icon: , permission: FREIGHT_PERMS.audit.view, }, { label: "Configuration", href: "/dashboard/configuration", icon: , children: [ ...getCategorySidebarChildren("configuration"), { label: "Train scheduling rules", href: "/dashboard/configuration/train-scheduling-rules", permission: FREIGHT_PERMS.trainScheduling.rulesManage, }, { label: "Trade access", href: "/dashboard/configuration/trade-access", permission: FREIGHT_PERMS.admin, }, { label: "Exchange rate", href: "/dashboard/configuration/exchange-rate", permission: FREIGHT_PERMS.admin, }, ], }, { label: "Rules", href: "/dashboard/rules", icon: , children: getCategorySidebarChildren("rules"), }, { label: "Staff", href: "/user-management", icon: , permission: [ FREIGHT_PERMS.admin, FREIGHT_PERMS.staff.roles.view, FREIGHT_PERMS.staff.employeeRegistration.view, FREIGHT_PERMS.staff.roleAssignment.view, ], }, ], }, ]; /** Hrefs of the two document-clearance menu items (stable identifiers). */ export const ET_CLEARANCE_HREF = "/dashboard/contracts/clearance"; export const DJ_CLEARANCE_HREF = "/dashboard/gl-djibouti/clearance"; // Routes a GL officer may reach beyond their clearance hub. Path B booking is // part of their job (create/rebook under a cleared contract, then view that // booking's clearance), but those routes live outside the clearance prefix — // without this allowlist the single-prefix lock bounces them out of their own // workflow. Matched against location.pathname (no query string). export const GL_WORKFLOW_PATH_PATTERNS: RegExp[] = [ /^\/dashboard\/contracts\/[^/]+\/create-booking(\/|$)/, /^\/dashboard\/bookings\/[^/]+\/clearance(\/|$)/, // The ET hub's rows open the shipment clearance detail at this URL. /^\/dashboard\/clearance\/[^/]+(\/|$)/, ]; const isEtClearanceItem = (item: SidebarItem): boolean => item.href === ET_CLEARANCE_HREF; const isDjClearanceItem = (item: SidebarItem): boolean => item.href === DJ_CLEARANCE_HREF; const isClearanceItem = (item: SidebarItem): boolean => isEtClearanceItem(item) || isDjClearanceItem(item); /** * Keep only items the user is permitted to see; drop now-empty sections. * * Position-scoped visibility (super_admin sees everything): * - Super Admin → sees all items (all permissions pass, all tabs visible) * - Ethiopian GL → sees ONLY the ET document-clearance page. * - Djibouti GL → sees ONLY the DJ clearance page. * - Everyone else → sees everything they have permission for, EXCEPT the two * clearance pages (those are GL-only). */ export const filterSidebarByPermission = ( sections: SidebarSection[], user: AuthUser | null | undefined, ): SidebarSection[] => { // Superadmin sees every section and item — no permission filtering. if (isSuperAdmin(user)) return sections; const etGl = isEthiopianGl(user); const djGl = isDjiboutiGl(user); const permissionAllowed = (item: SidebarItem): boolean => { if (!item.permission) return true; const keys = Array.isArray(item.permission) ? item.permission : [item.permission]; return keys.some((key) => hasFreightPermission(user, key)); }; // Recursive: children are filtered first; a group (item with children) stays // only while it still has visible children — so parents without their own // permission key never leak a whole subtree the user cannot open. const filterItems = (items: SidebarItem[]): SidebarItem[] => items .map((item) => item.children ? { ...item, children: filterItems(item.children) } : item, ) .filter((item) => { if (etGl || djGl) { // GL positions are locked to their single clearance page (parents // survive only as the path to that page). const isTarget = etGl ? isEtClearanceItem : isDjClearanceItem; return isTarget(item) || (item.children?.length ?? 0) > 0; } // Everyone else: hide the GL-only clearance pages entirely. if (isClearanceItem(item)) return false; if (!permissionAllowed(item)) return false; if (item.children) return item.children.length > 0; return true; }); return sections .map((section) => ({ ...section, items: filterItems(section.items), })) .filter((section) => section.items.length > 0); }; export const APP_TITLE = "EDR Freight Backoffice"; /** Flatten sidebar sections (incl. nested children) into {href, label} pairs. */ export const flattenSidebarItems = ( sections: SidebarSection[], ): { href: string; label: string }[] => sections.flatMap((section) => section.items.flatMap((item) => [ ...(item.href ? [{ href: item.href, label: item.label }] : []), ...(item.children ?? []) .filter((child): child is SidebarItem & { href: string } => Boolean(child.href), ) .map((child) => ({ href: child.href, label: child.label })), ]), ); /** Find the sidebar label whose href matches (exactly or as a prefix of) the current path. */ export const findActiveSidebarLabel = ( pathname: string, sections: SidebarSection[], ): string | undefined => { const path = pathname.toLowerCase(); const candidates = flattenSidebarItems(sections) .map(({ href, label }) => ({ label, href: href.split("?")[0].toLowerCase(), })) .sort((a, b) => b.href.length - a.href.length); return candidates.find( ({ href }) => path === href || path.startsWith(`${href}/`), )?.label; };