mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
The customers:* keys were seeded and present in the backoffice constants but
enforced nowhere except reset-password. Customer CRUD sat behind the coarse
edr_freight_app:admin umbrella, and every company read endpoint was unguarded.
Two routes could not be gated on the route alone, because the authority they
need depends on the request BODY, not the path:
- PATCH /companies/:id carries `status` (UpdateCompanyDto extends
PartialType(CreateCompanyDto)), so it both edits fields and blacklists.
- PATCH /company-profiles/:profileId/status is approve, reject, suspend and
blacklist on one route.
Both now take a one-of route guard and assert per-status against a shared
STATUS_PERM map: approving/reactivating needs customers:verify, suspending or
blacklisting needs customers:deactivate. PATCH /companies/:id additionally
requires customers:update when any non-status field is present, so a caller
holding only deactivate cannot rename a company. The backoffice mirrors the
same map so no button is offered that the server would reject.
GET /companies/:companyId/documents is left authenticated-only with the split
in the handler: it is dual-audience. The portal reads its own documents during
onboarding, and the contract-request detail page (gated on contracts:view)
reads the applicant's. Gating it on customers:view alone would have 403'd
customers on their own documents and blanked the contract reviewer's panel.
The two by-company customer-view reads take a one-of guard for the same reason
— otherwise a staffer granted only customers:view gets a detail page whose tabs
403 individually.
Frontend: the customers routes were sidebar-filtered but not wrapped in
RequirePermission, so direct URL navigation rendered them for anyone.
Verified: freight-api type-check clean; backoffice type-check unchanged from
HEAD (pre-existing errors only); 25 tests pass across the companies and
freight-permission suites. Not exercised against a running API.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1550 lines
51 KiB
TypeScript
1550 lines
51 KiB
TypeScript
import {
|
|
Boxes,
|
|
Building2,
|
|
Container,
|
|
FileSignature,
|
|
FileText,
|
|
Hammer,
|
|
LayoutDashboard,
|
|
LayoutGrid,
|
|
MapPin,
|
|
Network,
|
|
Package,
|
|
PackageCheck,
|
|
PackageOpen,
|
|
Paperclip,
|
|
Receipt,
|
|
ScrollText,
|
|
Send,
|
|
Settings,
|
|
ShieldCheck,
|
|
Ship,
|
|
SlidersHorizontal,
|
|
Train,
|
|
Truck,
|
|
Users,
|
|
Wallet,
|
|
LifeBuoy,
|
|
TrainFront,
|
|
} from "lucide-react";
|
|
import { useEffect } from "react";
|
|
import {
|
|
Navigate,
|
|
Outlet,
|
|
Route,
|
|
Routes,
|
|
useLocation,
|
|
useNavigate,
|
|
useParams,
|
|
} from "react-router-dom";
|
|
|
|
import {
|
|
FreightDashboardLayout,
|
|
type SidebarItem,
|
|
type SidebarSection,
|
|
} from "@/components/layout";
|
|
import { useAuth } from "./auth/useAuth";
|
|
import LoadingScreen from "./components/LoadingScreen";
|
|
import LoginPage from "./pages/auth/LoginPage";
|
|
import ForgotPasswordPage from "./pages/auth/ForgotPasswordPage";
|
|
import BookingContractPage from "./pages/bookings/BookingContractPage";
|
|
import BookingRequestDetailPage from "./pages/bookings/BookingRequestDetailPage";
|
|
import BookingRequestsPage from "./pages/bookings/BookingRequestsPage";
|
|
import NewBookingPage from "./pages/bookings/NewBookingPage";
|
|
import ContractRequestsPage from "./pages/contracts/ContractRequestsPage";
|
|
import ContractRequestDetailPage from "./pages/contracts/ContractRequestDetailPage";
|
|
import ContractViewPage from "./pages/contracts/ContractViewPage";
|
|
import ContractClearanceListPage from "./pages/contracts/ContractClearanceListPage";
|
|
import ContractClearanceDetailPage from "./pages/contracts/ContractClearanceDetailPage";
|
|
import ClearanceDocumentsPage from "./pages/contracts/ClearanceDocumentsPage";
|
|
import GlDjiboutiClearanceListPage from "./pages/contracts/GlDjiboutiClearanceListPage";
|
|
import GlClearanceDetailPage from "./pages/contracts/GlClearanceDetailPage";
|
|
import ShipmentRequestsPage from "./pages/contracts/ShipmentRequestsPage";
|
|
import ShipmentRequestDetailPage from "./pages/contracts/ShipmentRequestDetailPage";
|
|
import GlCreateBookingForm from "./components/contracts/GlCreateBookingForm";
|
|
import DocumentClearanceDetailPage from "./pages/bookings/DocumentClearanceDetailPage";
|
|
import DocumentClearanceListPage from "./pages/bookings/DocumentClearanceListPage";
|
|
import CustomerDetailPage from "./pages/customers/CustomerDetailPage";
|
|
import CustomersPage from "./pages/customers/CustomersPage";
|
|
import InvoiceDetailPage from "./pages/invoices/InvoiceDetailPage";
|
|
import InvoicesPage from "./pages/invoices/InvoicesPage";
|
|
import MyProfilePage from "./pages/dashboard/MyProfilePage";
|
|
import OverviewPage from "./pages/dashboard/OverviewPage";
|
|
import AiBookingMockTestPage from "./pages/ai/AiBookingMockTestPage";
|
|
import PaymentsPage from "./pages/payments/PaymentsPage";
|
|
//import EmployeesPage from "./pages/dashboard/user-management/EmployeesPage";
|
|
import { RequirePermission } from "./components/auth/RequirePermission";
|
|
import {
|
|
FREIGHT_PERMS,
|
|
hasPermission as hasFreightPermission,
|
|
isDjiboutiGl,
|
|
isEthiopianGl,
|
|
isSuperAdmin,
|
|
} from "./lib/permissions";
|
|
import FileUploadSettingsPage from "./pages/documents/FileUploadSettingsPage";
|
|
import DropdownSettingsPage from "./pages/dropdown_settings/DropdownSettingsPage";
|
|
import ContractTemplatesPage from "./pages/contract_templates/ContractTemplatesPage";
|
|
import ContractTemplateEditorPage from "./pages/contract_templates/ContractTemplateEditorPage";
|
|
import FleetResourcePage from "./pages/fleet/FleetResourcePage";
|
|
import VehicleDetailPage from "./pages/fleet/VehicleDetailPage";
|
|
import DriverDetailPage from "./pages/fleet/DriverDetailPage";
|
|
import RoutesPage from "./pages/fleet/RoutesPage";
|
|
import FuelPurchasePage from "./pages/fleet/FuelPurchasePage";
|
|
import FuelStatsPage from "./pages/fleet/FuelStatsPage";
|
|
import { MaintenancePage } from "./pages/fleet/MaintenancePage";
|
|
import { FinancialReportsPage } from "./pages/fleet/FinancialReportsPage";
|
|
import { FleetDashboard } from "./pages/fleet/FleetDashboard";
|
|
import { TrackingPage } from "./pages/fleet/TrackingPage";
|
|
import CompliancePage from "./pages/fleet/CompliancePage";
|
|
import IncidentsPage from "./pages/fleet/IncidentsPage";
|
|
import WorkOrdersPage from "./pages/fleet/WorkOrdersPage";
|
|
import ProcurementPage from "./pages/fleet/ProcurementPage";
|
|
import { getCategorySidebarChildren } from "./pages/ruleEngine/config/resources";
|
|
import RuleEngineLegacyRedirect from "./pages/ruleEngine/RuleEngineLegacyRedirect";
|
|
import RuleEngineResourcePage from "./pages/ruleEngine/RuleEngineResourcePage";
|
|
import CargoTypesPage from "./pages/ruleEngine/CargoTypesPage";
|
|
import TrainScheduleV2ListPage from "./pages/trainScheduling/TrainScheduleV2ListPage";
|
|
import BatchBoardPage from "./pages/trainScheduling/BatchBoardPage";
|
|
import BatchScheduleDetailPage from "./pages/trainScheduling/BatchScheduleDetailPage";
|
|
import TrainScheduleV2DetailPage from "./pages/trainScheduling/TrainScheduleV2DetailPage";
|
|
import TrainScheduleTrackPage from "./pages/trainScheduling/TrainScheduleTrackPage";
|
|
import TrainSchedulingGlobalRulesPage from "./pages/trainScheduling/TrainSchedulingGlobalRulesPage";
|
|
import ContractValidityPeriodsPage from "./pages/configuration/ContractValidityPeriodsPage";
|
|
import FirstMilePage from "./pages/operations/FirstMilePage";
|
|
import LastMilePage from "./pages/operations/LastMilePage";
|
|
import TrainDetailPage from "./pages/trains/TrainDetailPage";
|
|
import TrainBuilderDetailPage from "./pages/trainBuilder/TrainBuilderDetailPage";
|
|
import TrainBuilderListPage from "./pages/trainBuilder/TrainBuilderListPage";
|
|
import ArrivalQueuePage from "./pages/warehouses/ArrivalQueuePage";
|
|
import DispatchQueuePage from "./pages/warehouses/DispatchQueuePage";
|
|
import ExportDjiboutiUnloadingQueuePage from "./pages/warehouses/ExportDjiboutiUnloadingQueuePage";
|
|
import ExportWarehouseFlowPage from "./pages/warehouses/ExportWarehouseFlowPage";
|
|
import ImportWarehouseFlowPage from "./pages/warehouses/ImportWarehouseFlowPage";
|
|
import InterchangeDocumentsPage from "./pages/warehouses/InterchangeDocumentsPage";
|
|
import InventoryInquiryPage from "./pages/warehouses/InventoryInquiryPage";
|
|
import LoadedInventoryPage from "./pages/warehouses/LoadedInventoryPage";
|
|
import LoadingQueuePage from "./pages/warehouses/LoadingQueuePage";
|
|
import IntercityPage from "./pages/warehouses/IntercityPage";
|
|
import TrucksOnSitePage from "./pages/warehouses/TrucksOnSitePage";
|
|
import WarehouseDashboardPage from "./pages/warehouses/WarehouseDashboardPage";
|
|
import WarehouseDetailPage from "./pages/warehouses/WarehouseDetailPage";
|
|
import WarehouseInventoryPage from "./pages/warehouses/WarehouseInventoryPage";
|
|
import WarehouseInvoicesPage from "./pages/warehouses/WarehouseInvoicesPage";
|
|
import WarehouseListPage from "./pages/warehouses/WarehouseListPage";
|
|
import WarehouseRulesPage from "./pages/warehouses/WarehouseRulesPage";
|
|
import { HealthCheck } from "./features/health/HealthCheck";
|
|
import FaydaCallbackPage from "./pages/FaydaCallbackPage";
|
|
import { UserManagementRoutes } from "./user-management/route";
|
|
import SetPassword from "./shared/components/SetPassword";
|
|
import SupportInboxPage from "./pages/support/SupportInboxPage";
|
|
|
|
const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [
|
|
{
|
|
title: "Main menu",
|
|
items: [
|
|
{
|
|
label: "Overview",
|
|
href: "/dashboard/overview",
|
|
icon: <LayoutDashboard />,
|
|
permission: FREIGHT_PERMS.overview.view,
|
|
},
|
|
{
|
|
label: "Customers",
|
|
href: "/dashboard/customers",
|
|
icon: <Building2 />,
|
|
permission: FREIGHT_PERMS.customers.view,
|
|
},
|
|
{
|
|
label: "Contracts",
|
|
href: "/dashboard/contract-requests",
|
|
icon: <FileSignature />,
|
|
permission: FREIGHT_PERMS.contracts.view,
|
|
},
|
|
{
|
|
label: "Bookings",
|
|
href: "/dashboard/booking-requests",
|
|
icon: <FileText />,
|
|
permission: FREIGHT_PERMS.bookings.view,
|
|
},
|
|
// Operations hub: clearance-document review for contracts WITHOUT
|
|
// customs clearing (contract-level for one-time, per-booking for general).
|
|
{
|
|
label: "Clearance Documents",
|
|
href: "/dashboard/contracts/clearance-documents",
|
|
icon: <ShieldCheck />,
|
|
permission: FREIGHT_PERMS.contracts.opsClearanceReview,
|
|
},
|
|
{
|
|
label: "Payments",
|
|
href: "/dashboard/payments",
|
|
icon: <Wallet />,
|
|
permission: FREIGHT_PERMS.bookings.view,
|
|
},
|
|
{
|
|
label: "Invoices",
|
|
href: "/dashboard/invoices",
|
|
icon: <Receipt />,
|
|
permission: FREIGHT_PERMS.bookings.view,
|
|
},
|
|
{
|
|
label: "Support",
|
|
href: "/dashboard/support",
|
|
icon: <LifeBuoy />,
|
|
permission: FREIGHT_PERMS.support.view,
|
|
},
|
|
...demoItems,
|
|
],
|
|
},
|
|
{
|
|
// title: "Port & Terminal",
|
|
items: [
|
|
{
|
|
label: "Operations",
|
|
icon: <Settings />,
|
|
children: [
|
|
{
|
|
label: "Clearance",
|
|
href: "/dashboard/contracts/clearance",
|
|
icon: <ShieldCheck />,
|
|
permission: [
|
|
FREIGHT_PERMS.contracts.clearanceReview,
|
|
FREIGHT_PERMS.contracts.clearanceEtActions,
|
|
],
|
|
},
|
|
// {
|
|
// label: "Shipment Requests",
|
|
// href: "/dashboard/shipment-requests",
|
|
// icon: <Send />,
|
|
// 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: <ShieldCheck />,
|
|
// permission: FREIGHT_PERMS.contracts.opsClearanceReview,
|
|
// },
|
|
{
|
|
label: "GL Djibouti Clearance",
|
|
href: "/dashboard/gl-djibouti/clearance",
|
|
icon: <Ship />,
|
|
permission: FREIGHT_PERMS.contracts.clearanceDjActions,
|
|
},
|
|
{
|
|
label: "Train Schedules",
|
|
href: "/dashboard/operations/train-scheduling-v2",
|
|
icon: <Train />,
|
|
permission: FREIGHT_PERMS.trainScheduling.view,
|
|
},
|
|
{
|
|
label: "Batch Board",
|
|
href: "/dashboard/operations/batch-board",
|
|
icon: <LayoutGrid />,
|
|
permission: FREIGHT_PERMS.trainScheduling.view,
|
|
},
|
|
{
|
|
label: "First Mile",
|
|
href: "/dashboard/operations/first-mile",
|
|
icon: <Truck />,
|
|
permission: FREIGHT_PERMS.firstMile.view,
|
|
},
|
|
{
|
|
label: "Last Mile",
|
|
href: "/dashboard/operations/last-mile",
|
|
icon: <Truck />,
|
|
permission: FREIGHT_PERMS.lastMile.view,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: "Fleet Management",
|
|
icon: <Truck />,
|
|
children: [
|
|
{
|
|
label: "Fleet Dashboard",
|
|
href: "/dashboard/fleet-dashboard",
|
|
icon: <LayoutDashboard />,
|
|
permission: FREIGHT_PERMS.fleetDashboard.view,
|
|
},
|
|
{
|
|
label: "Routes",
|
|
href: "/dashboard/routes",
|
|
icon: <Network />,
|
|
permission: [FREIGHT_PERMS.routes.view, FREIGHT_PERMS.fleet.view],
|
|
},
|
|
{
|
|
label: "Locomotives",
|
|
href: "/dashboard/locomotives",
|
|
icon: <Train />,
|
|
permission: [FREIGHT_PERMS.locomotives.view, FREIGHT_PERMS.fleet.view],
|
|
},
|
|
{
|
|
label: "Train Builder",
|
|
href: "/dashboard/train-builder",
|
|
icon: <Hammer />,
|
|
permission: [FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view],
|
|
},
|
|
|
|
// {
|
|
// label: "Wagon types",
|
|
// href: "/dashboard/wagon-types",
|
|
// icon: <Boxes />,
|
|
// },
|
|
{
|
|
label: "Wagons",
|
|
href: "/dashboard/wagons",
|
|
icon: <Truck />,
|
|
permission: [FREIGHT_PERMS.wagons.view, FREIGHT_PERMS.fleet.view],
|
|
},
|
|
{
|
|
label: "Vehicles",
|
|
href: "/dashboard/vehicles",
|
|
icon: <Truck />,
|
|
permission: FREIGHT_PERMS.vehicles.view,
|
|
},
|
|
{
|
|
label: "Drivers",
|
|
href: "/dashboard/drivers",
|
|
icon: <Users />,
|
|
permission: FREIGHT_PERMS.drivers.view,
|
|
},
|
|
{
|
|
label: "Track Vehicles",
|
|
href: "/dashboard/tracking",
|
|
icon: <MapPin />,
|
|
permission: FREIGHT_PERMS.tracking.view,
|
|
},
|
|
{
|
|
label: "Fuel Purchases",
|
|
href: "/dashboard/fuel-purchases",
|
|
icon: <Truck />,
|
|
permission: FREIGHT_PERMS.fuel.view,
|
|
},
|
|
{
|
|
label: "Fuel Analytics",
|
|
href: "/dashboard/fuel-stats",
|
|
icon: <Truck />,
|
|
permission: FREIGHT_PERMS.fuel.view,
|
|
},
|
|
{
|
|
label: "Maintenance",
|
|
href: "/dashboard/maintenance",
|
|
icon: <Truck />,
|
|
permission: FREIGHT_PERMS.maintenance.view,
|
|
},
|
|
{
|
|
label: "Work Orders",
|
|
href: "/dashboard/work-orders",
|
|
icon: <SlidersHorizontal />,
|
|
permission: FREIGHT_PERMS.maintenance.view,
|
|
},
|
|
{
|
|
label: "Compliance & Alerts",
|
|
href: "/dashboard/compliance",
|
|
icon: <ShieldCheck />,
|
|
permission: FREIGHT_PERMS.fleet.view,
|
|
},
|
|
{
|
|
label: "Incidents",
|
|
href: "/dashboard/incidents",
|
|
icon: <FileText />,
|
|
permission: FREIGHT_PERMS.fleet.view,
|
|
},
|
|
{
|
|
label: "Procurement",
|
|
href: "/dashboard/procurement",
|
|
icon: <Package />,
|
|
permission: FREIGHT_PERMS.fleet.view,
|
|
},
|
|
{
|
|
label: "Financial Reports",
|
|
href: "/dashboard/financial-reports",
|
|
icon: <Wallet />,
|
|
permission: FREIGHT_PERMS.fleetReports.view,
|
|
},
|
|
// {
|
|
// label: "Containers",
|
|
// href: "/dashboard/containers",
|
|
// icon: <Container />,
|
|
// },
|
|
// {
|
|
// label: "Cargoes",
|
|
// href: "/dashboard/cargoes",
|
|
// icon: <Package />,
|
|
// },
|
|
],
|
|
},
|
|
{
|
|
label: "Imports",
|
|
href: "/dashboard/import-warehouse",
|
|
icon: <PackageOpen />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
children: [
|
|
{
|
|
label: "Import Overview",
|
|
href: "/dashboard/import-warehouse",
|
|
icon: <PackageOpen />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
{
|
|
label: "Arrival Queue",
|
|
href: "/dashboard/arrival-queue",
|
|
icon: <PackageOpen />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
{
|
|
label: "Dispatch Queue",
|
|
href: "/dashboard/dispatch-queue",
|
|
icon: <Send />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
{
|
|
label: "Terminal Inventory",
|
|
href: "/dashboard/warehouse-inventory?direction=IMPORT",
|
|
icon: <Package />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
{
|
|
label: "Inventory Inquiry",
|
|
href: "/dashboard/inventory-inquiry",
|
|
icon: <Boxes />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: "Exports",
|
|
href: "/dashboard/export-warehouse",
|
|
icon: <Truck />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
children: [
|
|
{
|
|
label: "Export Overview",
|
|
href: "/dashboard/export-warehouse",
|
|
icon: <Truck />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
{
|
|
label: "Loading Queue",
|
|
href: "/dashboard/loading-queue",
|
|
icon: <Truck />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
{
|
|
label: "Loaded Inventory",
|
|
href: "/dashboard/loaded-inventory",
|
|
icon: <PackageCheck />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
{
|
|
label: "Dispatch Queue",
|
|
href: "/dashboard/dispatch-queue",
|
|
icon: <Send />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
{
|
|
label: "Djibouti Unloading",
|
|
href: "/dashboard/export-djibouti-unloading",
|
|
icon: <PackageOpen />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
{
|
|
label: "Interchange Documents",
|
|
href: "/dashboard/interchange-documents",
|
|
icon: <FileText />,
|
|
permission: FREIGHT_PERMS.interchangeDocuments.view,
|
|
},
|
|
{
|
|
label: "Terminal Inventory",
|
|
href: "/dashboard/warehouse-inventory?direction=EXPORT",
|
|
icon: <Package />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: "Intercity",
|
|
href: "/dashboard/intercity",
|
|
icon: <TrainFront />,
|
|
permission: FREIGHT_PERMS.trainScheduling.view,
|
|
children: [
|
|
{
|
|
label: "Intercity Cargo",
|
|
href: "/dashboard/intercity",
|
|
icon: <TrainFront />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: "Warehouse Management",
|
|
icon: <Container />,
|
|
children: [
|
|
{
|
|
label: "Warehouse Dashboard",
|
|
href: "/dashboard/warehouse-dashboard",
|
|
icon: <LayoutDashboard />,
|
|
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: <Truck />,
|
|
permission: FREIGHT_PERMS.warehouseInventory.view,
|
|
},
|
|
{
|
|
label: "Warehouses",
|
|
href: "/dashboard/warehouses",
|
|
icon: <Container />,
|
|
permission: FREIGHT_PERMS.warehouses.view,
|
|
},
|
|
{
|
|
label: "Allocation & Fees",
|
|
href: "/dashboard/warehouse-rules",
|
|
icon: <SlidersHorizontal />,
|
|
permission: [
|
|
FREIGHT_PERMS.warehouseAllocationRules.view,
|
|
FREIGHT_PERMS.warehouseFeeRules.view,
|
|
],
|
|
},
|
|
{
|
|
label: "Fee Invoices",
|
|
href: "/dashboard/warehouse-fee-invoices",
|
|
icon: <Wallet />,
|
|
permission: FREIGHT_PERMS.warehouseFeeInvoices.view,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: "Freight configuration",
|
|
mutedTitle: true,
|
|
items: [
|
|
{
|
|
label: "File settings",
|
|
href: "/dashboard/file-settings",
|
|
icon: <Paperclip />,
|
|
permission: FREIGHT_PERMS.admin,
|
|
},
|
|
{
|
|
label: "Dropdown settings",
|
|
href: "/dashboard/dropdown-settings",
|
|
icon: <Settings />,
|
|
permission: FREIGHT_PERMS.admin,
|
|
},
|
|
{
|
|
label: "Contract templates",
|
|
href: "/dashboard/contract-templates",
|
|
icon: <ScrollText />,
|
|
permission: FREIGHT_PERMS.admin,
|
|
},
|
|
{
|
|
label: "Configuration",
|
|
href: "/dashboard/configuration",
|
|
icon: <Boxes />,
|
|
children: [
|
|
...getCategorySidebarChildren("configuration"),
|
|
{
|
|
label: "Train scheduling rules",
|
|
href: "/dashboard/configuration/train-scheduling-rules",
|
|
permission: FREIGHT_PERMS.trainScheduling.rulesManage,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: "Rules",
|
|
href: "/dashboard/rules",
|
|
icon: <SlidersHorizontal />,
|
|
children: getCategorySidebarChildren("rules"),
|
|
},
|
|
|
|
{
|
|
label: "Staff",
|
|
href: "/user-management",
|
|
icon: <Users />,
|
|
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). */
|
|
const ET_CLEARANCE_HREF = "/dashboard/contracts/clearance";
|
|
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).
|
|
const GL_WORKFLOW_PATH_PATTERNS: RegExp[] = [
|
|
/^\/dashboard\/contracts\/[^/]+\/create-booking(\/|$)/,
|
|
/^\/dashboard\/bookings\/[^/]+\/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).
|
|
*/
|
|
const filterSidebarByPermission = (
|
|
sections: SidebarSection[],
|
|
user: ReturnType<typeof useAuth>["user"],
|
|
): 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);
|
|
};
|
|
|
|
const APP_TITLE = "EDR Freight Backoffice";
|
|
|
|
/** Flatten sidebar sections (incl. nested children) into {href, label} pairs. */
|
|
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. */
|
|
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;
|
|
};
|
|
|
|
const DashboardShell = () => {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const { user, logout } = useAuth();
|
|
|
|
const demoItems: SidebarItem[] = [];
|
|
|
|
const sidebarSections = filterSidebarByPermission(
|
|
buildSidebarSections(demoItems),
|
|
user,
|
|
);
|
|
const displayName = user?.name?.en || user?.username || user?.email || "User";
|
|
|
|
// GL positions are locked to their single clearance page: if they navigate
|
|
// (or deep-link) anywhere else, send them back to their clearance hub.
|
|
// Super admin is exempt. Allow the clearance path + its detail sub-routes.
|
|
const superAdmin = isSuperAdmin(user);
|
|
const glClearanceHome = !superAdmin
|
|
? isEthiopianGl(user)
|
|
? ET_CLEARANCE_HREF
|
|
: isDjiboutiGl(user)
|
|
? DJ_CLEARANCE_HREF
|
|
: null
|
|
: null;
|
|
|
|
useEffect(() => {
|
|
const activeLabel = findActiveSidebarLabel(
|
|
location.pathname,
|
|
sidebarSections,
|
|
);
|
|
document.title = activeLabel ? `${activeLabel} | ${APP_TITLE}` : APP_TITLE;
|
|
}, [location.pathname, sidebarSections]);
|
|
|
|
if (
|
|
glClearanceHome &&
|
|
!location.pathname.startsWith(glClearanceHome) &&
|
|
!GL_WORKFLOW_PATH_PATTERNS.some((re) => re.test(location.pathname))
|
|
) {
|
|
return <Navigate to={glClearanceHome} replace />;
|
|
}
|
|
|
|
return (
|
|
<FreightDashboardLayout
|
|
sidebarSections={sidebarSections}
|
|
// GL Ethiopia / GL Djibouti are locked to a single clearance page — no
|
|
// sidebar (or mobile burger) at all; the page renders full width.
|
|
hideSidebar={Boolean(glClearanceHome)}
|
|
activeHref={location.pathname}
|
|
onNavigate={navigate}
|
|
enableThemeToggle
|
|
userName={displayName}
|
|
userEmail={user?.email}
|
|
onLogout={logout}
|
|
>
|
|
<Outlet />
|
|
</FreightDashboardLayout>
|
|
);
|
|
};
|
|
|
|
const App = () => {
|
|
const { user, loading } = useAuth();
|
|
|
|
if (loading) {
|
|
return <LoadingScreen />;
|
|
}
|
|
|
|
if (!user) {
|
|
return (
|
|
<Routes>
|
|
<Route path="/auth" element={<LoginPage />} />
|
|
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
|
|
{/* <Route path="/um/*" element={<UserManagementHostPage />} /> */}
|
|
<Route path="um/set-password" element={<SetPassword />} />
|
|
<Route path="/fayda/callback" element={<FaydaCallbackPage />} />
|
|
<Route path="*" element={<Navigate to="/auth" replace />} />
|
|
</Routes>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Routes>
|
|
{UserManagementRoutes()}
|
|
{/* <Route path="/um/*" element={<UserManagementHostPage />} /> */}
|
|
<Route path="/health" element={<HealthCheck />} />
|
|
<Route path="/callback" element={<FaydaCallbackPage />} />
|
|
<Route path="/" element={<Navigate to="/dashboard/overview" replace />} />
|
|
<Route
|
|
path="/dashboard"
|
|
element={<Navigate to="/dashboard/overview" replace />}
|
|
/>
|
|
<Route path="/dashboard" element={<DashboardShell />}>
|
|
<Route path="overview" element={<OverviewPage />} />
|
|
{/* Dev/testing page for the mock AI booking assistant. */}
|
|
<Route
|
|
path="ai-booking-mock-test"
|
|
element={<AiBookingMockTestPage />}
|
|
/>
|
|
<Route path="profile" element={<MyProfilePage />} />
|
|
|
|
<Route path="booking-requests" element={<BookingRequestsPage />} />
|
|
<Route
|
|
path="payments"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.bookings.view}>
|
|
<PaymentsPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route path="support" element={<SupportInboxPage />} />
|
|
<Route
|
|
path="customers"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.customers.view}>
|
|
<CustomersPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="customers/:id"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.customers.view}>
|
|
<CustomerDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="invoices"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.bookings.view}>
|
|
<InvoicesPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="invoices/:id"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.bookings.view}>
|
|
<InvoiceDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route path="booking-requests/new" element={<NewBookingPage />} />
|
|
<Route
|
|
path="booking-requests/:id"
|
|
element={<BookingRequestDetailPage />}
|
|
/>
|
|
<Route
|
|
path="booking-requests/:id/contract"
|
|
element={<BookingContractPage />}
|
|
/>
|
|
{/* Legacy booking-based clearance URLs → the contract clearance hub. */}
|
|
<Route
|
|
path="clearance"
|
|
element={<Navigate to="/dashboard/contracts/clearance" replace />}
|
|
/>
|
|
<Route
|
|
path="clearance/:id"
|
|
element={
|
|
<RequirePermission
|
|
permission={FREIGHT_PERMS.bookings.clearanceView}
|
|
>
|
|
<DocumentClearanceDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
{/* Contracts (Path A/B) */}
|
|
<Route
|
|
path="contract-requests"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.contracts.view}>
|
|
<ContractRequestsPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="contract-requests/:id"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.contracts.view}>
|
|
<ContractRequestDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="contract-requests/:id/view"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.contracts.view}>
|
|
<ContractViewPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="bookings/:bookingId/clearance"
|
|
element={
|
|
<RequirePermission
|
|
permission={FREIGHT_PERMS.bookings.clearanceView}
|
|
>
|
|
<DocumentClearanceDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="shipment-requests"
|
|
element={
|
|
<RequirePermission
|
|
permission={FREIGHT_PERMS.contracts.createBooking}
|
|
>
|
|
<ShipmentRequestsPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="shipment-requests/:id"
|
|
element={
|
|
<RequirePermission
|
|
permission={FREIGHT_PERMS.contracts.createBooking}
|
|
>
|
|
<ShipmentRequestDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
{/* Operations hub: clearance documents for non-customs contracts —
|
|
Contracts tab (contract-level) + General tab (per-booking). */}
|
|
<Route
|
|
path="contracts/clearance-documents"
|
|
element={
|
|
<RequirePermission
|
|
permission={FREIGHT_PERMS.contracts.opsClearanceReview}
|
|
>
|
|
<ClearanceDocumentsPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="contracts/clearance-documents/:id"
|
|
element={
|
|
<RequirePermission
|
|
permission={FREIGHT_PERMS.contracts.opsClearanceReview}
|
|
>
|
|
<ContractClearanceDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
{/* GL (Path B) contract clearance review hub */}
|
|
<Route
|
|
path="contracts/clearance"
|
|
element={
|
|
<RequirePermission
|
|
permission={[
|
|
FREIGHT_PERMS.contracts.clearanceReview,
|
|
FREIGHT_PERMS.contracts.clearanceEtActions,
|
|
]}
|
|
>
|
|
<ContractClearanceListPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="contracts/clearance/:id"
|
|
element={
|
|
<RequirePermission
|
|
permission={[
|
|
FREIGHT_PERMS.contracts.clearanceReview,
|
|
FREIGHT_PERMS.contracts.clearanceEtActions,
|
|
]}
|
|
>
|
|
<ContractClearanceDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="gl-ethiopia/clearance"
|
|
element={<LegacyGlEthiopiaClearanceRedirect />}
|
|
/>
|
|
<Route
|
|
path="gl-ethiopia/clearance/:id"
|
|
element={<LegacyGlEthiopiaClearanceRedirect />}
|
|
/>
|
|
<Route
|
|
path="gl-djibouti/clearance"
|
|
element={
|
|
<RequirePermission
|
|
permission={FREIGHT_PERMS.contracts.clearanceDjActions}
|
|
>
|
|
<GlDjiboutiClearanceListPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="gl-djibouti/clearance/:id"
|
|
element={
|
|
<RequirePermission
|
|
permission={FREIGHT_PERMS.contracts.clearanceDjActions}
|
|
>
|
|
<GlClearanceDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
{/* Path A — Operations reviews per-booking self-clearance documents
|
|
(GENERAL contracts without customs). */}
|
|
<Route
|
|
path="contracts/ops-clearance"
|
|
element={
|
|
<RequirePermission
|
|
permission={FREIGHT_PERMS.contracts.opsClearanceReview}
|
|
>
|
|
<DocumentClearanceListPage opsMode />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="contracts/:id/create-booking"
|
|
element={
|
|
<RequirePermission
|
|
permission={FREIGHT_PERMS.contracts.createBooking}
|
|
>
|
|
<GlCreateBookingForm />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
{/* Completion of an initiated (bare) instance after per-booking
|
|
clearance — same form, submits to the complete endpoint. */}
|
|
<Route
|
|
path="contracts/:id/bookings/:bookingId/complete"
|
|
element={
|
|
<RequirePermission
|
|
permission={FREIGHT_PERMS.contracts.createBooking}
|
|
>
|
|
<GlCreateBookingForm />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="bookings/:id/milestones"
|
|
element={<BookingMilestonesRedirect />}
|
|
/>
|
|
<Route path="warehouses" element={<WarehouseListPage />} />
|
|
<Route path="warehouses/:id" element={<WarehouseDetailPage />} />
|
|
<Route
|
|
path="warehouse-inventory"
|
|
element={<WarehouseInventoryPage />}
|
|
/>
|
|
<Route path="import-warehouse" element={<ImportWarehouseFlowPage />} />
|
|
<Route path="export-warehouse" element={<ExportWarehouseFlowPage />} />
|
|
<Route path="arrival-queue" element={<ArrivalQueuePage />} />
|
|
<Route path="loading-queue" element={<LoadingQueuePage />} />
|
|
<Route path="intercity" element={<IntercityPage />} />
|
|
<Route path="trucks-on-site" element={<TrucksOnSitePage />} />
|
|
<Route path="loaded-inventory" element={<LoadedInventoryPage />} />
|
|
<Route path="dispatch-queue" element={<DispatchQueuePage />} />
|
|
<Route
|
|
path="export-djibouti-unloading"
|
|
element={<ExportDjiboutiUnloadingQueuePage />}
|
|
/>
|
|
<Route
|
|
path="interchange-documents"
|
|
element={<InterchangeDocumentsPage />}
|
|
/>
|
|
<Route path="inventory-inquiry" element={<InventoryInquiryPage />} />
|
|
<Route path="warehouse-rules" element={<WarehouseRulesPage />} />
|
|
<Route
|
|
path="warehouse-fee-invoices"
|
|
element={<WarehouseInvoicesPage />}
|
|
/>
|
|
<Route
|
|
path="warehouse-dashboard"
|
|
element={<WarehouseDashboardPage />}
|
|
/>
|
|
|
|
<Route
|
|
path="operations/train-scheduling"
|
|
element={
|
|
<Navigate to="/dashboard/operations/train-scheduling-v2" replace />
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/batch-board"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
|
<BatchBoardPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/first-mile"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.firstMile.view}>
|
|
<FirstMilePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/last-mile"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.lastMile.view}>
|
|
<LastMilePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/batch-board/:scheduleId"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
|
<BatchScheduleDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/train-scheduling-v2"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
|
<TrainScheduleV2ListPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/train-scheduling-v2/:scheduleId"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
|
<TrainScheduleV2DetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/train-scheduling-v2/:scheduleId/track"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
|
<TrainScheduleTrackPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="routes"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.routes.view, FREIGHT_PERMS.fleet.view]}>
|
|
<RoutesPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="locomotives"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.locomotives.view, FREIGHT_PERMS.fleet.view]}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="trains"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="trains/:id"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
|
|
<TrainDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="train-builder"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
|
|
<TrainBuilderListPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="train-builder/:id"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
|
|
<TrainBuilderDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="wagons"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.wagons.view, FREIGHT_PERMS.fleet.view]}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="containers"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.containers.view, FREIGHT_PERMS.fleet.view]}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="cargoes"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.cargoes.view, FREIGHT_PERMS.fleet.view]}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="vehicles"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.vehicles.view}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="vehicles/:id"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.vehicles.view}>
|
|
<VehicleDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="drivers"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.drivers.view}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="drivers/:id"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.drivers.view}>
|
|
<DriverDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/train-scheduling"
|
|
element={
|
|
<Navigate to="/dashboard/operations/train-scheduling-v2" replace />
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/batch-board"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
|
<BatchBoardPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/batch-board/:scheduleId"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
|
<BatchScheduleDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/train-scheduling-v2"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
|
<TrainScheduleV2ListPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/train-scheduling-v2/:scheduleId"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
|
<TrainScheduleV2DetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="operations/train-scheduling-v2/:scheduleId/track"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
|
<TrainScheduleTrackPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="routes"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.routes.view, FREIGHT_PERMS.fleet.view]}>
|
|
<RoutesPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="fuel-purchases"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.fuel.view}>
|
|
<FuelPurchasePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="fuel-stats"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.fuel.view}>
|
|
<FuelStatsPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="maintenance"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.maintenance.view}>
|
|
<MaintenancePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="financial-reports"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.fleetReports.view}>
|
|
<FinancialReportsPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="fleet-dashboard"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.fleetDashboard.view}>
|
|
<FleetDashboard />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="tracking"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.tracking.view}>
|
|
<TrackingPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="compliance"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
|
<CompliancePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="incidents"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
|
<IncidentsPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="work-orders"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.maintenance.view}>
|
|
<WorkOrdersPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="procurement"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
|
|
<ProcurementPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="locomotives"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.locomotives.view, FREIGHT_PERMS.fleet.view]}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="trains"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="trains/:id"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
|
|
<TrainDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="train-builder"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
|
|
<TrainBuilderListPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="train-builder/:id"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
|
|
<TrainBuilderDetailPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="wagons"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.wagons.view, FREIGHT_PERMS.fleet.view]}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="containers"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.containers.view, FREIGHT_PERMS.fleet.view]}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="cargoes"
|
|
element={
|
|
<RequirePermission permission={[FREIGHT_PERMS.cargoes.view, FREIGHT_PERMS.fleet.view]}>
|
|
<FleetResourcePage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
{/* Legacy embedded user management routes */}
|
|
{/* <Route path="user-management" element={<UserManagementPage />} />
|
|
<Route path="user-management/users" element={<UsersPage />} />
|
|
<Route
|
|
path="user-management/position-types"
|
|
element={<PositionTypesPage />}
|
|
/> */}
|
|
{/* <Route path="user-management/employees" element={<EmployeesPage />} /> */}
|
|
{/* <Route
|
|
path="user-management/permissions"
|
|
element={<PermissionsPage />}
|
|
/>
|
|
<Route path="user-management/roles" element={<RolesPage />} /> */}
|
|
|
|
<Route
|
|
path="file-settings"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.admin}>
|
|
<FileUploadSettingsPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="dropdown-settings"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.admin}>
|
|
<DropdownSettingsPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="contract-templates"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.admin}>
|
|
<ContractTemplatesPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
<Route
|
|
path="contract-templates/:code"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.admin}>
|
|
<ContractTemplateEditorPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
|
|
<Route
|
|
path="configuration"
|
|
element={
|
|
<Navigate to="/dashboard/configuration/cargo-types" replace />
|
|
}
|
|
/>
|
|
<Route
|
|
path="configuration/train-scheduling-rules"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
|
|
<TrainSchedulingGlobalRulesPage />
|
|
</RequirePermission>
|
|
}
|
|
/>
|
|
{/* <Route
|
|
path="configuration/contract-validity-periods"
|
|
element={
|
|
<RequirePermission permission={FREIGHT_PERMS.admin}>
|
|
<ContractValidityPeriodsPage />
|
|
</RequirePermission>
|
|
}
|
|
/> */}
|
|
<Route path="configuration/cargo-types" element={<CargoTypesPage />} />
|
|
<Route
|
|
path="configuration/cargo-types/:id"
|
|
element={<CargoTypesPage />}
|
|
/>
|
|
<Route
|
|
path="configuration/:resource"
|
|
element={<RuleEngineResourcePage />}
|
|
/>
|
|
|
|
<Route
|
|
path="rules"
|
|
element={<Navigate to="/dashboard/rules/priority-configs" replace />}
|
|
/>
|
|
<Route path="rules/:resource" element={<RuleEngineResourcePage />} />
|
|
|
|
<Route
|
|
path="rule-engine"
|
|
element={
|
|
<Navigate to="/dashboard/configuration/cargo-types" replace />
|
|
}
|
|
/>
|
|
<Route
|
|
path="rule-engine/:resource"
|
|
element={<RuleEngineLegacyRedirect />}
|
|
/>
|
|
<Route path="org-structure" element={<Navigate to="/um" replace />} />
|
|
<Route path="org-structure/*" element={<Navigate to="/um" replace />} />
|
|
</Route>
|
|
|
|
<Route path="*" element={<Navigate to="/dashboard/overview" replace />} />
|
|
</Routes>
|
|
);
|
|
};
|
|
|
|
/** Redirect removed milestones page to document clearance. */
|
|
function BookingMilestonesRedirect() {
|
|
const { id } = useParams();
|
|
return <Navigate to={`/dashboard/bookings/${id}/clearance`} replace />;
|
|
}
|
|
|
|
/** Redirect legacy GL Ethiopia clearance URLs to the unified document clearance hub. */
|
|
function LegacyGlEthiopiaClearanceRedirect() {
|
|
const { id } = useParams();
|
|
if (id) {
|
|
return <Navigate to={`/dashboard/contracts/clearance/${id}`} replace />;
|
|
}
|
|
return <Navigate to="/dashboard/contracts/clearance" replace />;
|
|
}
|
|
|
|
export default App;
|