From 7af2ecc76eb37f466f193e3a6f8a4e6319df4e3d Mon Sep 17 00:00:00 2001 From: Marshal Date: Sun, 28 Jun 2026 19:08:30 +0000 Subject: [PATCH] add clearance history and operations history endpoints, update UI components for history view --- .../contracts/contract-clearance.service.ts | 24 ++++ .../modules/contracts/contracts.controller.ts | 14 +++ .../ContractClearanceReviewSection.tsx | 4 +- .../backoffice/src/constants/QUERY_KEYS.ts | 2 + .../backoffice/src/constants/URLS.ts | 2 + .../src/hooks/contracts/useContracts.ts | 16 +++ .../contracts/ContractClearanceListPage.tsx | 118 ++++++++++++------ .../src/services/contracts.service.ts | 12 ++ 8 files changed, 154 insertions(+), 38 deletions(-) diff --git a/apps/edr-freight-api/src/modules/contracts/contract-clearance.service.ts b/apps/edr-freight-api/src/modules/contracts/contract-clearance.service.ts index b22b0e217..964e9f817 100644 --- a/apps/edr-freight-api/src/modules/contracts/contract-clearance.service.ts +++ b/apps/edr-freight-api/src/modules/contracts/contract-clearance.service.ts @@ -506,4 +506,28 @@ export class ContractClearanceService { sortOrder: filter.sortOrder, }); } + + /** GL ET history: contracts that completed Path B clearance. */ + async history(filter: FilterContractDto): Promise { + return this.contractsRepository.findAllPaginated({ + page: filter.page ?? 1, + pageSize: filter.pageSize ?? 50, + statuses: ['CLEARANCE_READY_FOR_BOOKING', 'ACTIVE', 'CLOSED', 'CANCELLED'], + customsClearingEnabled: true, + sortBy: filter.sortBy ?? 'createdAt', + sortOrder: filter.sortOrder ?? 'DESC', + }); + } + + /** Operations history: contracts that completed Path A self-clearance review. */ + async opsHistory(filter: FilterContractDto): Promise { + return this.contractsRepository.findAllPaginated({ + page: filter.page ?? 1, + pageSize: filter.pageSize ?? 50, + statuses: ['CLEARANCE_READY_FOR_BOOKING', 'ACTIVE', 'CLOSED', 'CANCELLED'], + customsClearingEnabled: false, + sortBy: filter.sortBy ?? 'createdAt', + sortOrder: filter.sortOrder ?? 'DESC', + }); + } } diff --git a/apps/edr-freight-api/src/modules/contracts/contracts.controller.ts b/apps/edr-freight-api/src/modules/contracts/contracts.controller.ts index cf38f0fd9..7ec2611fa 100644 --- a/apps/edr-freight-api/src/modules/contracts/contracts.controller.ts +++ b/apps/edr-freight-api/src/modules/contracts/contracts.controller.ts @@ -459,6 +459,20 @@ export class ContractsController { return this.clearanceService.opsFinalize(id); } + @Get('clearance/history') + @BookingStaff(FREIGHT_PERMS.contracts.finalizeClearance) + @ApiOperation({ summary: 'GL ET clearance history: contracts that completed Path B clearance' }) + clearanceHistory(@Query() filter: FilterContractDto) { + return this.clearanceService.history(filter); + } + + @Get('clearance/ops-history') + @BookingStaff(FREIGHT_PERMS.contracts.opsClearanceReview) + @ApiOperation({ summary: 'Operations clearance history: contracts that completed Path A self-clearance' }) + opsClearanceHistory(@Query() filter: FilterContractDto) { + return this.clearanceService.opsHistory(filter); + } + // ── Booking under contract (Path A customer / Path B GL ET) ──────────────── @Post(':id/bookings') 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 6bc7fc28d..fa85cf815 100644 --- a/apps/edr-freight-web/backoffice/src/components/contracts/ContractClearanceReviewSection.tsx +++ b/apps/edr-freight-web/backoffice/src/components/contracts/ContractClearanceReviewSection.tsx @@ -112,9 +112,7 @@ export function ContractClearanceReviewSection({ ); const glDocs = useMemo( () => - (clearance?.documents ?? []).filter( - (d) => d.uploadedBy === "gl_et" || d.uploadedBy === "gl_dj", - ), + (clearance?.documents ?? []).filter((d) => d.uploadedBy === "gl"), [clearance], ); diff --git a/apps/edr-freight-web/backoffice/src/constants/QUERY_KEYS.ts b/apps/edr-freight-web/backoffice/src/constants/QUERY_KEYS.ts index 90cb52e08..59b493bf3 100644 --- a/apps/edr-freight-web/backoffice/src/constants/QUERY_KEYS.ts +++ b/apps/edr-freight-web/backoffice/src/constants/QUERY_KEYS.ts @@ -56,6 +56,8 @@ export const QUERY_KEYS = { clearance: (id: string) => ["contracts", "clearance", id] as const, clearanceQueue: (region?: string) => ["contracts", "clearance-queue", region ?? "ET"] as const, + clearanceHistory: (region?: string) => + ["contracts", "clearance-history", region ?? "ET"] as const, milestones: (id: string) => ["contracts", "milestones", id] as const, capacity: (id: string) => ["contracts", "capacity", id] as const, bookingMilestones: (bookingId: string) => diff --git a/apps/edr-freight-web/backoffice/src/constants/URLS.ts b/apps/edr-freight-web/backoffice/src/constants/URLS.ts index 261d8db11..d3195db6c 100644 --- a/apps/edr-freight-web/backoffice/src/constants/URLS.ts +++ b/apps/edr-freight-web/backoffice/src/constants/URLS.ts @@ -150,6 +150,8 @@ export const URL_CONSTANTS = { `/contracts/${id}/clearance/ops-review`, OPS_CLEARANCE_FINALIZE: (id: string) => `/contracts/${id}/clearance/ops-finalize`, + CLEARANCE_HISTORY: "/contracts/clearance/history", + OPS_CLEARANCE_HISTORY: "/contracts/clearance/ops-history", BOOKINGS: (id: string) => `/contracts/${id}/bookings`, CAPACITY: (id: string) => `/contracts/${id}/capacity`, MILESTONES: (id: string) => `/contracts/${id}/milestones`, diff --git a/apps/edr-freight-web/backoffice/src/hooks/contracts/useContracts.ts b/apps/edr-freight-web/backoffice/src/hooks/contracts/useContracts.ts index 43e3509b3..337d9f2de 100644 --- a/apps/edr-freight-web/backoffice/src/hooks/contracts/useContracts.ts +++ b/apps/edr-freight-web/backoffice/src/hooks/contracts/useContracts.ts @@ -61,6 +61,22 @@ export function useOpsClearanceQueue(enabled = true) { }); } +export function useContractClearanceHistory(enabled = true) { + return useQuery({ + queryKey: QUERY_KEYS.CONTRACTS.clearanceHistory("GL"), + queryFn: () => contractsService.getClearanceHistory(), + enabled, + }); +} + +export function useOpsClearanceHistory(enabled = true) { + return useQuery({ + queryKey: QUERY_KEYS.CONTRACTS.clearanceHistory("OPS"), + queryFn: () => contractsService.getOpsClearanceHistory(), + enabled, + }); +} + export function useContractMilestones(id: string | undefined) { return useQuery({ queryKey: QUERY_KEYS.CONTRACTS.milestones(id ?? ""), 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 2b34b0c4e..570293775 100644 --- a/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceListPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/contracts/ContractClearanceListPage.tsx @@ -15,8 +15,10 @@ import { } from "@mantine/core"; import { ArrowRight, + CheckCircle, ChevronRight, FileText, + History, Inbox, LayoutGrid, RefreshCw, @@ -36,15 +38,20 @@ import { } from "@edr/ui-common"; import type { Freight } from "@edr/types"; +import { ContractStatusBadge } from "@/components/contracts/ContractStatusBadge"; import { PageContainer } from "@/components/page/PageContainer"; import { PageHeader } from "@/components/page/PageHeader"; import { KpiStrip } from "@/components/page/KpiStrip"; import { bookingTable } from "@/components/bookings/booking-ui.styles"; import { + useContractClearanceHistory, useContractClearanceQueue, + useOpsClearanceHistory, useOpsClearanceQueue, } from "@/hooks/contracts/useContracts"; +type PageTab = "queue" | "history"; + type ViewMode = "table" | "cards"; interface ClearanceRow { @@ -56,6 +63,7 @@ interface ClearanceRow { originLabel: string; destinationLabel: string; contractKind: string; + status: string; } function yardLabel( @@ -83,6 +91,7 @@ function toClearanceRow(contract: Freight.IContract): ClearanceRow { originLabel: yardLabel(first?.originYard), destinationLabel: yardLabel(last?.destinationYard), contractKind: contract.contractKind, + status: contract.status, }; } @@ -119,13 +128,19 @@ export default function ContractClearanceListPage({ const navigate = useNavigate(); const [query, setQuery] = useState(""); const [view, setView] = useState("table"); + const [pageTab, setPageTab] = useState("queue"); const { pagination, setPagination } = usePagination({ pageSize: 10 }); - const glQueue = useContractClearanceQueue(!opsMode); - const opsQueue = useOpsClearanceQueue(opsMode); + const isHistory = pageTab === "history"; + + const glQueue = useContractClearanceQueue(!opsMode && !isHistory); + const opsQueue = useOpsClearanceQueue(opsMode && !isHistory); + const glHistory = useContractClearanceHistory(!opsMode && isHistory); + const opsHistory = useOpsClearanceHistory(opsMode && isHistory); + const { data, isLoading, isError, isFetching, refetch } = opsMode - ? opsQueue - : glQueue; + ? (isHistory ? opsHistory : opsQueue) + : (isHistory ? glHistory : glQueue); const allRows = useMemo( () => (data?.items ?? []).map(toClearanceRow), @@ -229,11 +244,14 @@ export default function ContractClearanceListPage({ { id: "status", header: () => Status, - cell: () => ( - - Under review - - ), + cell: ({ row }) => + isHistory ? ( + + ) : ( + + Under review + + ), }, { id: "go", @@ -261,11 +279,11 @@ export default function ContractClearanceListPage({ meta={ } + leftSection={isHistory ? : } > - {counts.all} awaiting review + {isHistory ? `${counts.all} completed` : `${counts.all} awaiting review`} } action={ @@ -282,28 +300,53 @@ export default function ContractClearanceListPage({ } /> + + { + setPageTab(v as PageTab); + setPagination({ pageIndex: 0, pageSize: pagination.pageSize }); + }} + data={[ + { + value: "queue", + label: ( + + + Queue + + ), + }, + { + value: "history", + label: ( + + + History + + ), + }, + ]} + /> + + @@ -401,6 +444,7 @@ export default function ContractClearanceListPage({ rows={pagedRows} loading={isLoading} onOpen={openDetail} + isHistory={isHistory} /> )} @@ -414,10 +458,12 @@ function ClearanceCardGrid({ rows, loading, onOpen, + isHistory = false, }: { rows: ClearanceRow[]; loading: boolean; onOpen: (id: string) => void; + isHistory?: boolean; }) { if (loading) { return ( @@ -449,7 +495,7 @@ function ClearanceCardGrid({ }} > {rows.map((r) => ( - onOpen(r.id)} /> + onOpen(r.id)} isHistory={isHistory} /> ))} ); @@ -458,9 +504,11 @@ function ClearanceCardGrid({ function ClearanceCard({ row, onOpen, + isHistory = false, }: { row: ClearanceRow; onOpen: () => void; + isHistory?: boolean; }) { return ( - - Under review + + {isHistory ? "Cleared" : "Under review"} diff --git a/apps/edr-freight-web/backoffice/src/services/contracts.service.ts b/apps/edr-freight-web/backoffice/src/services/contracts.service.ts index 3c814ce7f..8a3085098 100644 --- a/apps/edr-freight-web/backoffice/src/services/contracts.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/contracts.service.ts @@ -212,6 +212,18 @@ export const contractsService = { }; }, + getClearanceHistory: async (): Promise => { + const response = await client.get(C.CLEARANCE_HISTORY); + const data = unwrap(response.data); + return { items: (data.items ?? []) as Freight.IContract[], total: data.total ?? 0 }; + }, + + getOpsClearanceHistory: async (): Promise => { + const response = await client.get(C.OPS_CLEARANCE_HISTORY); + const data = unwrap(response.data); + return { items: (data.items ?? []) as Freight.IContract[], total: data.total ?? 0 }; + }, + opsReviewClearanceDocument: ( id: string, payload: { fileKey: string; status: "APPROVED" | "QUERIED"; note?: string },