import { useMemo, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { ActionIcon, Alert, Badge, Box, Button, Center, Divider, Grid, Group, Loader, NumberInput, Select, Stack, Text, Textarea, TextInput, } from "@mantine/core"; import { Container as ContainerIcon, FileText, Package, Plus, Trash2, } from "lucide-react"; import type { Freight } from "@edr/types"; import { PageContainer } from "@/components/page"; import { PageHeader } from "@/components/page/PageHeader"; import { SectionCard } from "@/components/bookings/detail/SectionCard"; import { useContractCapacity, useContractDetail, useContractMutations, } from "@/hooks/contracts/useContracts"; import { Boxes } from "lucide-react"; interface UnitDraft { containerNumber: string; sealNumber: string; vgmTons: number | string; } interface ContainerLineDraft { containerSize: string; hazardousQuantity: number | string; reeferQuantity: number | string; units: UnitDraft[]; } interface BulkLineDraft { cargoTypeId: string; cargoWeightTons: number | string; itemCount: number | string; hazardousQuantity: number | string; } function emptyUnit(): UnitDraft { return { containerNumber: "", sealNumber: "", vgmTons: "" }; } export default function GlCreateBookingForm() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const { data: contract, isLoading } = useContractDetail(id); const { data: capacity = [] } = useContractCapacity(id); const mutations = useContractMutations(id ?? ""); const [scheduledDate, setScheduledDate] = useState(""); const [contractRouteId, setContractRouteId] = useState(null); const [notes, setNotes] = useState(""); const [containerLines, setContainerLines] = useState([]); const [bulkLines, setBulkLines] = useState([]); const isContainer = contract?.freightType === "CONTAINER"; const routes = useMemo( () => [...(contract?.routes ?? [])].sort((a, b) => a.sortOrder - b.sortOrder), [contract?.routes], ); const needsRouteSelect = contract?.contractKind === "GENERAL" && routes.length > 1; const containerSizes = useMemo(() => { const sizes = new Set(); (contract?.cargoScope ?? []).forEach((s) => { if (s.containerSize) sizes.add(s.containerSize); }); return [...sizes]; }, [contract?.cargoScope]); if (isLoading) { return (
); } if (!contract) { return ( ); } // ── Container line helpers ── const addContainerLine = () => setContainerLines((prev) => [ ...prev, { containerSize: containerSizes[0] ?? "20ft", hazardousQuantity: "", reeferQuantity: "", units: [emptyUnit()], }, ]); const removeContainerLine = (idx: number) => setContainerLines((prev) => prev.filter((_, i) => i !== idx)); const patchLine = (idx: number, patch: Partial) => setContainerLines((prev) => prev.map((l, i) => (i === idx ? { ...l, ...patch } : l)), ); const addUnit = (lineIdx: number) => patchLine(lineIdx, { units: [...containerLines[lineIdx].units, emptyUnit()], }); const removeUnit = (lineIdx: number, unitIdx: number) => patchLine(lineIdx, { units: containerLines[lineIdx].units.filter((_, i) => i !== unitIdx), }); const patchUnit = ( lineIdx: number, unitIdx: number, patch: Partial, ) => patchLine(lineIdx, { units: containerLines[lineIdx].units.map((u, i) => i === unitIdx ? { ...u, ...patch } : u, ), }); // ── Bulk line helpers ── const addBulkLine = () => setBulkLines((prev) => [ ...prev, { cargoTypeId: "", cargoWeightTons: "", itemCount: "", hazardousQuantity: "" }, ]); const removeBulkLine = (idx: number) => setBulkLines((prev) => prev.filter((_, i) => i !== idx)); const patchBulk = (idx: number, patch: Partial) => setBulkLines((prev) => prev.map((l, i) => (i === idx ? { ...l, ...patch } : l)), ); const canSubmit = Boolean(scheduledDate) && (!needsRouteSelect || Boolean(contractRouteId)) && (isContainer ? containerLines.length > 0 : bulkLines.length > 0); const handleSubmit = () => { if (!scheduledDate) return; const payload: Freight.CreateBookingUnderContractDto = { scheduledDate, ...(contractRouteId ? { contractRouteId } : {}), ...(notes.trim() ? { notes: notes.trim() } : {}), }; if (isContainer) { payload.containers = containerLines.map((l) => ({ containerSize: l.containerSize, quantity: l.units.length, ...(l.hazardousQuantity !== "" ? { hazardousQuantity: Number(l.hazardousQuantity) } : {}), ...(l.reeferQuantity !== "" ? { reeferQuantity: Number(l.reeferQuantity) } : {}), units: l.units.map((u) => ({ containerNumber: u.containerNumber, ...(u.sealNumber ? { sealNumber: u.sealNumber } : {}), vgmTons: Number(u.vgmTons) || 0, })), })); } else { payload.bulkLines = bulkLines.map((l) => ({ ...(l.cargoTypeId ? { cargoTypeId: l.cargoTypeId } : {}), ...(l.cargoWeightTons !== "" ? { cargoWeightTons: Number(l.cargoWeightTons) } : {}), ...(l.itemCount !== "" ? { itemCount: Number(l.itemCount) } : {}), ...(l.hazardousQuantity !== "" ? { hazardousQuantity: Number(l.hazardousQuantity) } : {}), })); } mutations.createBooking.mutate(payload, { onSuccess: (booking) => navigate(`/dashboard/bookings/${booking.id}/milestones`), }); }; return ( {capacity.length > 0 && ( c.remaining === 0) ? "red" : "blue"} variant="light" radius="md" icon={} title="Contract draw-down capacity" > {capacity.map((c, i) => ( {c.containerSize ?? "Bulk"}: {c.remaining} of {c.cap} left ))} )} setScheduledDate(e.currentTarget.value)} required /> {needsRouteSelect && ( patchLine(lineIdx, { containerSize: v ?? line.containerSize, }) } data={ containerSizes.length > 0 ? containerSizes : ["20ft", "40ft"] } /> patchLine(lineIdx, { hazardousQuantity: v }) } /> patchLine(lineIdx, { reeferQuantity: v }) } /> {line.units.map((unit, unitIdx) => ( patchUnit(lineIdx, unitIdx, { containerNumber: e.currentTarget.value, }) } /> patchUnit(lineIdx, unitIdx, { sealNumber: e.currentTarget.value, }) } /> patchUnit(lineIdx, unitIdx, { vgmTons: v }) } /> removeUnit(lineIdx, unitIdx)} aria-label="Remove unit" > ))} ))} )} ) : ( } onClick={addBulkLine} > Add line } > {bulkLines.length === 0 ? ( Add at least one bulk line. ) : ( {bulkLines.map((line, idx) => ( Line {idx + 1} removeBulkLine(idx)} aria-label="Remove line" > patchBulk(idx, { cargoTypeId: e.currentTarget.value }) } /> patchBulk(idx, { cargoWeightTons: v }) } /> patchBulk(idx, { itemCount: v })} /> patchBulk(idx, { hazardousQuantity: v }) } /> ))} )} )}