From 544cd4620c32168ec512da645d71bf2db4fe8b29 Mon Sep 17 00:00:00 2001 From: Marshal Date: Mon, 6 Jul 2026 07:52:20 +0000 Subject: [PATCH] enhance permission filtering for sidebar items and improve risk assignment logic in clearance components --- apps/edr-freight-web/backoffice/src/App.tsx | 13 ++++++----- .../contracts/PhasedClearanceActionPanel.tsx | 22 +++++++++++++++---- .../contracts/ContractClearanceDetailPage.tsx | 3 ++- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/App.tsx b/apps/edr-freight-web/backoffice/src/App.tsx index f7b26af00..263e19af0 100644 --- a/apps/edr-freight-web/backoffice/src/App.tsx +++ b/apps/edr-freight-web/backoffice/src/App.tsx @@ -474,7 +474,8 @@ const isClearanceItem = (item: SidebarItem): boolean => /** * Keep only items the user is permitted to see; drop now-empty sections. * - * Position-scoped visibility (super_admin bypasses all of this): + * 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 @@ -484,9 +485,11 @@ const filterSidebarByPermission = ( sections: SidebarSection[], user: ReturnType["user"], ): SidebarSection[] => { - const superAdmin = isSuperAdmin(user); - const etGl = !superAdmin && isEthiopianGl(user); - const djGl = !superAdmin && isDjiboutiGl(user); + // 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; @@ -497,8 +500,6 @@ const filterSidebarByPermission = ( }; const itemAllowed = (item: SidebarItem): boolean => { - if (superAdmin) return true; - // GL positions are locked to their single clearance page. if (etGl) return isEtClearanceItem(item); if (djGl) return isDjClearanceItem(item); diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/PhasedClearanceActionPanel.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/PhasedClearanceActionPanel.tsx index 6852590cd..75e901d10 100644 --- a/apps/edr-freight-web/backoffice/src/components/contracts/PhasedClearanceActionPanel.tsx +++ b/apps/edr-freight-web/backoffice/src/components/contracts/PhasedClearanceActionPanel.tsx @@ -122,7 +122,14 @@ function computeImportActiveStep( if (!clearance.gatepassGranted) return 8; if (!t1Uploaded && !clearance.t1?.closed) return 9; if (!clearance.t1?.closed) return 10; - if (!isBookingMilestoneDone(bookingMilestones, "RISK_ASSIGNED")) return 11; + // Risk is "assigned" when the booking milestone says so OR the clearance view + // already carries a riskLevel. The ET page derives its bookingMilestones from a + // separately-fetched booking id that can lag or mismatch the booking carrying + // the milestone — `clearance.riskLevel` is server truth and matches the badge. + const riskAssigned = + Boolean(clearance.riskLevel) || + isBookingMilestoneDone(bookingMilestones, "RISK_ASSIGNED"); + if (!riskAssigned) return 11; // Additional duty round is optional — resolved once skipped or paid. const secondDutyResolved = clearance.secondDuty?.skipped || @@ -229,6 +236,13 @@ export function PhasedClearanceActionPanel({ const actionBookingId = clearance.t1?.bookingId ?? clearance.linkedBookingId ?? bookingId ?? null; const t1Uploaded = t1FilesFromWorkflow(workflowFiles).length > 0; + // Risk is assigned when the clearance view carries a riskLevel (server truth, + // drives the badge) OR the fetched booking milestone confirms it. Kept in sync + // with computeImportActiveStep so the stepper never freezes on a page whose + // bookingMilestones lag/mismatch the booking that holds the milestone. + const riskAssigned = + Boolean(clearance.riskLevel) || + isBookingMilestoneDone(bookingMilestones, "RISK_ASSIGNED"); const activeStep = useMemo( () => isImport @@ -595,7 +609,7 @@ export function PhasedClearanceActionPanel({ label="Customs risk" description="GL Ethiopia assigns Green / Yellow / Red" icon={ - isBookingMilestoneDone(bookingMilestones, "RISK_ASSIGNED") ? ( + riskAssigned ? ( ) : ( @@ -606,7 +620,7 @@ export function PhasedClearanceActionPanel({ bookingId={actionBookingId} clearance={clearance} canAct={showEt && canEt} - done={isBookingMilestoneDone(bookingMilestones, "RISK_ASSIGNED")} + done={riskAssigned} onChanged={onChanged} /> @@ -620,7 +634,7 @@ export function PhasedClearanceActionPanel({ bookingId={actionBookingId} clearance={clearance} canAct={showEt && canEt} - riskAssigned={isBookingMilestoneDone(bookingMilestones, "RISK_ASSIGNED")} + riskAssigned={riskAssigned} onChanged={onChanged} onViewFile={onViewFile} onDownloadFile={onDownloadFile} diff --git a/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceDetailPage.tsx b/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceDetailPage.tsx index 66abbdd72..2c3e6fa9d 100644 --- a/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceDetailPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceDetailPage.tsx @@ -44,7 +44,7 @@ export default function ContractClearanceDetailPage() { const { id } = useParams<{ id: string }>(); const { view, viewer } = useFileViewer(); - const { data: contract } = useContractDetail(id); + const { data: contract, refetch: refetchContract } = useContractDetail(id); const { data: clearance, isLoading, @@ -250,6 +250,7 @@ export default function ContractClearanceDetailPage() { phasedCustoms={phasedCustoms} onChanged={() => { void refetch(); + void refetchContract(); void refetchBookingMilestones(); }} />