import { ArrowLeftRight, BookOpen, Boxes, Building2, BarChart3, Container, FileSignature, FileText, Hammer, History, Landmark, 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, 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: "USD Payments", href: "/dashboard/usd-payments", 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, }, { label: "Locomotives", href: "/dashboard/locomotives", icon: , permission: FREIGHT_PERMS.locomotives.view, }, { label: "Train Builder", href: "/dashboard/train-builder", icon: , permission: FREIGHT_PERMS.trains.view, }, // { // label: "Wagon types", // href: "/dashboard/wagon-types", // icon: , // }, { label: "Wagons", href: "/dashboard/wagons", icon: , permission: FREIGHT_PERMS.wagons.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.compliance.view, }, { label: "Incidents", href: "/dashboard/incidents", icon: , // No dedicated backend key exists for incidents yet. Not part of // the fleet.view/admin fallback cleanup — removing fleet.view // here with nothing to replace it would lock the page to // super-admin only, so it stays as the sole (if coarse) gate. permission: FREIGHT_PERMS.fleet.view, }, { label: "Procurement", href: "/dashboard/procurement", icon: , permission: FREIGHT_PERMS.procurement.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: "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.settings.fileUpload.view, }, { label: "Dropdown settings", href: "/dashboard/dropdown-settings", icon: , permission: FREIGHT_PERMS.settings.dropdown.view, }, { label: "Stamp settings", href: "/dashboard/stamp-settings", icon: , permission: FREIGHT_PERMS.settings.stamp.view, }, { label: "Invoice stamp", href: "/dashboard/invoice-stamp-settings", icon: , permission: FREIGHT_PERMS.settings.invoiceStamp.view, }, { label: "Contract templates", href: "/dashboard/contract-templates", icon: , // `view` opens the page; `read` alone is API-only and shows no menu. permission: FREIGHT_PERMS.settings.contractTemplates.view, }, { label: "Portal content", href: "/dashboard/portal-content", icon: , permission: [ FREIGHT_PERMS.settings.supportContent.view, FREIGHT_PERMS.settings.supportContent.manage, ], }, { label: "Audit logs", href: "/dashboard/audit-logs", icon: , permission: FREIGHT_PERMS.auditLog.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.tradeAccess.view, }, { label: "Exchange rate", href: "/dashboard/configuration/exchange-rate", permission: FREIGHT_PERMS.settings.exchangeRate.view, }, ], }, { 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, ], }, ], }, ]; /** * Keep only items the user is permitted to see; drop now-empty sections. * Super Admin sees everything; everyone else is filtered purely by each * item's `permission` field (OR across the array when one is given). */ 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 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 (!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; };