diff --git a/apps/edr-freight-web/backoffice/src/App.tsx b/apps/edr-freight-web/backoffice/src/App.tsx index bda88d912..4c5016e94 100644 --- a/apps/edr-freight-web/backoffice/src/App.tsx +++ b/apps/edr-freight-web/backoffice/src/App.tsx @@ -164,7 +164,7 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [ label: "Reports", href: "/dashboard/reports", icon: , - permission: FREIGHT_PERMS.bookings.view, + permission: FREIGHT_PERMS.reports.view, }, { label: "Customers", @@ -196,19 +196,19 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [ label: "Payments", href: "/dashboard/payments", icon: , - permission: FREIGHT_PERMS.bookings.view, + permission: FREIGHT_PERMS.payments.view, }, { label: "Invoices", href: "/dashboard/invoices", icon: , - permission: FREIGHT_PERMS.bookings.view, + permission: FREIGHT_PERMS.invoices.view, }, { label: "Support", href: "/dashboard/support", icon: , - permission: FREIGHT_PERMS.support.view, + permission: FREIGHT_PERMS.support.agentView, }, ...demoItems, ], @@ -844,26 +844,30 @@ const App = () => { element={} /> }> - } /> - } /> - } /> + } /> + } /> + } /> {/* Dev/testing page for the mock AI booking assistant. */} } + element={ + + + + } /> } /> - } /> + } /> + } /> - } /> + } /> { + } @@ -891,19 +895,27 @@ const App = () => { + } /> - } /> + } /> } + element={ + + + + } /> } + element={ + + + + } /> {/* Legacy booking-based clearance URLs → the contract clearance hub. */} { path="bookings/:id/milestones" element={} /> - } /> - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - } - /> - } - /> - } /> - } /> - } - /> - } - /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> { } /> - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> { } /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> {/* Legacy embedded user management routes */} {/* } /> diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/ClearanceOpsTabs.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/ClearanceOpsTabs.tsx index 610fb7e3d..a61646d6f 100644 --- a/apps/edr-freight-web/backoffice/src/components/contracts/ClearanceOpsTabs.tsx +++ b/apps/edr-freight-web/backoffice/src/components/contracts/ClearanceOpsTabs.tsx @@ -68,6 +68,8 @@ export function ClearanceOpsTabs({ Boolean(exchangeEntityId) && (hasPermission(user, FREIGHT_PERMS.contracts.clearanceEtActions) || hasPermission(user, FREIGHT_PERMS.contracts.clearanceDjActions)); + // Risk assignment + incident reporting hit bookings:operations endpoints. + const canOps = hasPermission(user, FREIGHT_PERMS.bookings.operations); const hasTabs = (showOpsTabs && hasOps) || showDocuments || showExchange; if (!hasTabs) { @@ -98,12 +100,12 @@ export function ClearanceOpsTabs({ Document exchange ) : null} - {showOpsTabs && riskMs ? ( + {showOpsTabs && canOps && riskMs ? ( }> Risk assignment ) : null} - {showOpsTabs && bookingId ? ( + {showOpsTabs && canOps && bookingId ? ( }> Incidents @@ -129,7 +131,7 @@ export function ClearanceOpsTabs({ ) : null} - {showOpsTabs && riskMs && bookingId ? ( + {showOpsTabs && canOps && riskMs && bookingId ? ( @@ -137,7 +139,7 @@ export function ClearanceOpsTabs({ ) : null} - {showOpsTabs && bookingId ? ( + {showOpsTabs && canOps && bookingId ? ( diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/ContractClearanceReviewSection.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/ContractClearanceReviewSection.tsx index c773933ea..5e2ca9e83 100644 --- a/apps/edr-freight-web/backoffice/src/components/contracts/ContractClearanceReviewSection.tsx +++ b/apps/edr-freight-web/backoffice/src/components/contracts/ContractClearanceReviewSection.tsx @@ -41,6 +41,8 @@ import { fetchViewableFile, } from "@/services/files.service"; import { useContractClearanceMutations } from "@/hooks/contracts/useContracts"; +import { useAuth } from "@/auth/useAuth"; +import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions"; import { useFileViewer } from "@/hooks/useFileViewer"; export interface ContractClearanceReviewSectionProps { @@ -123,6 +125,21 @@ export function ContractClearanceReviewSection({ } | null>(null); const { view, viewer } = useFileViewer(); + // Mirror the API guards: Path A (self-clearance) actions need + // ops_clearance_review; Path B (customs) review needs clearance_review or + // the ET phased key. Without the matching key every action would 403 — show + // the audit view instead of dead buttons. + const { user } = useAuth(); + const canReviewHere = selfClear + ? hasPermission(user, FREIGHT_PERMS.contracts.opsClearanceReview) + : hasPermission(user, FREIGHT_PERMS.contracts.clearanceReview) || + hasPermission(user, FREIGHT_PERMS.contracts.clearanceEtActions); + // Finalize has its own API key on the customs path (contracts:finalize_clearance). + const canFinalizeHere = selfClear + ? hasPermission(user, FREIGHT_PERMS.contracts.opsClearanceReview) + : hasPermission(user, FREIGHT_PERMS.contracts.finalizeClearance); + readOnly = readOnly || !canReviewHere; + const reviewerTeam = selfClear ? "Operations" : "Global Logistics"; const { data: clearance, isLoading } = useQuery({ @@ -484,7 +501,7 @@ export function ContractClearanceReviewSection({ color="edr-green" radius="md" leftSection={} - disabled={!clearance.allApproved} + disabled={!clearance.allApproved || !canFinalizeHere} loading={finalizeClearance.isPending} onClick={() => finalizeClearance.mutate(undefined, { diff --git a/apps/edr-freight-web/backoffice/src/components/trainScheduling/ScheduleBatchPanel.tsx b/apps/edr-freight-web/backoffice/src/components/trainScheduling/ScheduleBatchPanel.tsx index d44ef4540..9b410ce88 100644 --- a/apps/edr-freight-web/backoffice/src/components/trainScheduling/ScheduleBatchPanel.tsx +++ b/apps/edr-freight-web/backoffice/src/components/trainScheduling/ScheduleBatchPanel.tsx @@ -19,6 +19,8 @@ import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge"; import { api } from "@/services/api"; import { useToast } from "@/hooks/use-toast"; import type { TrainScheduleDetail } from "@/types/trainScheduling"; +import { useAuth } from "@/auth/useAuth"; +import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions"; interface ScheduleBatchPanelProps { schedule: TrainScheduleDetail; @@ -31,6 +33,8 @@ const windowColor: Record = { }; export function ScheduleBatchPanel({ schedule }: ScheduleBatchPanelProps) { + const { user } = useAuth(); + const canMarkPaid = hasPermission(user, FREIGHT_PERMS.trainScheduling.markPaid); const { toast } = useToast(); const actions = { runBatch: useMutation(api.trainScheduling.runBatch.mutationOptions()), @@ -179,7 +183,7 @@ export function ScheduleBatchPanel({ schedule }: ScheduleBatchPanelProps) { {!locked && ( - {b.status !== "PAID" && ( + {canMarkPaid && b.status !== "PAID" && ( + {mayRecordPayment && ( + + )} @@ -456,9 +463,11 @@ function InvoiceDetailModal({ id, onClose }: { id: string | null; onClose: () => onChange={(e) => setDriverPhone(e.currentTarget.value)} style={{ flex: 1 }} /> - + {mayRecordPayment && ( + + )} )} @@ -506,7 +515,7 @@ function InvoiceDetailModal({ id, onClose }: { id: string | null; onClose: () => Gate clearance & exit paper )} - {inv.status !== 'PAID' && inv.status !== 'CANCELLED' && ( + {inv.status !== 'PAID' && inv.status !== 'CANCELLED' && canCancelInvoice && (