From f45d260c532f521d3bdfba1311064ccdac925701 Mon Sep 17 00:00:00 2001 From: Marshal Date: Sun, 28 Jun 2026 21:34:06 +0000 Subject: [PATCH] enhance contract clearance details with service type and customs information --- .../ContractClearanceReviewSection.tsx | 7 +-- .../contracts/ContractClearanceDetailPage.tsx | 34 ++++++++++---- .../contracts/ContractClearanceListPage.tsx | 45 +++++++++++++++++++ packages/types/src/freight/contracts.ts | 10 ++++- 4 files changed, 83 insertions(+), 13 deletions(-) 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 7f84450f3..c660bb928 100644 --- a/apps/edr-freight-web/backoffice/src/components/contracts/ContractClearanceReviewSection.tsx +++ b/apps/edr-freight-web/backoffice/src/components/contracts/ContractClearanceReviewSection.tsx @@ -110,11 +110,12 @@ export function ContractClearanceReviewSection({ (clearance?.documents ?? []).filter((d) => d.uploadedBy === "customer"), [clearance], ); + // GL output documents — anything not uploaded by the customer. The backend + // tags these `uploadedBy: 'gl'`; matching on "not customer" keeps it robust if + // that ever splits into gl_et / gl_dj. const glDocs = useMemo( () => - (clearance?.documents ?? []).filter( - (d) => d.uploadedBy === "gl_et" || d.uploadedBy === "gl_dj", - ), + (clearance?.documents ?? []).filter((d) => d.uploadedBy !== "customer"), [clearance], ); 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 1279177bf..19e2c8cb9 100644 --- a/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceDetailPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceDetailPage.tsx @@ -222,6 +222,11 @@ function ClearanceHero({ stats: { pct: number; approved: number; total: number }; }) { const direction = contract?.tradeDirection ?? "—"; + const serviceName = contract?.serviceType?.serviceName ?? null; + const customs = + contract?.serviceType?.includesCustoms ?? + contract?.customsClearingEnabled ?? + false; const routes = [...(contract?.routes ?? [])].sort( (a, b) => a.sortOrder - b.sortOrder, ); @@ -253,16 +258,27 @@ function ClearanceHero({ > {direction} - } - > - Customs - + {customs ? ( + } + > + Customs + + ) : ( + + No customs + + )} + {serviceName && ( + + {serviceName} + + )} {origin} diff --git a/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceListPage.tsx b/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceListPage.tsx index 5bd321583..304e6a69a 100644 --- a/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceListPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceListPage.tsx @@ -54,6 +54,8 @@ interface ClearanceRow { originLabel: string; destinationLabel: string; contractKind: string; + serviceTypeName: string; + customs: boolean; status: string; /** true once GL has finalized clearance — customer now books in the portal. */ ready: boolean; @@ -84,11 +86,32 @@ function toClearanceRow(contract: Freight.IContract): ClearanceRow { originLabel: yardLabel(first?.originYard), destinationLabel: yardLabel(last?.destinationYard), contractKind: contract.contractKind, + serviceTypeName: contract.serviceType?.serviceName ?? "—", + customs: + contract.serviceType?.includesCustoms ?? contract.customsClearingEnabled, status: contract.status, ready: contract.status === "CLEARANCE_READY_FOR_BOOKING", }; } +function CustomsBadge({ customs }: { customs: boolean }) { + return customs ? ( + } + > + Customs + + ) : ( + + No customs + + ); +} + function DirectionIcon({ direction }: { direction: string }) { const isImport = direction === "IMPORT"; const Icon = isImport ? Truck : ShipWheel; @@ -248,6 +271,21 @@ export default function ContractClearanceListPage() { ), }, + { + id: "service", + header: () => Service, + cell: ({ row }) => { + const r = row.original; + return ( + + + {r.serviceTypeName} + + + + ); + }, + }, { id: "status", header: () => Status, @@ -545,6 +583,13 @@ function ClearanceCard({ {row.contractKind === "GENERAL" ? "General" : "One-time"} + + + + {row.serviceTypeName} + + + ); } diff --git a/packages/types/src/freight/contracts.ts b/packages/types/src/freight/contracts.ts index 331da7990..39e032bfb 100644 --- a/packages/types/src/freight/contracts.ts +++ b/packages/types/src/freight/contracts.ts @@ -230,7 +230,7 @@ export interface ContractClearanceDocument { fileKey: string; label: string; required: boolean; - uploadedBy: "customer" | "gl_et" | "gl_dj"; + uploadedBy: "customer" | "gl" | "gl_et" | "gl_dj"; phase: ContractDocPhase; settingCode: string; file: { id: string; name: string; url: string } | null; @@ -406,6 +406,14 @@ export interface IContract extends BaseEntity { freightType: ContractFreightType; serviceTypeId: string; + /** Loaded service-type relation (queue + detail responses include it). */ + serviceType?: { + id: string; + code: string; + serviceName: string; + canBeBookedAlone: boolean; + includesCustoms: boolean; + } | null; paymentCurrency: string; customsClearingEnabled: boolean; customsClearingAgent?: string | null;