import { directionLabel } from "@/lib/utils"; import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { AlertTriangle, ArrowLeft, ArrowRight, Box as BoxIcon, CalendarClock, ClipboardList, Download, FileSignature, FileText, Files, Flame, History, Info, LayoutGrid, Milestone, MoreHorizontal, Package, Receipt, RefreshCw, Route as RouteIcon, Snowflake, Wallet, } from "lucide-react"; import { ActionIcon, Alert, Badge, Box, Button, Center, Container, Grid, Group, Loader, Menu, Paper, SimpleGrid, Stack, Tabs, Text, } from "@mantine/core"; import toast from "react-hot-toast"; import { PageContainer, PageHeader, KpiStrip } from "@/components/page"; import type { KpiItem } from "@/components/page"; import { EntityLink } from "@/components/detail"; import { SectionCard } from "@/components/bookings/detail/SectionCard"; import { detailStyles } from "@/components/bookings/detail/booking-detail.styles"; import { TableCard } from "@/components/customers"; import { ContractCourtBadge, ContractStatusBadge, } from "@/components/contracts/ContractStatusBadge"; import { ContractWorkflowStepper } from "@/components/contracts/ContractWorkflowStepper"; import { ContractActionsToolbar } from "@/components/contracts/ContractActionsToolbar"; import { ContractApprovalStepsCard } from "@/components/contracts/ContractApprovalStepsCard"; import { HazardDeclarationPanel } from "@/components/contracts/HazardDeclarationPanel"; import { ClearanceWorkflowFilesPanel } from "@/components/contracts/ClearanceWorkflowFilesPanel"; import { BookingRequestStatusBadge } from "@/components/contracts/BookingRequestStatusBadge"; import { ContractRevisionTimeline } from "@/components/contracts/ContractRevisionTimeline"; import { ContractMilestonesTimeline } from "@/components/contracts/ContractMilestonesTimeline"; import { ContractCustomerCard, ContractDocumentsCard, } from "@/components/contracts/detail/ContractDetailTabCards"; import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge"; import { getContractStatusMeta } from "@/features/contracts/contract-status.config"; import { toBookingListRow } from "@/features/bookings/mapBookingListRow"; import { useFileViewer } from "@/hooks/useFileViewer"; import { useBookingList } from "@/hooks/bookings/useBookings"; import { useContractDetail, useContractMutations, } from "@/hooks/contracts/useContracts"; import { contractsService } from "@/services/contracts.service"; import { api } from "@/services/api"; import { QUERY_KEYS } from "@/constants/QUERY_KEYS"; import { formatMoney } from "@/components/customers"; import { downloadBookingFile, fetchViewableFile, } from "@/services/files.service"; import type { CustomerDocument } from "@/types/customer"; import type { BookingDetail } from "@/types/booking"; import { DataTable, type ColumnDef } from "@edr/ui-common"; import type { Freight } from "@edr/types"; // Clearance phase — actionable (docs approve / query / finalize on the hub). const CLEARANCE_ACTIVE_STATUSES = [ "AWAITING_CLEARANCE_DOCUMENTS", "CLEARANCE_UNDER_REVIEW", "CLEARANCE_READY_FOR_BOOKING", ]; // Clearance is done — its documents are still worth loading (read-only record). const CLEARANCE_DONE_STATUSES = [ "ACTIVE_SHIPMENT_IN_PROGRESS", "FULLY_EXECUTED", "CONTRACT_ACTIVE", "CONTRACT_CLOSED", "EXPIRED", ]; // Contract is in (or past) its clearance phase — load the clearance view so the // Documents tab can show customs workflow files, and surface the "Review // clearance" deep-link to the Operations hub. const CLEARANCE_REVIEW_STATUSES = [ ...CLEARANCE_ACTIVE_STATUSES, ...CLEARANCE_DONE_STATUSES, ]; function formatDate(value: string | null | undefined): string { if (!value) return "—"; const d = new Date(value); return Number.isNaN(d.getTime()) ? "—" : d.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", }); } /** Same, plus the clock — for values the staff pick to the minute. */ function formatDateTime(value: string | null | undefined): string { if (!value) return "—"; const d = new Date(value); return Number.isNaN(d.getTime()) ? "—" : d.toLocaleString(undefined, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); } export default function ContractRequestDetailPage() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const { data: contract, isLoading, isError, refetch, isFetching, } = useContractDetail(id); const mutations = useContractMutations(id ?? ""); const [searchParams, setSearchParams] = useSearchParams(); const { view, viewer } = useFileViewer(); const requestedTab = searchParams.get("tab"); const setTab = (tab: string | null) => setSearchParams( (prev) => { const next = new URLSearchParams(prev); if (!tab || tab === "details") next.delete("tab"); else next.set("tab", tab); return next; }, { replace: true }, ); const handleViewFile = (file: NonNullable[number]) => view({ name: file.name, url: file.signedUrl ?? file.url, mimeType: file.mimeType, }); const handleDownloadFile = async ( file: NonNullable[number], ) => { try { await downloadBookingFile(file.id, file.name); } catch { toast.error("Could not download file."); } }; const hasClearancePhase = Boolean( contract && CLEARANCE_REVIEW_STATUSES.includes(contract.status), ); const { data: clearanceView } = useQuery({ queryKey: QUERY_KEYS.CONTRACTS.clearance(id ?? ""), queryFn: () => contractsService.getClearance(id!), enabled: Boolean(id) && hasClearancePhase, }); // Customer profile documents (national ID, TIN, import/business license) for // the company this contract belongs to. Shown as a separate section in the // Documents tab, alongside the contract's own attached files. const companyId = contract?.companyId ?? ""; const profileDocumentsQuery = useQuery( api.customers.documents.queryOptions({ input: { id: companyId }, enabled: Boolean(companyId), }), ); const profileDocumentsRaw = Array.isArray(profileDocumentsQuery.data) ? profileDocumentsQuery.data : []; // Reshape to the contract-file shape so we can reuse ContractDocumentsCard. const profileDocuments = profileDocumentsRaw.map( (doc: CustomerDocument) => ({ id: doc.id, code: doc.code, name: doc.name, url: doc.url ?? "", mimeType: doc.mimeType, size: doc.size, resourceId: companyId, resource: "company", }) satisfies NonNullable[number], ); // Bookings drawn down under this contract, and the customer's raw shipment // requests against it — the two halves of "what has this contract produced". const { data: contractBookings, isLoading: bookingsLoading } = useBookingList( { contractId: id, pageSize: 100 }, Boolean(id), ); const bookingRequestsQuery = useQuery({ queryKey: QUERY_KEYS.CONTRACTS.bookingRequests(id ?? ""), queryFn: () => contractsService.listBookingRequests(id!), enabled: Boolean(id), }); const downloadContractPdf = async () => { if (!contract?.id) return; try { const blob = await contractsService.downloadContractDocument(contract.id); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; const contractPdf = contract.files?.find((f) => f.code === "contract"); a.download = contractPdf?.name ?? `contract-${contract.reference}.pdf`; a.click(); URL.revokeObjectURL(url); } catch { toast.error("Could not download contract PDF."); } }; if (isLoading) { return (
Loading contract…
); } if (isError || !contract) { return (
Contract not found This request may have been removed or the link is invalid.
); } const statusMeta = getContractStatusMeta(contract.status); const routes = [...(contract.routes ?? [])].sort( (a, b) => a.sortOrder - b.sortOrder, ); const showApprovalCard = contract.status === "PENDING_APPROVAL" || contract.status === "APPROVED" || contract.status === "APPROVED_PENDING_SIGNATURE" || contract.status === "REJECTED"; // Clearance review + finalize now lives solely on the Operations "Clearance // Documents" hub. The Staff-actions "Review clearance" button deep-links there // while the contract is in a clearance-review status — no embedded tab here. const inClearanceReview = CLEARANCE_REVIEW_STATUSES.includes(contract.status); const files = contract.files ?? []; const contractPdf = files.find((f) => f.code === "contract"); // Signature files (code `signature_`) are baked into the contract PDF — // don't list them as standalone documents in the Documents tab. const contractDocuments = files.filter( (f) => !f.code.startsWith("signature_"), ); const hasContractDocument = Boolean( contractPdf || contract.contractGeneratedAt, ); const canViewSign = (contract.status === "CONTRACT_READY" || contract.status === "SIGNED_CUSTOMER") && Boolean(contract.contractGeneratedAt); // Resolve the active tab from the URL, falling back to details when the // requested tab isn't available for this contract. const currentTab = requestedTab === "shipments" ? "shipments" : requestedTab === "documents" ? "documents" : requestedTab === "history" ? "history" : "details"; const customerLabel = contract.isGovernment ? (contract.governmentInstitution ?? "Government") : (contract.company?.name ?? "—"); const kpis: KpiItem[] = [ { label: "Shipments", value: contract.activeBookingCount ?? 0, hint: "active", icon: Package, color: "edr-green", }, { label: "Valid until", value: contract.contractValidUntil ? formatDate(contract.contractValidUntil) : "—", icon: CalendarClock, color: "blue", }, { label: "Routes", value: routes.length, icon: RouteIcon, color: "teal", }, { label: "Currency", value: contract.paymentCurrency, icon: Wallet, color: "orange", }, ]; return ( {contract.contractKind === "GENERAL" ? "General" : "One-time"} } subtitle={ · Created {formatDate(contract.createdAt)} {contract.contractValidUntil ? ` · Valid until ${formatDateTime(contract.contractValidUntil)}` : ""} } action={ refetch()} > {hasContractDocument && ( {canViewSign && ( } onClick={() => navigate(`/dashboard/contract-requests/${contract.id}/view`) } > View & sign contract )} {contractPdf && ( } onClick={() => void fetchViewableFile(contractPdf.id, contractPdf.name).then( view, ) } > View contract )} } onClick={() => void downloadContractPdf()} > Download PDF )} } /> {/* A contract resting in APPROVED means the automatic PDF generation on final approval failed — on success it moves straight to CONTRACT_READY. Offer the manual retry. */} {contract.status === "APPROVED" ? ( } title="Contract document was not generated" > All approvals are complete, but generating the contract PDF failed. Retry the generation below. ) : null} {contract.status === "REJECTED" && contract.latestRejectionNote ? ( } title="Rejection reason" > {contract.latestRejectionNote} ) : null} {contract.status === "PENDING_APPROVAL" && contract.latestSendBackNote ? ( } title="Sent back in the approval chain" > {contract.latestSendBackNote} ) : null} {/* LEFT — primary content */} }> Details }> Shipments } rightSection={ contractDocuments.length + profileDocuments.length > 0 ? ( {contractDocuments.length + profileDocuments.length} ) : null } > Documents }> History {contract.equipmentReturn ? ( ) : null} {contract.contractValidityDays != null ? ( ) : null} {contract.estimatedShipmentDate ? ( ) : null} {contract.firstMilePickupAddress ? ( ) : null} {contract.lastMileDeliveryAddress ? ( ) : null} {contract.financialTerms ? ( Financial terms {contract.financialTerms} ) : null} {routes.length === 0 ? ( No routes on this contract. ) : ( {routes.map((r) => ( {r.originYard?.label ?? r.originYard?.code ?? "Origin"} {r.destinationYard?.label ?? r.destinationYard?.code ?? "Destination"} {r.km != null ? ( {r.km} km ) : null} ))} )} {directionLabel(contract.tradeDirection)} {contract.freightType} {contract.isHazardous ? ( } > Hazardous ) : null} {contract.isReefer ? ( } > Reefer ) : null} {contract.isHazardous ? ( ) : null} {(contract.cargoScope ?? []).length === 0 ? ( No cargo scope lines. ) : ( {(contract.cargoScope ?? []).map((s) => { const isContainer = Boolean(s.containerSize); // Bulk lines carry their commodity detail (name + unit); // container lines carry the size (20ft / 40ft). const title = isContainer ? `${s.containerSize} container` : (s.cargoType?.cargoTypeName ?? s.cargoFreeText ?? s.cargoType?.code ?? "Bulk cargo"); // quantityCap unit: containers for a size line, else the // cargo type's unit of measure (tons / items / …), default tons. const capUnit = isContainer ? "containers" : (s.cargoType?.unitOfMeasure?.toLowerCase() ?? "tons"); return (
{title} {isContainer ? "Container" : "Bulk"} {s.cargoType?.code ? ( Code: {s.cargoType.code} ) : null} {s.quantityCap != null ? `Cap: ${s.quantityCap} ${capUnit}` : "Cap: uncapped"}
); })}
)}
{contract.pricingBreakdown?.lineItems?.length ? ( {contract.pricingBreakdown.lineItems.map((li) => ( {li.label} {li.containerSize ? ` · ${li.containerSize}` : ""} {contract.pricingBreakdown?.currency} {li.unitPrice} /{" "} {li.unit} ))} ) : null} {contract.contractSummary ? ( {contract.contractSummary} ) : null}
navigate(`/dashboard/booking-requests/${row.id}`) } /> navigate(`/dashboard/shipment-requests/${row.id}`) } /> {(clearanceView?.workflowFiles?.length ?? 0) > 0 ? ( void handleDownloadFile({ id: f.id, name: f.name } as never)} /> ) : null}
{/* RIGHT — sticky action rail */} navigate( `/dashboard/contracts/clearance-documents/${contract.id}`, ) : undefined } /> {showApprovalCard && ( )}
{viewer}
); } const bookingColumns: ColumnDef[] = [ { id: "reference", header: "Booking", cell: ({ row }) => ( {row.original.reference} ), }, { id: "route", header: "Route", cell: ({ row }) => { const r = toBookingListRow(row.original); return ( {r.originLabel} {r.destinationLabel} ); }, }, { id: "status", header: "Status", cell: ({ row }) => , }, { id: "amount", header: "Amount", meta: { headerClassName: "text-right", cellClassName: "text-right" }, cell: ({ row }) => ( {formatMoney(Number(row.original.totalAmount), row.original.paymentCurrency)} ), }, { id: "createdAt", header: "Created", meta: { headerClassName: "text-right", cellClassName: "text-right" }, cell: ({ row }) => ( {formatDate(row.original.createdAt)} ), }, ]; const requestColumns: ColumnDef[] = [ { id: "reference", header: "Request", cell: ({ row }) => ( {row.original.reference} ), }, { id: "status", header: "Status", cell: ({ row }) => , }, { id: "scheduledDate", header: "Requested for", cell: ({ row }) => ( {row.original.scheduledDate ? formatDate(row.original.scheduledDate) : "—"} ), }, { id: "createdAt", header: "Submitted", meta: { headerClassName: "text-right", cellClassName: "text-right" }, cell: ({ row }) => ( {formatDate(row.original.createdAt)} ), }, ]; function InfoRow({ label, value }: { label: string; value: string }) { return (
{label} {value}
); }