Merge branch 'dev'

This commit is contained in:
Marshal
2026-08-13 14:14:31 +00:00
219 changed files with 13585 additions and 5559 deletions

View File

@@ -1,4 +1,5 @@
import { useEffect } from "react";
import { useEffect, useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import {
Navigate,
Outlet,
@@ -10,6 +11,7 @@ import {
} from "react-router-dom";
import { FreightDashboardLayout, type SidebarItem } from "@/components/layout";
import { api } from "@/services/api";
import { useAuth } from "./auth/useAuth";
import LoadingScreen from "./components/LoadingScreen";
import LoginPage from "./pages/auth/LoginPage";
@@ -36,15 +38,13 @@ import CustomerDetailPage from "./pages/customers/CustomerDetailPage";
import CustomersPage from "./pages/customers/CustomersPage";
import ShippingLineCompaniesPage from "./pages/shipping-lines/ShippingLineCompaniesPage";
import InvoiceDetailPage from "./pages/invoices/InvoiceDetailPage";
import InvoicesPage from "./pages/invoices/InvoicesPage";
import UsdPaymentsPage from "./pages/invoices/UsdPaymentsPage";
import FinanceHubPage from "./pages/invoices/FinanceHubPage";
import MyProfilePage from "./pages/dashboard/MyProfilePage";
import OverviewPage from "./pages/dashboard/OverviewPage";
import ReportsHubPage from "./pages/reports/ReportsHubPage";
import ReportsIndexRedirect from "./pages/reports/ReportsIndexRedirect";
import ReportPage from "./pages/reports/ReportPage";
import AuditLogsPage from "./pages/AuditLogsPage";
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 } from "./lib/permissions";
@@ -53,6 +53,7 @@ import NoAccessPage from "./pages/NoAccessPage";
import FileUploadSettingsPage from "./pages/documents/FileUploadSettingsPage";
import DropdownSettingsPage from "./pages/dropdown_settings/DropdownSettingsPage";
import CompanyStampSettingsPage from "./pages/settings/CompanyStampSettingsPage";
import LogoSettingsPage from "./pages/settings/LogoSettingsPage";
import ContractTemplatesPage from "./pages/contract_templates/ContractTemplatesPage";
import PortalContentPage from "./pages/portal_content/PortalContentPage";
import ContractTemplateEditorPage from "./pages/contract_templates/ContractTemplateEditorPage";
@@ -82,7 +83,6 @@ import TrainScheduleTrackPage from "./pages/trainScheduling/TrainScheduleTrackPa
import TrainSchedulingGlobalRulesPage from "./pages/trainScheduling/TrainSchedulingGlobalRulesPage";
import TradeAccessPage from "./pages/configuration/TradeAccessPage";
import ExchangeRateSettingsCard from "./pages/settings/ExchangeRateSettingsCard";
import ContractValidityPeriodsPage from "./pages/configuration/ContractValidityPeriodsPage";
import FirstMilePage from "./pages/operations/FirstMilePage";
import LastMilePage from "./pages/operations/LastMilePage";
import TrainDetailPage from "./pages/trains/TrainDetailPage";
@@ -140,8 +140,18 @@ const DashboardShell = () => {
const demoItems: SidebarItem[] = [];
const { data: reportCatalog } = useQuery(api.reports.catalog.queryOptions());
const reportItems: SidebarItem[] = useMemo(
() =>
(reportCatalog ?? []).map((report) => ({
label: report.title,
href: `/dashboard/reports/${report.key}`,
})),
[reportCatalog],
);
const sidebarSections = filterSidebarByPermission(
buildSidebarSections(demoItems),
buildSidebarSections(demoItems, reportItems),
user,
);
const displayName = user?.name?.en || user?.username || user?.email || "User";
@@ -200,12 +210,43 @@ const App = () => {
{/* Landing is per-user: /dashboard/overview is gated on overview:view, so
a fixed target strands anyone without that key on a blank page. */}
<Route path="/" element={<Navigate to={landingPath} replace />} />
<Route path="/dashboard" element={<Navigate to={landingPath} replace />} />
<Route
path="/dashboard"
element={<Navigate to={landingPath} replace />}
/>
<Route path="/dashboard" element={<DashboardShell />}>
<Route path="overview" element={<RequirePermission permission={FREIGHT_PERMS.overview.view}><OverviewPage /></RequirePermission>} />
<Route path="reports" element={<RequirePermission permission={FREIGHT_PERMS.reports.view}><ReportsHubPage /></RequirePermission>} />
<Route path="reports/:reportKey" element={<RequirePermission permission={FREIGHT_PERMS.reports.view}><ReportPage /></RequirePermission>} />
<Route path="audit-logs" element={<RequirePermission permission={FREIGHT_PERMS.auditLog.view}><AuditLogsPage /></RequirePermission>} />
<Route
path="overview"
element={
<RequirePermission permission={FREIGHT_PERMS.overview.view}>
<OverviewPage />
</RequirePermission>
}
/>
<Route
path="reports"
element={
<RequirePermission permission={FREIGHT_PERMS.reports.view}>
<ReportsIndexRedirect />
</RequirePermission>
}
/>
<Route
path="reports/:reportKey"
element={
<RequirePermission permission={FREIGHT_PERMS.reports.view}>
<ReportPage />
</RequirePermission>
}
/>
<Route
path="audit-logs"
element={
<RequirePermission permission={FREIGHT_PERMS.auditLog.view}>
<AuditLogsPage />
</RequirePermission>
}
/>
{/* Dev/testing page for the mock AI booking assistant. */}
<Route
path="ai-booking-mock-test"
@@ -217,16 +258,28 @@ const App = () => {
/>
<Route path="profile" element={<MyProfilePage />} />
<Route path="booking-requests" element={<RequirePermission permission={FREIGHT_PERMS.bookings.view}><BookingRequestsPage /></RequirePermission>} />
<Route
path="payments"
path="booking-requests"
element={
<RequirePermission permission={FREIGHT_PERMS.payments.view}>
<PaymentsPage />
<RequirePermission permission={FREIGHT_PERMS.bookings.view}>
<BookingRequestsPage />
</RequirePermission>
}
/>
{/* Payments used to be its own page; it's now the "payments" tab on
the merged Invoices hub. Old bookmarks/links still land there. */}
<Route
path="payments"
element={<Navigate to="/dashboard/invoices?tab=payments" replace />}
/>
<Route
path="support"
element={
<RequirePermission permission={FREIGHT_PERMS.support.agentView}>
<SupportInboxPage />
</RequirePermission>
}
/>
<Route path="support" element={<RequirePermission permission={FREIGHT_PERMS.support.agentView}><SupportInboxPage /></RequirePermission>} />
<Route
path="customers"
element={
@@ -243,6 +296,11 @@ const App = () => {
</RequirePermission>
}
/>
{/* Merged Invoices / Payments / USD Payments hub — tabs switch via
?tab=invoices|payments|usd-payments (default invoices). Access is
OR'd across both keys so a user with just one still gets in; each
tab hides itself if the user lacks the permission it used to be
routed on. */}
<Route
path="shipping-lines"
element={
@@ -254,18 +312,19 @@ const App = () => {
<Route
path="invoices"
element={
<RequirePermission permission={FREIGHT_PERMS.invoices.view}>
<InvoicesPage />
<RequirePermission
permission={[
FREIGHT_PERMS.invoices.view,
FREIGHT_PERMS.payments.view,
]}
>
<FinanceHubPage />
</RequirePermission>
}
/>
<Route
path="usd-payments"
element={
<RequirePermission permission={FREIGHT_PERMS.invoices.view}>
<UsdPaymentsPage />
</RequirePermission>
}
element={<Navigate to="/dashboard/invoices?tab=usd-payments" replace />}
/>
<Route
path="invoices/:id"
@@ -275,7 +334,14 @@ const App = () => {
</RequirePermission>
}
/>
<Route path="booking-requests/new" element={<RequirePermission permission={FREIGHT_PERMS.bookings.view}><NewBookingPage /></RequirePermission>} />
<Route
path="booking-requests/new"
element={
<RequirePermission permission={FREIGHT_PERMS.bookings.view}>
<NewBookingPage />
</RequirePermission>
}
/>
<Route
path="wagon-cancellations"
element={
@@ -484,25 +550,195 @@ const App = () => {
path="bookings/:id/milestones"
element={<BookingMilestonesRedirect />}
/>
<Route path="warehouses" element={<RequirePermission permission={FREIGHT_PERMS.warehouses.view}><WarehouseListPage /></RequirePermission>} />
<Route path="warehouses/:id" element={<RequirePermission permission={FREIGHT_PERMS.warehouses.view}><WarehouseDetailPage /></RequirePermission>} />
<Route path="warehouse-inventory" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><WarehouseInventoryPage /></RequirePermission>} />
<Route path="import-warehouse" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><ImportWarehouseFlowPage /></RequirePermission>} />
<Route path="export-warehouse" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><ExportWarehouseFlowPage /></RequirePermission>} />
<Route path="arrival-queue" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><ArrivalQueuePage /></RequirePermission>} />
<Route path="loading-queue" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><LoadingQueuePage /></RequirePermission>} />
<Route path="intercity" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><IntercityPage /></RequirePermission>} />
<Route path="trucks-on-site" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><TrucksOnSitePage /></RequirePermission>} />
<Route path="import-trucks" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><ImportTrucksPage /></RequirePermission>} />
<Route path="container-returns" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><ContainerReturnsPage /></RequirePermission>} />
<Route path="loaded-inventory" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><LoadedInventoryPage /></RequirePermission>} />
<Route path="dispatch-queue" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><DispatchQueuePage /></RequirePermission>} />
<Route path="export-djibouti-unloading" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><ExportDjiboutiUnloadingQueuePage /></RequirePermission>} />
<Route path="interchange-documents" element={<RequirePermission permission={FREIGHT_PERMS.interchangeDocuments.view}><InterchangeDocumentsPage /></RequirePermission>} />
<Route path="inventory-inquiry" element={<RequirePermission permission={FREIGHT_PERMS.warehouseInventory.view}><InventoryInquiryPage /></RequirePermission>} />
<Route path="warehouse-rules" element={<RequirePermission permission={[FREIGHT_PERMS.warehouseAllocationRules.view, FREIGHT_PERMS.warehouseFeeRules.view]}><WarehouseRulesPage /></RequirePermission>} />
<Route path="warehouse-fee-invoices" element={<RequirePermission permission={FREIGHT_PERMS.warehouseFeeInvoices.view}><WarehouseInvoicesPage /></RequirePermission>} />
<Route path="warehouse-dashboard" element={<RequirePermission permission={FREIGHT_PERMS.warehouseDashboard.view}><WarehouseDashboardPage /></RequirePermission>} />
<Route
path="warehouses"
element={
<RequirePermission permission={FREIGHT_PERMS.warehouses.view}>
<WarehouseListPage />
</RequirePermission>
}
/>
<Route
path="warehouses/:id"
element={
<RequirePermission permission={FREIGHT_PERMS.warehouses.view}>
<WarehouseDetailPage />
</RequirePermission>
}
/>
<Route
path="warehouse-inventory"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<WarehouseInventoryPage />
</RequirePermission>
}
/>
<Route
path="import-warehouse"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<ImportWarehouseFlowPage />
</RequirePermission>
}
/>
<Route
path="export-warehouse"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<ExportWarehouseFlowPage />
</RequirePermission>
}
/>
<Route
path="arrival-queue"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<ArrivalQueuePage />
</RequirePermission>
}
/>
<Route
path="loading-queue"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<LoadingQueuePage />
</RequirePermission>
}
/>
<Route
path="intercity"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<IntercityPage />
</RequirePermission>
}
/>
<Route
path="trucks-on-site"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<TrucksOnSitePage />
</RequirePermission>
}
/>
<Route
path="import-trucks"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<ImportTrucksPage />
</RequirePermission>
}
/>
<Route
path="container-returns"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<ContainerReturnsPage />
</RequirePermission>
}
/>
<Route
path="loaded-inventory"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<LoadedInventoryPage />
</RequirePermission>
}
/>
<Route
path="dispatch-queue"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<DispatchQueuePage />
</RequirePermission>
}
/>
<Route
path="export-djibouti-unloading"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<ExportDjiboutiUnloadingQueuePage />
</RequirePermission>
}
/>
<Route
path="interchange-documents"
element={
<RequirePermission
permission={FREIGHT_PERMS.interchangeDocuments.view}
>
<InterchangeDocumentsPage />
</RequirePermission>
}
/>
<Route
path="inventory-inquiry"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseInventory.view}
>
<InventoryInquiryPage />
</RequirePermission>
}
/>
<Route
path="warehouse-rules"
element={
<RequirePermission
permission={[
FREIGHT_PERMS.warehouseAllocationRules.view,
FREIGHT_PERMS.warehouseFeeRules.view,
]}
>
<WarehouseRulesPage />
</RequirePermission>
}
/>
<Route
path="warehouse-fee-invoices"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseFeeInvoices.view}
>
<WarehouseInvoicesPage />
</RequirePermission>
}
/>
<Route
path="warehouse-dashboard"
element={
<RequirePermission
permission={FREIGHT_PERMS.warehouseDashboard.view}
>
<WarehouseDashboardPage />
</RequirePermission>
}
/>
<Route
path="operations/train-scheduling"
@@ -781,7 +1017,9 @@ const App = () => {
<Route
path="file-settings"
element={
<RequirePermission permission={FREIGHT_PERMS.settings.fileUpload.view}>
<RequirePermission
permission={FREIGHT_PERMS.settings.fileUpload.view}
>
<FileUploadSettingsPage />
</RequirePermission>
}
@@ -789,7 +1027,9 @@ const App = () => {
<Route
path="dropdown-settings"
element={
<RequirePermission permission={FREIGHT_PERMS.settings.dropdown.view}>
<RequirePermission
permission={FREIGHT_PERMS.settings.dropdown.view}
>
<DropdownSettingsPage />
</RequirePermission>
}
@@ -804,9 +1044,7 @@ const App = () => {
<Route
path="stamp-settings"
element={
<RequirePermission
permission={FREIGHT_PERMS.settings.stamp.view}
>
<RequirePermission permission={FREIGHT_PERMS.settings.stamp.view}>
<CompanyStampSettingsPage />
</RequirePermission>
}
@@ -816,6 +1054,15 @@ const App = () => {
path="invoice-stamp-settings"
element={<Navigate to="/dashboard/stamp-settings" replace />}
/>
{/* The ONE company logo, shown in the header of every generated document. */}
<Route
path="logo-settings"
element={
<RequirePermission permission={FREIGHT_PERMS.settings.logo.view}>
<LogoSettingsPage />
</RequirePermission>
}
/>
<Route
path="contract-templates"
element={
@@ -875,7 +1122,9 @@ const App = () => {
<Route
path="configuration/exchange-rate"
element={
<RequirePermission permission={FREIGHT_PERMS.settings.exchangeRate.view}>
<RequirePermission
permission={FREIGHT_PERMS.settings.exchangeRate.view}
>
<div className="p-4">
<ExchangeRateSettingsCard />
</div>
@@ -943,4 +1192,3 @@ function LegacyGlEthiopiaClearanceRedirect() {
}
export default App;