import { useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { Button, Modal, Stack, Text, Textarea } from "@mantine/core"; import { Check, FileCheck, FilePen, FileSignature, MessageSquareWarning, RefreshCw, ShieldCheck, XCircle, Zap, } from "lucide-react"; import type { Freight } from "@edr/types"; import { api } from "@/services/api"; import { SectionCard } from "@/components/bookings/detail/SectionCard"; import { ContractDocumentEditorModal } from "@/components/contracts/ContractDocumentEditorModal"; import type { useContractMutations } from "@/hooks/contracts/useContracts"; /** Dropdown-settings code holding the admin-configured contract validity days. */ const CONTRACT_VALIDITY_PERIODS_CODE = "contract_validity_periods"; type Mutations = ReturnType; interface ContractActionsToolbarProps { contract: Freight.IContract; mutations: Mutations; /** Switch the detail page to its Clearance Review tab. */ onReviewClearance?: () => void; } // Contract is in the pre-booking clearance phase — staff can review the // customer's uploaded documents. const CLEARANCE_REVIEW_STATUSES = [ "AWAITING_CLEARANCE_DOCUMENTS", "CLEARANCE_UNDER_REVIEW", "CLEARANCE_READY_FOR_BOOKING", ]; /** Detail-page staff actions: accept / request changes / reject / generate / sign. */ export function ContractActionsToolbar({ contract, mutations, onReviewClearance, }: ContractActionsToolbarProps) { const navigate = useNavigate(); const { status } = contract; const [editorOpen, setEditorOpen] = useState(false); const [editorMode, setEditorMode] = useState<"accept" | "edit">("accept"); const [changesOpen, setChangesOpen] = useState(false); const [changesNote, setChangesNote] = useState(""); const [rejectOpen, setRejectOpen] = useState(false); const [rejectReason, setRejectReason] = useState(""); // Admin-configured validity durations (days) for the accept dialog. Staff can // only pick one of these — no free-typing. Read-only setting, fetched once. const { data: validitySetting, isLoading: validityLoading } = useQuery({ ...api.dropdownSettings.getByCode.queryOptions({ input: { code: CONTRACT_VALIDITY_PERIODS_CODE }, }), retry: false, }); const validityOptions = useMemo( () => [...(validitySetting?.children ?? [])] .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) .map((o) => ({ value: String(o.value), label: o.label })), [validitySetting], ); if (["REJECTED", "CANCELLED", "EXPIRED", "CONTRACT_CLOSED"].includes(status)) { return null; } if (status === "CHANGES_REQUESTED") { return ( No staff actions until the customer resubmits the contract. ); } const canAccept = status === "SUBMITTED"; // While the contract is PENDING_APPROVAL and NO approver has acted yet, staff // can edit this contract's articles and (re)generate its PDF. The first // approval action locks the document. const docLocked = status !== "PENDING_APPROVAL" || (contract.approvalSteps ?? []).some((s) => s.status !== "PENDING"); const canEditGenerate = status === "PENDING_APPROVAL" && !docLocked; const documentGenerated = Boolean(contract.contractGeneratedAt); // Legacy fallback: if a contract ever lands on APPROVED without a document // (older flow), still offer a manual generate that moves it to CONTRACT_READY. const needsManualGenerate = status === "APPROVED" && !contract.contractGeneratedAt; // Signing now happens on the contract VIEW page (staff must open and read the // generated contract before signing) — no sign button in this toolbar. const canViewContract = ["CONTRACT_READY", "SIGNED_CUSTOMER"].includes(status) && Boolean(contract.contractGeneratedAt); // Show "Review clearance" while the contract is in the document-review phase. // Reviewer = GL (Path B / customs) or Operations (Path A / no customs). const canReviewClearance = Boolean(onReviewClearance) && CLEARANCE_REVIEW_STATUSES.includes(status); const clearanceReviewer = contract.customsClearingEnabled ? "Review clearance (GL)" : "Review clearance (Ops)"; return ( Confirm each step before it is applied. {canAccept && ( <> )} {canEditGenerate && ( <> {documentGenerated ? "Document generated. Approvers can now review it. You can still edit and regenerate until the first approval." : "Review the contract document, edit its articles if needed, then generate it so approvers can review."} )} {needsManualGenerate && ( )} {canViewContract && ( )} {canReviewClearance && ( )} {/* GL "Create booking" removed for now — clearance ends at finalize and the customer creates the booking in the portal. */} {!canAccept && !canEditGenerate && !needsManualGenerate && !canViewContract && !canReviewClearance && ( No staff actions available for this status. Monitor until the workflow advances. )} {/* Accept / edit — review + optionally edit this contract's articles */} setEditorOpen(false)} contractId={contract.id} mode={editorMode} validityOptions={validityOptions} validityLoading={validityLoading} accepting={mutations.staffAccept.isPending} saving={mutations.updateDocument.isPending} onAccept={(days, snapshot) => mutations.staffAccept.mutate( { validityDays: days, documentSnapshot: snapshot }, { onSuccess: () => setEditorOpen(false) }, ) } onSaveEdit={(snapshot) => mutations.updateDocument.mutate(snapshot, { onSuccess: () => setEditorOpen(false), }) } /> {/* Request changes */} setChangesOpen(false)} title="Request changes" centered >