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, Building2, Calendar, CalendarClock, Download, FileSignature, FileText, Files, Flame, LayoutGrid, Package, Receipt, RefreshCw, Route as RouteIcon, ShieldCheck, Snowflake, Users, } from "lucide-react"; import { Alert, Badge, Box, Button, Center, Container, Grid, Group, Loader, Paper, Stack, Tabs, Text, Title, } from "@mantine/core"; import toast from "react-hot-toast"; import "@/components/overview/overview.css"; import { PageContainer } from "@/components/page"; import Breadcrumbs from "@/components/ui/Breadcrumbs"; import { SectionCard } from "@/components/bookings/detail/SectionCard"; import { detailStyles } from "@/components/bookings/detail/booking-detail.styles"; import { ContractStatusBadge } from "@/components/contracts/ContractStatusBadge"; import { ContractWorkflowStepper } from "@/components/contracts/ContractWorkflowStepper"; import { ContractActionsToolbar } from "@/components/contracts/ContractActionsToolbar"; import { ContractApprovalStepsCard } from "@/components/contracts/ContractApprovalStepsCard"; import { ContractClearanceReviewSection } from "@/components/contracts/ContractClearanceReviewSection"; import { ClearanceWorkflowFilesPanel } from "@/components/contracts/ClearanceWorkflowFilesPanel"; import { ContractRevisionTimeline } from "@/components/contracts/ContractRevisionTimeline"; import { ContractCustomerCard, ContractDocumentsCard, } from "@/components/contracts/detail/ContractDetailTabCards"; import { getContractStatusMeta } from "@/features/contracts/contract-status.config"; import { useFileViewer } from "@/hooks/useFileViewer"; 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 { downloadBookingFile, fetchViewableFile, } from "@/services/files.service"; import type { CustomerDocument } from "@/types/customer"; import type { Freight } from "@edr/types"; // Clearance phase — staff can still ACT (approve / query / finalize). const CLEARANCE_ACTIVE_STATUSES = [ "AWAITING_CLEARANCE_DOCUMENTS", "CLEARANCE_UNDER_REVIEW", "CLEARANCE_READY_FOR_BOOKING", ]; // Clearance is done — the tab stays visible but READ-ONLY so staff/customer can // see which documents were approved, by whom, and when. const CLEARANCE_DONE_STATUSES = [ "ACTIVE_SHIPMENT_IN_PROGRESS", "FULLY_EXECUTED", "CONTRACT_ACTIVE", "CONTRACT_CLOSED", "EXPIRED", ]; // Show the Clearance Review tab in either phase (active or done). 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", }); } 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) => setSearchParams( (prev) => { const next = new URLSearchParams(prev); if (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 showClearanceTabQuery = 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) && showClearanceTabQuery, }); // 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], ); 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"; const showClearanceTab = CLEARANCE_REVIEW_STATUSES.includes(contract.status); const phasedCustoms = contract.contractKind === "ONE_TIME" && Boolean(contract.customsClearingEnabled); const docsPhaseComplete = clearanceView?.milestones?.some( (m) => m.milestoneCode === "DOCUMENTS_APPROVED" && m.status === "COMPLETED", ) ?? false; const clearanceApprovalsLocked = phasedCustoms && docsPhaseComplete; // Once clearance is finalized the tab is informational only — no approve/query. const clearanceReadOnly = CLEARANCE_DONE_STATUSES.includes(contract.status); // Path A (no customs) → Operations reviews; Path B (customs) → GL reviews. const selfClear = !contract.customsClearingEnabled; 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 (e.g. clearance pre-phase). const currentTab = requestedTab === "documents" ? "documents" : requestedTab === "customer" ? "customer" : requestedTab === "clearance" && showClearanceTab ? "clearance" : "details"; const customerLabel = contract.isGovernment ? (contract.governmentInstitution ?? "Government") : (contract.company?.name ?? "—"); return ( {/* Hero */} Contract reference {contract.reference} {contract.contractKind === "GENERAL" ? "General" : "One-time"} {contract.contractValidUntil ? ( ) : null} {hasContractDocument && ( {canViewSign && ( )} {contractPdf && ( )} )} {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} setTab(v ?? "details")} variant="pills" color="edr-green" classNames={{ list: "ov-tablist", tab: "ov-tab" }} > }> Details } rightSection={ contractDocuments.length + profileDocuments.length > 0 ? ( {contractDocuments.length + profileDocuments.length} ) : null } > Documents }> Customer {showClearanceTab && ( } > Clearance Review )} {/* LEFT — primary content */} {currentTab === "clearance" ? ( refetch()} /> {(clearanceView?.workflowFiles?.length ?? 0) > 0 ? ( void handleDownloadFile({ id: f.id, name: f.name } as never)} /> ) : null} ) : currentTab === "documents" ? ( {(clearanceView?.workflowFiles?.length ?? 0) > 0 ? ( void handleDownloadFile({ id: f.id, name: f.name } as never)} /> ) : null} ) : currentTab === "customer" ? ( ) : ( {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.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}
)}
{/* RIGHT — sticky action rail */} setTab("clearance") : undefined } /> {showApprovalCard && ( )}
{viewer}
); } function MetaItem({ icon: Icon, text, }: { icon: typeof Building2; text: string; }) { return ( {text} ); }