import { useMemo, useState } from "react"; import { Alert, Badge, Button, FileInput, Group, Modal, NumberInput, Paper, Select, Stack, Stepper, Text, Textarea, } from "@mantine/core"; import { DateInput, DateTimePicker } from "@mantine/dates"; import { AlertTriangle, CheckCircle2, FileText, PackageCheck, Receipt, Ship, Train, Truck, Upload, } from "lucide-react"; import type { Freight } from "@edr/types"; import toast from "react-hot-toast"; import { SectionCard } from "@/components/bookings/detail/SectionCard"; import { PhasedFileDropzone } from "@/components/contracts/PhasedFileDropzone"; import { TransitPermitMultiUpload, type TransitPermitUploadedRow, } from "@/components/contracts/TransitPermitMultiUpload"; import { findWorkflowFile, PhasedUploadedFileRow, } from "@/components/contracts/PhasedUploadedFileRow"; import { DeclarationStep, StepStatus, isBookingMilestoneDone, isMilestoneDone, type ClearanceViewLike, type MilestoneRow, } from "@/components/contracts/PhasedClearanceActionPanel"; import { contractsService } from "@/services/contracts.service"; import { bookingsService } from "@/services/bookings.service"; /** * Export customs flow, ordered per the stakeholder process: * customer docs → RO (DJ) → declaration (ET, auto-releases) → create booking (ET) * → payment + wagons → transport document (ET) → train to Djibouti → gate pass (DJ) * → accept T1 (DJ) → final invoice (DJ) + customer slip + GL confirm. */ export function computeExportActiveStep( clearance: ClearanceViewLike, bookingMilestones: MilestoneRow[], bookingCreated: boolean, ): number { const released = Boolean(clearance.bookingReady || clearance.operationReady); if (!isMilestoneDone(clearance.milestones, "DOCUMENTS_APPROVED")) return 0; if (!isMilestoneDone(clearance.milestones, "RELEASE_ORDER_SECURED")) return 1; if (!isMilestoneDone(clearance.milestones, "DECLARED") || !released) return 2; if (!bookingCreated) return 3; if ( !isBookingMilestoneDone(bookingMilestones, "FREIGHT_PAYMENT_SETTLED") || !( isBookingMilestoneDone(bookingMilestones, "WAGON_ALLOCATED") || clearance.train?.wagonAllocated ) ) { return 4; } if (!isBookingMilestoneDone(bookingMilestones, "EXPORT_TRANSPORT_ISSUED")) return 5; if (!clearance.train?.arrivedAt) return 6; if (!clearance.gatepassGranted) return 7; if (!clearance.t1Closed) return 8; if (clearance.finalInvoice?.status !== "PAID") return 9; return 10; } export function exportTransitFilesFromWorkflow( workflowFiles: Freight.ClearanceWorkflowFile[], ): TransitPermitUploadedRow[] { return workflowFiles .filter( (f) => f.category === "transit" && f.code.toLowerCase().startsWith("export_transport_document") && f.file, ) .map((f) => ({ code: f.code, label: f.label, file: f.file!, })); } export function ExportClearanceStepper({ contractId, bookingId, clearance, workflowFiles = [], showEt, canEt, showDj, canDj, onChanged, bookingCreateHref, onViewFile, onDownloadFile, useUploadModals = false, onUploadRoRequest, bookingCreated = false, bookingMilestones = [], }: { contractId?: string; bookingId?: string; clearance: ClearanceViewLike; workflowFiles?: Freight.ClearanceWorkflowFile[]; showEt: boolean; canEt: boolean; showDj: boolean; canDj: boolean; onChanged?: () => void; bookingCreateHref?: string; onViewFile?: (file: { name: string; url: string }) => void; onDownloadFile?: (file: { id: string; name: string }) => void; useUploadModals?: boolean; onUploadRoRequest?: () => void; bookingCreated?: boolean; bookingMilestones?: MilestoneRow[]; }) { // Pre-booking actions (RO, declaration, release) target the contract when one // is present; GENERAL customs bookings run the same flow keyed on the booking. const isBooking = Boolean(bookingId) && !contractId; const entityId = contractId ?? bookingId ?? ""; // The booking that carries the post-booking steps (gate pass, T1, invoice). const actionBookingId = clearance.linkedBookingId ?? bookingId ?? null; const effectiveBookingCreated = bookingCreated || Boolean(clearance.linkedBookingId) || isBooking; const activeStep = useMemo( () => computeExportActiveStep(clearance, bookingMilestones, effectiveBookingCreated), [clearance, bookingMilestones, effectiveBookingCreated], ); const released = Boolean(clearance.bookingReady || clearance.operationReady); const declared = isMilestoneDone(clearance.milestones, "DECLARED"); const paymentSettled = isBookingMilestoneDone(bookingMilestones, "FREIGHT_PAYMENT_SETTLED"); const wagonAllocated = isBookingMilestoneDone(bookingMilestones, "WAGON_ALLOCATED") || Boolean(clearance.train?.wagonAllocated); const transportIssued = isBookingMilestoneDone(bookingMilestones, "EXPORT_TRANSPORT_ISSUED"); return ( {clearance.roHold && clearance.roHoldReason ? ( } title="Release Order on hold"> {clearance.roHoldReason} ) : null} {clearance.nextAction ? ( {clearance.nextAction.actor.replace("_", " ")} —{" "} {clearance.nextAction.action} ) : null} Export customs clearance ) : undefined } > } > {showDj && canDj ? ( useUploadModals ? ( ) : ( ) ) : ( {findWorkflowFile(workflowFiles, "release_order") ? ( ) : null} {clearance.vesselDepartureDate ? ( Vessel departure:{" "} {new Date(clearance.vesselDepartureDate).toLocaleDateString()} ) : null} )} : } > {showEt && canEt && !effectiveBookingCreated && (activeStep >= 2 || declared) ? ( {declared && !released ? ( ) : null} ) : ( )} } > {released && bookingCreateHref && !effectiveBookingCreated && showEt && canEt ? ( Export is released. Create the shipment booking for the customer. ) : ( )} } > : } > {showEt && canEt && actionBookingId && wagonAllocated ? ( ) : ( {exportTransitFilesFromWorkflow(workflowFiles).map((row) => ( ))} )} } > : } > : } > ) : ( ) } > ); } /** Legacy in-flight contracts: declaration done before auto-release existed. */ function ConfirmExportReleaseFallback({ entityId, isBooking, onChanged, }: { entityId: string; isBooking: boolean; onChanged?: () => void; }) { const [loading, setLoading] = useState(false); return ( ); } function GatepassStep({ bookingId, clearance, canAct, onChanged, }: { bookingId: string | null; clearance: ClearanceViewLike; canAct: boolean; onChanged?: () => void; }) { const [opened, setOpened] = useState(false); const [at, setAt] = useState(new Date()); const [loading, setLoading] = useState(false); if (clearance.gatepassGranted) { return ( ); } const arrived = Boolean(clearance.train?.arrivedAt); return ( {canAct && bookingId ? ( <> setOpened(false)} title={Grant gate pass} radius="md" size="sm" > setAt(v ? new Date(v) : null)} required /> ) : null} ); } function AcceptT1Step({ bookingId, clearance, transportIssued, workflowFiles = [], canAct, onChanged, onViewFile, onDownloadFile, }: { bookingId: string | null; clearance: ClearanceViewLike; transportIssued: boolean; workflowFiles?: Freight.ClearanceWorkflowFile[]; canAct: boolean; onChanged?: () => void; onViewFile?: (file: { name: string; url: string }) => void; onDownloadFile?: (file: { id: string; name: string }) => void; }) { const [loading, setLoading] = useState(false); const files = exportTransitFilesFromWorkflow(workflowFiles); return ( {files.map((row) => ( ))} {clearance.t1Closed ? ( ) : ( <> {canAct && bookingId ? ( ) : null} )} ); } function FinalInvoiceStep({ bookingId, clearance, canDjAct, canConfirm, onChanged, onViewFile, onDownloadFile, }: { bookingId: string | null; clearance: ClearanceViewLike; canDjAct: boolean; canConfirm: boolean; onChanged?: () => void; onViewFile?: (file: { name: string; url: string }) => void; onDownloadFile?: (file: { id: string; name: string }) => void; }) { const [opened, setOpened] = useState(false); const [amount, setAmount] = useState(""); const [currency, setCurrency] = useState("ETB"); const [description, setDescription] = useState(""); const [file, setFile] = useState(null); const [sending, setSending] = useState(false); const [confirming, setConfirming] = useState(false); const invoice = clearance.finalInvoice ?? null; const paid = invoice?.status === "PAID"; if (!clearance.offloaded && !invoice) { return ( ); } return ( {invoice ? (
{invoice.invoiceNumber} {invoice.totalAmount.toLocaleString()} {invoice.currency} {invoice.description ? ` — ${invoice.description}` : ""}
{invoice.status}
) : null} {invoice?.invoiceFile ? ( ) : null} {invoice?.slipFile ? ( ) : null} {paid ? ( ) : invoice ? ( <> {canConfirm && bookingId && invoice.slipFile ? ( ) : null} ) : canDjAct && bookingId ? ( <> Cargo offloaded — send the final invoice to the customer. setOpened(false)} title={Send final invoice} radius="md" size="md" >