import { useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { Button, Group, Modal, Stack, Text, Textarea } from "@mantine/core"; import { Check, Eye, // FilePen, // ponytail: back with the "Edit contract articles" button FileSignature, MessageSquareWarning, PauseCircle, PlayCircle, ShieldCheck, XCircle, Zap, } from "lucide-react"; import type { Freight } from "@edr/types"; import { useAuth } from "@/auth/useAuth"; import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions"; import { api } from "@/services/api"; import { contractsService } from "@/services/contracts.service"; import { SectionCard } from "@/components/bookings/detail/SectionCard"; import { ContractDocumentEditorModal } from "@/components/contracts/ContractDocumentEditorModal"; import { ContractPreviewModal } from "@/components/contracts/ContractPreviewModal"; 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", ]; /** * Every step from the customer signature onward can be frozen. Mirrors * SUSPENDABLE_CONTRACT_STATUSES on the API — the server is the authority, this * list only decides whether the button is drawn. */ const SUSPENDABLE_STATUSES = [ "SIGNED_CUSTOMER", "FULLY_EXECUTED", "CONTRACT_ACTIVE", "AWAITING_CLEARANCE_DOCUMENTS", "CLEARANCE_UNDER_REVIEW", "CLEARANCE_READY_FOR_BOOKING", "ACTIVE_SHIPMENT_IN_PROGRESS", ]; /** Detail-page staff actions: accept / request changes / reject / generate / sign. */ export function ContractActionsToolbar({ contract, mutations, onReviewClearance, }: ContractActionsToolbarProps) { const navigate = useNavigate(); const { user } = useAuth(); const { status } = contract; // Intake permissions are split per freight type: an accept:bulk holder must // not see the accept button on a container contract (API enforces the same). const arm = contract.freightType === "BULK" ? "bulk" : "container"; const mayAccept = hasPermission(user, FREIGHT_PERMS.contracts.staffAccept[arm]); const mayRequestChanges = hasPermission( user, FREIGHT_PERMS.contracts.requestChanges[arm], ); const mayReject = hasPermission(user, FREIGHT_PERMS.contracts.reject[arm]); // One key both ways — whoever can freeze a contract can unfreeze it. const maySuspend = hasPermission(user, FREIGHT_PERMS.contracts.suspend); const [editorOpen, setEditorOpen] = useState(false); const [editorMode, setEditorMode] = useState<"accept" | "edit">("accept"); const [previewOpen, setPreviewOpen] = useState(false); const [changesOpen, setChangesOpen] = useState(false); const [changesNote, setChangesNote] = useState(""); const [rejectOpen, setRejectOpen] = useState(false); const [rejectReason, setRejectReason] = useState(""); const [suspendOpen, setSuspendOpen] = useState(false); const [suspendReason, setSuspendReason] = useState(""); const [resumeOpen, setResumeOpen] = useState(false); const [resumeNote, setResumeNote] = useState(""); // Whether the document is editable depends on WHO is viewing — only the // approver whose turn it is may edit — so the server decides, not the client. const { data: draft } = useQuery({ queryKey: ["contracts", contract.id, "document-draft"], queryFn: () => contractsService.getContractDocumentDraft(contract.id), enabled: contract.status === "PENDING_APPROVAL", staleTime: 0, }); // 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. ); } // Frozen: nothing on this contract moves — no new bookings, no progress on // the shipments already under it — until the suspension is lifted, which // returns the contract to the status it was suspended at. if (status === "SUSPENDED") { return ( This contract is frozen. New bookings are blocked and its existing shipments cannot progress. {contract.statusBeforeSuspension ? ` Lifting the suspension returns it to ${contract.statusBeforeSuspension}.` : ""} {contract.latestSuspensionNote && ( Reason: {contract.latestSuspensionNote} )} {maySuspend ? ( ) : ( You do not have permission to lift a suspension. )} setResumeOpen(false)} title="Lift suspension?" centered > Contract {contract.reference} will return to{" "} {contract.statusBeforeSuspension ?? "CONTRACT_ACTIVE"} and the customer will be notified. Bookings on it resume immediately.