import { type ReactNode, useMemo, useState } from "react"; import { ArrowRight, Eye, MoreHorizontal, Printer, RefreshCw, Ruler, Truck, } from "lucide-react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { ColumnDef } from "@edr/ui-common"; import { DataTable, DataTableFooter, usePagination } from "@edr/ui-common"; import { ActionIcon, Badge, Box, Button, Card, Checkbox, Divider, Group, Menu, Modal, NumberInput, ScrollArea, Select, SimpleGrid, Stack, Text, TextInput, UnstyledButton, } from "@mantine/core"; import type { ArrivalQueueItem } from "@/types/warehouse"; import { warehouseService } from "@/services/warehouse.service"; import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles"; import { QUERY_KEYS } from "@/constants/QUERY_KEYS"; import { useToast } from "@/hooks/use-toast"; import { LAST_MILE_STATUSES, type LastMileApiStatus, type LastMileRecord, lastMileService, } from "@/services/last-mile.service"; import { vehiclesService } from "@/services/vehicles.service"; import { ratesService } from "@/services/rates.service"; const formatPrice = (amount: number) => `ETB ${amount.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, })}`; const STATUS_META: Record = { PAYMENT_PENDING: { label: "Payment Pending", color: "yellow" }, READY_TO_TRANSIT: { label: "Ready to Transit", color: "blue" }, IN_TRANSIT: { label: "In Transit", color: "indigo" }, DELIVERED: { label: "Delivered", color: "green" }, }; const NEXT_STATUS: Partial> = { PAYMENT_PENDING: "READY_TO_TRANSIT", READY_TO_TRANSIT: "IN_TRANSIT", IN_TRANSIT: "DELIVERED", }; type AssignmentStatus = "ASSIGNED" | "UNASSIGNED"; type StatusFilter = "ALL" | LastMileApiStatus | AssignmentStatus; const FILTER_OPTIONS: { value: StatusFilter; label: string }[] = [ { value: "ALL", label: "All" }, ...LAST_MILE_STATUSES.filter((s) => s !== "PAYMENT_PENDING").map((s) => ({ value: s as StatusFilter, label: STATUS_META[s].label })), { value: "ASSIGNED", label: "Assigned" }, { value: "UNASSIGNED", label: "Unassigned" }, ]; const vehicleLabel = (record: LastMileRecord) => { if (!record.vehicle) return null; const v = record.vehicle; const parts = [`${v.manufacturer} ${v.model}`, v.plateNumber]; if (v.code) parts.unshift(v.code); const plates = [v.powerPlateNo, v.trailerPlateNo].filter(Boolean).join(" / "); if (plates) parts.push(plates); return parts.join(" · "); }; const isAssigned = (record: LastMileRecord) => Boolean(record.vehicleId); const bookingRef = (r: LastMileRecord) => r.booking?.reference ?? r.bookingId; const customerName = (r: LastMileRecord) => r.booking?.company?.name ?? "—"; const deliveryLocation = (r: LastMileRecord) => r.booking?.lastMileDeliveryAddress ?? "—"; const cargoDesc = (r: LastMileRecord) => { const parts = [r.booking?.cargoType?.cargoTypeName ?? r.booking?.cargoType?.label ?? r.booking?.cargoType?.name ?? r.booking?.cargoFreeText].filter(Boolean); if (r.booking?.cargoTotalWeightVgm) parts.push(`${r.booking.cargoTotalWeightVgm} t`); return parts.join(" · ") || "—"; }; const originYardName = (r: LastMileRecord) => r.booking?.originYard?.label ?? r.booking?.originYard?.name ?? "—"; const contactPersonName = (r: LastMileRecord) => r.booking?.company?.contactPersonName ?? "—"; const contactPhone = (r: LastMileRecord) => r.booking?.company?.contactPersonPhone ?? r.booking?.company?.phone ?? "—"; const requestedDate = (r: LastMileRecord) => { const d = r.booking?.scheduledDate; return d ? new Date(d).toISOString().slice(0, 10) : "—"; }; const serviceTypeName = (r: LastMileRecord) => r.booking?.serviceType?.label ?? r.booking?.serviceType?.name ?? "—"; const InfoRow = ({ label, value }: { label: string; value: string }) => ( {label} {value} ); const BookingInfo = ({ record }: { record: LastMileRecord }) => ( {bookingRef(record)} {STATUS_META[record.status].label} {isAssigned(record) ? "Assigned" : "Unassigned"} ); const tripSlipRows = (record: LastMileRecord): [string, string][] => [ ["Customer", customerName(record)], ["Service", serviceTypeName(record)], ["Pickup (origin yard)", originYardName(record)], ["Destination", deliveryLocation(record)], ["Cargo", cargoDesc(record)], ["Advanced Payment", formatPrice(record.advancedPayment)], ["Post Payment", formatPrice(record.remainingPayment)], ["Vehicle", vehicleLabel(record) ?? "Unassigned"], ["Contact", `${contactPersonName(record)} · ${contactPhone(record)}`], ["Requested date", requestedDate(record)], ["Est. Distance (KM)", record.estimatedKm != null ? String(record.estimatedKm) : "—"], ["Actual Distance (KM)", record.exactKm != null ? String(record.exactKm) : "—"], ["Status", STATUS_META[record.status].label], ]; const SampleStamp = () => ( EDR FREIGHT APPROVED OPERATIONS ); const SignatureBlock = ({ title, stamp }: { title: string; stamp?: ReactNode }) => ( {title} Name: Signature: {stamp && ( {stamp} )} ); const TripSlipDocument = ({ record }: { record: LastMileRecord }) => ( EDR Freight Last Mile Trip Slip {bookingRef(record)} {requestedDate(record)} {tripSlipRows(record).map(([label, value]) => ( ))} } /> ); const escapeHtml = (v: string) => v.replace(/&/g, "&").replace(//g, ">"); const buildTripSlipHtml = (record: LastMileRecord) => { const rows = tripSlipRows(record) .map(([l, v]) => `${escapeHtml(l)}${escapeHtml(v)}`) .join(""); const sig = (title: string, withStamp: boolean) => `
${title}
Name:
Signature:
${withStamp ? '
EDR FREIGHTAPPROVEDOPERATIONS
' : ""}
`; return ` Trip Slip ${escapeHtml(bookingRef(record))}

EDR Freight

Last Mile Trip Slip

${escapeHtml(bookingRef(record))}${escapeHtml(requestedDate(record))}
${rows}
Acknowledgement
${sig("Driver", false)}${sig("Operator", true)}
`; }; const LastMilePage = () => { const { toast } = useToast(); const qc = useQueryClient(); const { pagination, setPagination } = usePagination({ pageSize: 10 }); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("ALL"); const [filterPostPaymentPending, setFilterPostPaymentPending] = useState(false); const [rowSelection, setRowSelection] = useState>({}); const [assignOpen, setAssignOpen] = useState(false); const [bulkMode, setBulkMode] = useState(false); const [detailOpen, setDetailOpen] = useState(false); const [tripSlipOpen, setTripSlipOpen] = useState(false); const [tripSlipRecord, setTripSlipRecord] = useState(null); const [activeId, setActiveId] = useState(null); const [vehicleValue, setVehicleValue] = useState(null); // 2-step "Assign Mile" accept modal (arrival queue → vehicle) const [acceptOpen, setAcceptOpen] = useState(false); const [acceptStep, setAcceptStep] = useState<1 | 2>(1); const [selectedArrivalItems, setSelectedArrivalItems] = useState([]); const [acceptVehicleValue, setAcceptVehicleValue] = useState(null); const [arrivalSearch, setArrivalSearch] = useState(""); const [distanceOpen, setDistanceOpen] = useState(false); const [distanceValue, setDistanceValue] = useState(""); const [invoiceOpen, setInvoiceOpen] = useState(false); const [invoiceRecord, setInvoiceRecord] = useState(null); const { data: listData, isLoading } = useQuery({ queryKey: QUERY_KEYS.LAST_MILE.list(), queryFn: async () => { const res = await lastMileService.list(); return res.data; }, }); const { data: vehiclesData } = useQuery({ queryKey: ["vehicles", "list"], queryFn: async () => { const res = await vehiclesService.getAll({ status: "ACTIVE" }); return res.data; }, }); const { data: ratesData } = useQuery({ queryKey: ["rates", "LAST_MILE"], queryFn: async () => { const res = await ratesService.getByType("LAST_MILE"); return res.data; }, }); const records = listData?.data ?? []; const vehicleOptions = useMemo( () => (Array.isArray(vehiclesData) ? vehiclesData : []).map((v) => { const parts = [`${v.manufacturer} ${v.model}`, v.plateNumber]; if (v.code) parts.unshift(v.code); const plates = [v.powerPlateNo, v.trailerPlateNo].filter(Boolean).join(" / "); if (plates) parts.push(plates); return { value: v.id, label: parts.join(" · ") }; }), [vehiclesData], ); const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: string; data: { status?: LastMileApiStatus; vehicleId?: string | null } }) => lastMileService.update(id, data), onSuccess: () => { void qc.invalidateQueries({ queryKey: QUERY_KEYS.LAST_MILE.ROOT }); }, onError: () => { toast({ title: "Update failed", variant: "destructive" }); }, }); const updateDistanceMutation = useMutation({ mutationFn: ({ id, exactKm, remainingPayment }: { id: string; exactKm: number; remainingPayment?: number }) => lastMileService.update(id, { exactKm, ...(remainingPayment != null && { remainingPayment }) }), onSuccess: () => { void qc.invalidateQueries({ queryKey: QUERY_KEYS.LAST_MILE.list() }); if (activeRecord) { toast({ title: "Distance updated", description: `${bookingRef(activeRecord)} → ${distanceValue} km` }); } closeDistance(); }, onError: () => { toast({ title: "Update failed", variant: "destructive" }); }, }); const { data: arrivalQueueData, isLoading: arrivalLoading } = useQuery({ queryKey: ["warehouse-inventory", "arrival-queue"], queryFn: () => warehouseService.arrivalQueue().then((r) => r.data), enabled: acceptOpen, }); const arrivalQueue = arrivalQueueData ?? []; const filteredArrivalQueue = useMemo(() => { const term = arrivalSearch.trim().toLowerCase(); if (!term) return arrivalQueue; return arrivalQueue.filter((item) => [item.bookingReference, item.customer, item.cargo, item.warehouse, item.yard] .join(" ") .toLowerCase() .includes(term), ); }, [arrivalQueue, arrivalSearch]); const acceptMutation = useMutation({ mutationFn: async ({ items, vehicleId }: { items: ArrivalQueueItem[]; vehicleId: string | null }) => { const created = await Promise.all( items.map((item) => lastMileService.accept(item.bookingReference).then((r) => r.data)), ); if (vehicleId) { await Promise.all(created.map((record) => lastMileService.update(record.id, { vehicleId }))); } return created; }, onSuccess: (created) => { void qc.invalidateQueries({ queryKey: QUERY_KEYS.LAST_MILE.ROOT }); toast({ title: "Last-mile leg created", description: `${created.length} ${created.length === 1 ? "delivery" : "deliveries"} accepted successfully.`, }); closeAccept(); }, onError: () => { toast({ title: "Accept failed", variant: "destructive" }); }, }); const openAccept = () => { setAcceptOpen(true); setAcceptStep(1); setSelectedArrivalItems([]); setAcceptVehicleValue(null); setArrivalSearch(""); }; const closeAccept = () => { setAcceptOpen(false); setAcceptStep(1); setSelectedArrivalItems([]); setAcceptVehicleValue(null); setArrivalSearch(""); }; const toggleArrivalItem = (item: ArrivalQueueItem) => { setSelectedArrivalItems((prev) => prev.some((i) => i.bookingId === item.bookingId) ? prev.filter((i) => i.bookingId !== item.bookingId) : [...prev, item], ); }; const handleAcceptConfirm = () => { if (!selectedArrivalItems.length) return; acceptMutation.mutate({ items: selectedArrivalItems, vehicleId: acceptVehicleValue }); }; const openDistance = (id: string) => { setActiveId(id); setDistanceValue(""); setDistanceOpen(true); }; const closeDistance = () => { setDistanceOpen(false); setActiveId(null); setDistanceValue(""); }; const openInvoice = (record: LastMileRecord) => { setInvoiceRecord(record); setInvoiceOpen(true); }; const closeInvoice = () => { setInvoiceOpen(false); setInvoiceRecord(null); }; const handleSaveDistance = () => { const distance = parseFloat(distanceValue); if (!activeId || isNaN(distance) || distance < 0) { toast({ title: "Invalid distance", description: "Enter a valid distance value.", variant: "destructive" }); return; } let remainingPayment: number | undefined; if (ratesData?.data) { const lastMileRate = ratesData.data.find( (r) => r.rateType === "LAST_MILE" && (r.status === "LIVE" || r.status === "DRAFT") ); if (lastMileRate) { const rateValue = parseFloat(lastMileRate.rateValue); remainingPayment = distance * rateValue; } } updateDistanceMutation.mutate({ id: activeId, exactKm: distance, remainingPayment }); }; const activeRecord = useMemo( () => records.find((r) => r.id === activeId) ?? null, [records, activeId], ); const selectedIds = useMemo( () => Object.keys(rowSelection).filter((id) => rowSelection[id]), [rowSelection], ); const matchesFilter = (r: LastMileRecord) => { if (filterPostPaymentPending && r.isPostPaymentCompleted) return false; switch (statusFilter) { case "ALL": return true; case "ASSIGNED": return isAssigned(r); case "UNASSIGNED": return !isAssigned(r); default: return r.status === statusFilter; } }; const statusCounts = useMemo(() => { const counts: Record = { ALL: records.length, PAYMENT_PENDING: 0, READY_TO_TRANSIT: 0, IN_TRANSIT: 0, DELIVERED: 0, ASSIGNED: 0, UNASSIGNED: 0, }; for (const r of records) { counts[r.status] = (counts[r.status] ?? 0) + 1; if (isAssigned(r)) counts.ASSIGNED += 1; else counts.UNASSIGNED += 1; } return counts; }, [records]); const filteredRecords = useMemo(() => { const term = search.trim().toLowerCase(); return records.filter((r) => { if (!matchesFilter(r)) return false; if (!term) return true; return [bookingRef(r), customerName(r), deliveryLocation(r), cargoDesc(r)] .join(" ") .toLowerCase() .includes(term); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [records, search, statusFilter, filterPostPaymentPending]); const pageCount = Math.max(1, Math.ceil(filteredRecords.length / pagination.pageSize)); const pagedRecords = useMemo(() => { const start = pagination.pageIndex * pagination.pageSize; return filteredRecords.slice(start, start + pagination.pageSize); }, [filteredRecords, pagination]); const openAssign = (id: string | null) => { const resolved = id ?? filteredRecords.find((r) => !isAssigned(r))?.id ?? null; setBulkMode(false); setActiveId(resolved); setVehicleValue(null); setAssignOpen(true); }; const openBulkAssign = () => { setBulkMode(true); setActiveId(null); setVehicleValue(null); setAssignOpen(true); }; const closeAssign = () => { setAssignOpen(false); setBulkMode(false); setActiveId(null); setVehicleValue(null); }; const handleAssign = () => { if (!vehicleValue) { toast({ title: "Select a vehicle", description: "Choose a vehicle to assign.", variant: "destructive" }); return; } const targetIds = bulkMode ? selectedIds : [activeId ?? filteredRecords.find((r) => !isAssigned(r))?.id].filter((id): id is string => Boolean(id)); if (!targetIds.length) return; const selectedLabel = vehicleOptions.find((o) => o.value === vehicleValue)?.label ?? vehicleValue; Promise.all(targetIds.map((id) => updateMutation.mutateAsync({ id, data: { vehicleId: vehicleValue } }))) .then(() => { toast({ title: "Vehicle assigned", description: bulkMode ? `${targetIds.length} deliveries → ${selectedLabel}` : selectedLabel, }); if (bulkMode) setRowSelection({}); closeAssign(); }) .catch(() => void 0); }; const handleAdvanceStatus = (record: LastMileRecord) => { const next = NEXT_STATUS[record.status]; if (!next) return; updateMutation.mutate( { id: record.id, data: { status: next } }, { onSuccess: () => toast({ title: "Status updated", description: `${bookingRef(record)} → ${STATUS_META[next].label}` }), }, ); }; const handlePrintTripSlip = (record: LastMileRecord) => { setTripSlipRecord(record); setTripSlipOpen(true); }; const printTripSlip = () => { if (!tripSlipRecord) return; const win = window.open("", "_blank", "width=820,height=920"); if (!win) { toast({ title: "Pop-up blocked", description: "Allow pop-ups to print the trip slip.", variant: "destructive" }); return; } win.document.write(buildTripSlipHtml(tripSlipRecord)); win.document.close(); }; const columns = useMemo((): ColumnDef[] => { const headerClassName = ruleEngineTable.headerCell; const cellClassName = ruleEngineTable.bodyCell; return [ { id: "select", size: 40, meta: { headerClassName, cellClassName }, header: ({ table }) => ( table.toggleAllPageRowsSelected(e.currentTarget.checked)} /> ), cell: ({ row }) => ( ), }, { id: "bookingRef", header: "Booking", meta: { headerClassName, cellClassName }, cell: ({ row }) => {bookingRef(row.original)}, }, { id: "customer", header: "Customer", meta: { headerClassName, cellClassName }, cell: ({ row }) => customerName(row.original), }, { id: "pickup", header: "Pickup", meta: { headerClassName, cellClassName }, cell: ({ row }) => originYardName(row.original), }, { id: "destination", header: "Destination", meta: { headerClassName, cellClassName }, cell: ({ row }) => deliveryLocation(row.original), }, { id: "cargo", header: "Cargo", meta: { headerClassName, cellClassName }, cell: ({ row }) => cargoDesc(row.original), }, { id: "advancedPayment", header: "Advanced Payment", meta: { headerClassName, cellClassName }, cell: ({ row }) => formatPrice(row.original.advancedPayment), }, { id: "postPayment", header: "Post Payment", meta: { headerClassName, cellClassName }, cell: ({ row }) => formatPrice(row.original.remainingPayment), }, { id: "vehicle", header: "Vehicle", meta: { headerClassName, cellClassName }, cell: ({ row }) => vehicleLabel(row.original) ?? , }, { id: "estimatedKm", header: "Est. Distance (KM)", meta: { headerClassName, cellClassName }, cell: ({ row }) => row.original.estimatedKm != null ? row.original.estimatedKm : , }, { id: "exactKm", header: "Actual Distance (KM)", meta: { headerClassName, cellClassName }, cell: ({ row }) => row.original.exactKm != null ? row.original.exactKm : , }, { id: "invoice", header: "Invoice", meta: { headerClassName, cellClassName }, cell: ({ row }) => { const hasDistance = row.original.exactKm != null && row.original.exactKm > 0; if (!hasDistance) { return ; } return ( openInvoice(row.original)} c="blue" fw={500} style={{ textDecoration: "underline", cursor: "pointer" }} > #345 ); }, }, { id: "status", header: "Status", meta: { headerClassName, cellClassName }, cell: ({ row }) => { const meta = STATUS_META[row.original.status]; return {meta.label}; }, }, { id: "assignment", header: "Assignment", meta: { headerClassName, cellClassName }, cell: ({ row }) => ( {isAssigned(row.original) ? "Assigned" : "Unassigned"} ), }, { id: "actions", header: "Actions", meta: { headerClassName, cellClassName: `${cellClassName} whitespace-nowrap` }, cell: ({ row }) => { const assigned = isAssigned(row.original); const nextStatus = NEXT_STATUS[row.original.status]; const canPrint = row.original.status !== "PAYMENT_PENDING"; return ( } disabled={!nextStatus} onClick={() => handleAdvanceStatus(row.original)} > {nextStatus ? `Mark ${STATUS_META[nextStatus].label}` : STATUS_META[row.original.status].label} } disabled={assigned} onClick={() => openAssign(row.original.id)} > Assign } disabled={!assigned} onClick={() => openAssign(row.original.id)} > Reassign } onClick={() => { setActiveId(row.original.id); setDetailOpen(true); }} > View detail } onClick={() => openDistance(row.original.id)} > Add distance {canPrint && ( } onClick={() => handlePrintTripSlip(row.original)} > Print trip slip )} ); }, }, ]; // eslint-disable-next-line react-hooks/exhaustive-deps }, [vehicleOptions]); return ( setSearch(e.currentTarget.value)} w={260} /> {selectedIds.length > 0 && ( )} {FILTER_OPTIONS.map((option) => { const active = statusFilter === option.value; return ( ); })} row.id, state: { pagination, rowSelection }, onPaginationChange: setPagination, onRowSelectionChange: setRowSelection, }} containerClassName="border-0 shadow-none bg-transparent" footer={({ table, pagination: fp }) => ( )} /> {/* 2-step Assign Mile (arrival queue → vehicle) */} {acceptStep === 1 ? "Select Arrivals" : "Assign Mile"} } size="xl" radius="lg" centered > {acceptStep === 1 ? ( setArrivalSearch(e.currentTarget.value)} /> {arrivalLoading ? ( Loading arrivals… ) : filteredArrivalQueue.length === 0 ? ( No arrivals in queue. ) : ( filteredArrivalQueue.map((item) => { const checked = selectedArrivalItems.some((i) => i.bookingId === item.bookingId); return ( toggleArrivalItem(item)} > toggleArrivalItem(item)} onClick={(e) => e.stopPropagation()} /> {item.bookingReference} {item.arrivalDate ? item.arrivalDate.slice(0, 10) : "—"} {item.customer ?? "—"} {item.cargo && {item.cargo}} {item.warehouse && · {item.warehouse}} {item.yard && · {item.yard}} ); }) )} {selectedArrivalItems.length > 0 ? `${selectedArrivalItems.length} selected` : "Select one or more arrivals"} ) : ( Selected arrivals ({selectedArrivalItems.length}) {selectedArrivalItems.map((item) => ( {item.bookingReference} {item.customer ?? "—"} {item.warehouse ?? item.yard ?? "—"} ))} {/* View detail modal */} { setDetailOpen(false); setActiveId(null); }} title={Delivery Detail} size="lg" radius="lg" centered > {activeRecord && } {/* Trip slip modal */} setTripSlipOpen(false)} title={Trip Slip} size="lg" radius="lg" centered > {tripSlipRecord && } {/* Add Actual Distance modal */} Add Actual Distance} size="md" radius="lg" centered > {activeRecord && ( {bookingRef(activeRecord)} Customer {customerName(activeRecord)} Est. Distance (KM) {activeRecord.estimatedKm ?? "—"} )} setDistanceValue(String(v ?? ""))} min={0} step={0.1} decimalScale={2} /> {/* Invoice modal */} Invoice #345} size="lg" radius="lg" centered > {invoiceRecord && ( <> EDR Freight Invoice #345 Post Payment {formatPrice(invoiceRecord.remainingPayment)} Advanced Payment {formatPrice(invoiceRecord.advancedPayment)} {(() => { const postPayment = parseFloat(String(invoiceRecord.remainingPayment)); const advancedPayment = parseFloat(String(invoiceRecord.advancedPayment)); const difference = postPayment - advancedPayment; if (difference > 0) { return ( Remaining to Pay {formatPrice(difference)} ); } else if (difference < 0) { return ( Refund {formatPrice(Math.abs(difference))} ); } else { return ( Status Settled ); } })()} )} ); }; export default LastMilePage;