import { useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { useParams } from "react-router-dom"; import { Alert, Badge, Box, Grid, Group, Loader, Paper, Progress, RingProgress, Stack, Text, ThemeIcon, } from "@mantine/core"; import { AlertCircle, ArrowRight, CheckCircle2, Clock, PackageCheck, ShieldCheck, } from "lucide-react"; import { Link } from "react-router-dom"; import { ClearanceOpsTabs } from "@/components/contracts/ClearanceOpsTabs"; import { PageContainer } from "@/components/page/PageContainer"; import { PageHeader } from "@/components/page/PageHeader"; import { SectionCard } from "@/components/bookings/detail/SectionCard"; import { ContractClearanceReviewSection } from "@/components/contracts/ContractClearanceReviewSection"; import { PhasedClearanceActionPanel } from "@/components/contracts/PhasedClearanceActionPanel"; import { QUERY_KEYS } from "@/constants/QUERY_KEYS"; import { useFileViewer } from "@/hooks/useFileViewer"; import { contractsService } from "@/services/contracts.service"; import { downloadBookingFile } from "@/services/files.service"; import { useBookingMilestones, useContractDetail, } from "@/hooks/contracts/useContracts"; export default function ContractClearanceDetailPage() { const { id } = useParams<{ id: string }>(); const { view, viewer } = useFileViewer(); const { data: contract, refetch: refetchContract } = useContractDetail(id); const { data: clearance, isLoading, isError, refetch, } = useQuery({ queryKey: QUERY_KEYS.CONTRACTS.clearance(id ?? ""), queryFn: () => contractsService.getClearance(id!), enabled: Boolean(id), }); const stats = useMemo(() => { const docs = (clearance?.documents ?? []).filter( (d) => d.uploadedBy === "customer", ); const total = docs.length; const approved = docs.filter((d) => d.reviewStatus === "APPROVED").length; const queried = docs.filter((d) => d.reviewStatus === "QUERIED").length; const pending = total - approved - queried; const pct = total === 0 ? 0 : Math.round((approved / total) * 100); return { total, approved, queried, pending, pct }; }, [clearance]); const reference = contract?.reference ?? "Clearance"; const phasedCustoms = contract?.contractKind === "ONE_TIME" && Boolean(contract.customsClearingEnabled); const docsPhaseComplete = clearance?.milestones?.some( (m) => m.milestoneCode === "DOCUMENTS_APPROVED" && m.status === "COMPLETED", ) ?? false; const ready = clearance?.bookingReady === true; const docReviewLocked = phasedCustoms ? docsPhaseComplete : clearance?.clearanceStatus === "CLEARANCE_READY_FOR_BOOKING"; const shipmentLocked = Boolean( contract?.status && [ "ACTIVE_SHIPMENT_IN_PROGRESS", "FULLY_EXECUTED", "CONTRACT_ACTIVE", "CONTRACT_CLOSED", "EXPIRED", ].includes(contract.status), ); const linkedBookingId = useMemo(() => { // Prefer the clearance view's server-resolved linkedBookingId (same field the // GL Djibouti page uses). The contract's clearanceCycles[cycle].bookingId can // be null/stale for an export FCFS booking, which would disable // useBookingMilestones → empty milestones → the export "Payment & wagon // allocation" step reads FREIGHT_PAYMENT_SETTLED as not-done and stays stuck. const cycle = contract?.clearanceCycles?.find( (c) => c.cycleNumber === (contract?.clearanceCycleNumber ?? 1), ); return clearance?.linkedBookingId ?? cycle?.bookingId ?? undefined; }, [clearance, contract]); const bookingAlreadyCreated = Boolean(linkedBookingId) || shipmentLocked; const canCreateBooking = ready && !bookingAlreadyCreated; const reviewReadOnly = shipmentLocked; const queriesLocked = Boolean(clearance?.preClearanceFinalized); const bookingHref = `/dashboard/contracts/${id}/create-booking`; const { data: bookingMilestones, refetch: refetchBookingMilestones } = useBookingMilestones(linkedBookingId); if (isLoading) { return ( Loading clearance… ); } if (isError || !clearance) { return ( }> We couldn’t load this contract’s clearance. ); } const workflowFiles = clearance.workflowFiles ?? []; return ( } > Booking created ) : ready ? ( } > Ready — create booking ) : clearance.allApproved ? ( } > All approved ) : ( } > Review pending ) } /> {bookingAlreadyCreated ? ( } title="Shipment booking created" > GL Ethiopia has created the shipment booking for this contract. {linkedBookingId ? ( <> {" "} View booking → ) : null} ) : canCreateBooking ? ( } title="Clearance complete" > Pre-booking clearance is complete. GL Ethiopia can create the shipment booking. ) : null} void downloadBookingFile(f.id, f.name)} clearanceTab={ { void refetch(); void refetchContract(); void refetchBookingMilestones(); }} /> {phasedCustoms ? ( { void refetch(); void refetchBookingMilestones(); }} onViewFile={view} onDownloadFile={(f) => void downloadBookingFile(f.id, f.name)} bookingCreateHref={canCreateBooking ? bookingHref : undefined} bookingCreated={bookingAlreadyCreated} /> ) : ( {stats.pct}% approved } /> )} } /> {viewer} ); } function ClearanceHero({ contract, stats, }: { contract: ReturnType["data"]; 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, ); const origin = routes[0]?.originYard?.label ?? routes[0]?.originYard?.code ?? "Origin"; const last = routes[routes.length - 1] ?? routes[0]; const destination = last?.destinationYard?.label ?? last?.destinationYard?.code ?? "Destination"; return ( {contract?.reference ?? "Clearance"} {direction} {customs ? ( } > Customs ) : ( No customs )} {serviceName && ( {serviceName} )} {origin} {destination} Document review {stats.approved}/{stats.total} ); } function ProgressStat({ color, label, value, }: { color: string; label: string; value: number; }) { return ( {value} {label} ); }