import { Fragment, useEffect, useMemo, useState, type MouseEvent } from 'react'; import { ActionIcon, Alert, Badge, Button, Checkbox, Group, Loader, Menu, Modal, NumberInput, ScrollArea, Select, Stack, Table, Tabs, Text, Textarea, TextInput, Tooltip, } from '@mantine/core'; import { ArrowRightLeft, ChevronDown, ChevronRight, ClipboardCheck, Eye, FileText, History, Info, MapPin, MoreHorizontal, PackageCheck, PackageOpen, PackageSearch, Send, Search, Train, Truck, } from 'lucide-react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { api } from '@/services/api'; import { QUERY_KEYS } from '@/constants/QUERY_KEYS'; import { useToast } from '@/hooks/use-toast'; import { useAllWarehouseYards, useAllWarehouseZones, useInventoryInquiry } from '@/hooks/useWarehouses'; import { firstMileService } from '@/services/first-mile.service'; import { warehouseService } from '@/services/warehouse.service'; import type { EligibleBooking, InventoryInquiryFilter, InventoryInquiryResult, ImportTrain, ImportTrainItem, ImportUnloadedItem, ReadyToLoadRow, ReceiveInventoryPayload, TruckEntrancePayload, Warehouse, WarehouseInventoryItem, WarehouseYard, WarehouseZone, } from '@/types/warehouse'; import { BookingSelect } from './BookingSelect'; import { DeliverInventoryModal } from './DeliverInventoryModal'; import { ContainerItemsModal } from './ContainerItemsModal'; import { FeePreviewModal } from './FeePreviewModal'; import { InspectionReportModal } from './InspectionReportModal'; import { InventoryDetailModal } from './InventoryDetailModal'; import { InventoryHistoryModal } from './InventoryHistoryModal'; import { InventoryWorkbench } from './InventoryWorkbench'; import { InventoryInquiryDetailModal } from './InventoryInquiryDetailModal'; import { MoveInventoryModal } from './MoveInventoryModal'; import { ReleaseOrderModal } from './ReleaseOrderModal'; import { StoreInventoryModal } from './StoreInventoryModal'; import { WarehouseInquiryTable } from './WarehouseInquiryTable'; import { extractErrorMessage, formatDate, formatNumber, inventoryStatusOptions } from './options'; import { openPdfBlob } from './pdf'; import '@/components/overview/overview.css'; type ImportUnloadAssignment = { bookingId: string; warehouseId: string; yardId: string; zoneId: string }; type ImportUnloadAssignmentDraft = Partial>; interface ReceiveInventoryModalProps { opened: boolean; onClose: () => void; /** When supplied the modal locks to a single booking (legacy single-receive). */ bookingId?: string; bookingLabel?: string; mode?: 'single' | 'bulk'; direction?: WarehouseFlowDirection; onReceived?: () => void; } function GrnDocumentButton({ inventoryId, grnNumber }: { inventoryId: string; grnNumber?: string | null }) { const { toast } = useToast(); const [loading, setLoading] = useState(false); const openDocument = async (event: MouseEvent) => { event.stopPropagation(); if (!grnNumber) { toast({ variant: 'destructive', title: 'GRN document unavailable', description: 'This item has no GRN number yet.' }); return; } setLoading(true); const pdfWindow = window.open('', '_blank'); try { const response = await warehouseService.downloadGrnDocument(inventoryId); const opened = openPdfBlob(response.data, `grn-${grnNumber}.pdf`, pdfWindow); toast({ title: opened ? 'GRN document opened' : 'GRN document downloaded' }); } catch (error) { pdfWindow?.close(); toast({ variant: 'destructive', title: 'GRN document failed', description: extractErrorMessage(error) }); } finally { setLoading(false); } }; return ( ); } interface Location { warehouseId: string; yardId: string; zoneId: string; } interface TruckEntranceFormState { ownerName: string; consigneeDetails: string; edrDigitalBookingId: string; tin: string; customerPhone: string; truckPlateNumber: string; trailerPlateNumber: string; assignedEquipmentNumber: string; customsSealNumber: string; declarationNumber: string; incoterms: string; hsCodes: string; itemCode: string; itemDescription: string; packagingType: string; unitCount: number | ''; grossWeightKg: number | ''; weighingRequired: boolean | null; netWeightKg: number | ''; volumeDimensions: string; conditionAtReceipt: string; damagedRejectedQuantity: number | ''; warehouseCodeLocation: string; driverName: string; driverPhone: string; driverLicenseNumber: string; truckType: string; entranceTareWeightKg: number | ''; exitTareWeightKg: number | ''; driverSignatoryName: string; warehouseManagerName: string; } interface LockedTruckEntranceFields { ownerName?: boolean; consigneeDetails?: boolean; tin?: boolean; edrDigitalBookingId?: boolean; customerPhone?: boolean; truckPlateNumber?: boolean; trailerPlateNumber?: boolean; assignedEquipmentNumber?: boolean; itemDescription?: boolean; packagingType?: boolean; unitCount?: boolean; grossWeightKg?: boolean; driverName?: boolean; driverPhone?: boolean; driverLicenseNumber?: boolean; truckType?: boolean; } type PackagingFreightType = 'CONTAINER' | 'BULK' | 'MIXED'; const emptyTruckEntrance = (): TruckEntranceFormState => ({ ownerName: '', consigneeDetails: '', edrDigitalBookingId: '', tin: '', customerPhone: '', truckPlateNumber: '', trailerPlateNumber: '', assignedEquipmentNumber: '', customsSealNumber: '', declarationNumber: '', incoterms: '', hsCodes: '', itemCode: '', itemDescription: '', packagingType: '', unitCount: '', grossWeightKg: '', weighingRequired: null, netWeightKg: '', volumeDimensions: '', conditionAtReceipt: '', damagedRejectedQuantity: '', warehouseCodeLocation: '', driverName: '', driverPhone: '', driverLicenseNumber: '', truckType: '', entranceTareWeightKg: '', exitTareWeightKg: '', driverSignatoryName: '', warehouseManagerName: '', }); const toTruckEntrancePayload = (form: TruckEntranceFormState): TruckEntrancePayload => ({ ownerName: form.ownerName.trim() || undefined, consigneeDetails: form.consigneeDetails.trim() || undefined, edrDigitalBookingId: form.edrDigitalBookingId.trim() || undefined, tin: form.tin.trim() || undefined, customerPhone: form.customerPhone.trim() || undefined, truckPlateNumber: form.truckPlateNumber.trim(), trailerPlateNumber: form.trailerPlateNumber.trim() || undefined, assignedEquipmentNumber: form.assignedEquipmentNumber.trim() || undefined, customsSealNumber: form.customsSealNumber.trim() || undefined, declarationNumber: form.declarationNumber.trim() || undefined, incoterms: form.incoterms.trim() || undefined, hsCodes: form.hsCodes.trim() || undefined, itemCode: form.itemCode.trim() || undefined, itemDescription: form.itemDescription.trim() || undefined, packagingType: form.packagingType.trim() || undefined, unitCount: form.unitCount === '' ? undefined : Number(form.unitCount), weighingRequired: form.weighingRequired ?? undefined, grossWeightKg: form.weighingRequired && form.grossWeightKg !== '' ? Number(form.grossWeightKg) : undefined, netWeightKg: form.netWeightKg === '' ? undefined : Number(form.netWeightKg), volumeDimensions: form.volumeDimensions.trim() || undefined, conditionAtReceipt: form.conditionAtReceipt.trim() || undefined, damagedRejectedQuantity: form.damagedRejectedQuantity === '' ? undefined : Number(form.damagedRejectedQuantity), warehouseCodeLocation: form.warehouseCodeLocation.trim() || undefined, driverName: form.driverName.trim(), driverPhone: form.driverPhone.trim(), driverLicenseNumber: form.driverLicenseNumber.trim() || undefined, truckType: form.truckType.trim() || undefined, entranceTareWeightKg: form.entranceTareWeightKg === '' ? undefined : Number(form.entranceTareWeightKg), exitTareWeightKg: form.weighingRequired && form.exitTareWeightKg !== '' ? Number(form.exitTareWeightKg) : undefined, driverSignatoryName: form.driverSignatoryName.trim() || undefined, warehouseManagerName: form.warehouseManagerName.trim() || undefined, }); const commonNonEmptyValue = (values: Array) => { const unique = [...new Set(values.map((value) => value?.trim()).filter(Boolean))] as string[]; return unique.length === 1 ? unique[0] : ''; }; const truckEntranceFromBookings = (bookings: EligibleBooking[]): { form: TruckEntranceFormState; lockedFields: LockedTruckEntranceFields; packagingFreightType: PackagingFreightType; } => { const ownerName = commonNonEmptyValue(bookings.map((booking) => booking.customer)); const tin = commonNonEmptyValue(bookings.map((booking) => booking.customerTin)); const customerPhone = commonNonEmptyValue(bookings.map((booking) => booking.customerPhone)); const consigneeDetails = commonNonEmptyValue(bookings.map((booking) => booking.customer)); const assignedEquipmentNumber = commonNonEmptyValue( bookings.map((booking) => booking.customerTruckContainerNumber || booking.containerNumber), ); const itemDescription = commonNonEmptyValue(bookings.map((booking) => booking.cargoDescription ?? booking.cargo)); const packagingType = commonNonEmptyValue(bookings.map((booking) => booking.containerPackagingType)); const truckPlateNumber = commonNonEmptyValue( bookings.map((booking) => booking.firstMileTruckPlateNumber || booking.customerTruckPlateNumber), ); const trailerPlateNumber = commonNonEmptyValue(bookings.map((booking) => booking.firstMileTrailerPlateNumber)); const driverName = commonNonEmptyValue( bookings.map((booking) => booking.firstMileDriverName || booking.customerTruckDriverName), ); const driverPhone = commonNonEmptyValue(bookings.map((booking) => booking.firstMileDriverPhone)); const driverLicenseNumber = commonNonEmptyValue(bookings.map((booking) => booking.firstMileDriverLicenseNumber)); const truckType = commonNonEmptyValue( bookings.map((booking) => booking.firstMileTruckType || booking.customerTruckType), ); const edrDigitalBookingId = bookings.length === 1 ? bookings[0]?.reference ?? bookings[0]?.id ?? '' : commonNonEmptyValue(bookings.map((booking) => booking.reference)); const unitCount = bookings.length === 1 && bookings[0]?.containerQuantity != null ? Number(bookings[0].containerQuantity) : ''; const freightTypes = [...new Set(bookings.map((booking) => booking.freightType).filter(Boolean))]; const packagingFreightType = freightTypes.length === 1 && freightTypes[0] === 'CONTAINER' ? 'CONTAINER' : freightTypes.length === 1 && freightTypes[0] === 'BULK' ? 'BULK' : 'MIXED'; return { form: { ...emptyTruckEntrance(), ownerName, consigneeDetails, tin, customerPhone, edrDigitalBookingId, assignedEquipmentNumber, itemDescription, packagingType, unitCount, grossWeightKg: '', truckPlateNumber, trailerPlateNumber, driverName, driverPhone, driverLicenseNumber, truckType, }, lockedFields: { ownerName: Boolean(ownerName), consigneeDetails: Boolean(consigneeDetails), tin: Boolean(tin), edrDigitalBookingId: Boolean(edrDigitalBookingId), customerPhone: Boolean(customerPhone), assignedEquipmentNumber: Boolean(assignedEquipmentNumber), itemDescription: Boolean(itemDescription), packagingType: Boolean(packagingType), unitCount: unitCount !== '', grossWeightKg: false, truckPlateNumber: Boolean(truckPlateNumber), trailerPlateNumber: Boolean(trailerPlateNumber), driverName: Boolean(driverName), driverPhone: Boolean(driverPhone), driverLicenseNumber: Boolean(driverLicenseNumber), truckType: Boolean(truckType), }, packagingFreightType, }; }; const BULK_PACKAGING_TYPE_OPTIONS = [ { value: 'BAG', label: 'Bag' }, { value: 'SACK', label: 'Sack' }, { value: 'BALE', label: 'Bale' }, { value: 'CARTON', label: 'Carton' }, { value: 'CRATE', label: 'Crate' }, { value: 'DRUM', label: 'Drum' }, { value: 'BARREL', label: 'Barrel' }, { value: 'PALLET', label: 'Pallet' }, { value: 'LOOSE_BULK', label: 'Loose bulk' }, { value: 'OTHER', label: 'Other' }, ]; const CONTAINER_PACKAGING_TYPE_OPTIONS = [ { value: 'CONTAINER_20FT', label: '20 ft container' }, { value: 'CONTAINER_40FT', label: '40 ft container' }, { value: 'CONTAINER_45FT', label: '45 ft container' }, { value: 'REEFER_CONTAINER', label: 'Reefer container' }, { value: 'TANK_CONTAINER', label: 'Tank container' }, { value: 'FLAT_RACK_CONTAINER', label: 'Flat rack container' }, { value: 'OPEN_TOP_CONTAINER', label: 'Open top container' }, { value: 'OTHER_CONTAINER', label: 'Other container' }, ]; const packagingOptionsFor = (freightType: PackagingFreightType) => freightType === 'CONTAINER' ? CONTAINER_PACKAGING_TYPE_OPTIONS : freightType === 'BULK' ? BULK_PACKAGING_TYPE_OPTIONS : [...CONTAINER_PACKAGING_TYPE_OPTIONS, ...BULK_PACKAGING_TYPE_OPTIONS]; function TruckEntranceFields({ value, onChange, lockedFields, packagingFreightType = 'MIXED', allowTruckWeighing = true, }: { value: TruckEntranceFormState; onChange: (next: TruckEntranceFormState) => void; lockedFields?: LockedTruckEntranceFields; packagingFreightType?: PackagingFreightType; allowTruckWeighing?: boolean; }) { const packagingOptions = packagingOptionsFor(packagingFreightType); const quantityLabel = packagingFreightType === 'CONTAINER' ? 'Container quantity' : packagingFreightType === 'BULK' ? 'Unit count' : 'Quantity'; return ( Customer and cargo ownership onChange({ ...value, ownerName: e.currentTarget.value })} /> onChange({ ...value, consigneeDetails: e.currentTarget.value })} /> onChange({ ...value, edrDigitalBookingId: e.currentTarget.value })} /> onChange({ ...value, tin: e.currentTarget.value })} /> onChange({ ...value, customerPhone: e.currentTarget.value })} /> Transport and equipment tracking onChange({ ...value, truckPlateNumber: e.currentTarget.value })} /> onChange({ ...value, trailerPlateNumber: e.currentTarget.value })} /> onChange({ ...value, assignedEquipmentNumber: e.currentTarget.value })} /> onChange({ ...value, customsSealNumber: e.currentTarget.value })} /> onChange({ ...value, driverName: e.currentTarget.value })} /> onChange({ ...value, driverPhone: e.currentTarget.value })} /> onChange({ ...value, driverLicenseNumber: e.currentTarget.value })} /> onChange({ ...value, truckType: e.currentTarget.value })} /> {allowTruckWeighing ? ( <> onChange({ ...value, packagingType: v ?? '' })} /> onChange({ ...value, unitCount: v === '' ? '' : Number(v) })} /> onChange({ ...value, netWeightKg: v === '' ? '' : Number(v) })} /> onChange({ ...value, volumeDimensions: e.currentTarget.value })} /> Quality and inspection onChange({ ...value, conditionAtReceipt: e.currentTarget.value })} /> onChange({ ...value, damagedRejectedQuantity: v === '' ? '' : Number(v) })} /> onChange({ ...value, warehouseCodeLocation: e.currentTarget.value })} /> onChange({ ...value, driverSignatoryName: e.currentTarget.value })} /> onChange({ ...value, warehouseManagerName: e.currentTarget.value })} /> ); } /** Cascading Warehouse → Yard → Zone selectors (ACTIVE only). */ function LocationSelects({ value, onChange, allowedYardTypes, allowedZoneTypes, }: { value: Location; onChange: (next: Location) => void; /** When non-empty, only yards of these types are offered (matched to freight). */ allowedYardTypes?: string[]; /** When non-empty, only zones of these types are offered. */ allowedZoneTypes?: string[]; }) { const warehousesQuery = useQuery( api.warehouses.list.queryOptions({ input: { filter: { status: 'ACTIVE' } } }), ); const yardsQuery = useQuery( api.warehouses.listYards.queryOptions({ input: { warehouseId: value.warehouseId ?? '' }, enabled: Boolean(value.warehouseId), }), ); const zonesQuery = useQuery( api.warehouses.listZones.queryOptions({ input: { yardId: value.yardId ?? '' }, enabled: Boolean(value.yardId), }), ); const warehouseOptions = useMemo( () => (warehousesQuery.data ?? []).map((w) => ({ value: w.id, label: `${w.name} (${w.code})` })), [warehousesQuery.data], ); const yardOptions = useMemo( () => (yardsQuery.data ?? []) .filter((y) => y.status === 'ACTIVE') .filter((y) => !allowedYardTypes?.length || allowedYardTypes.includes((y as { type?: string }).type ?? '')) .map((y) => ({ value: y.id, label: `${y.name} (${y.code})` })), [yardsQuery.data, allowedYardTypes], ); const zoneOptions = useMemo( () => (zonesQuery.data ?? []) .filter((z) => z.status === 'ACTIVE') .filter((z) => !allowedZoneTypes?.length || allowedZoneTypes.includes((z as { type?: string }).type ?? '')) .map((z) => ({ value: z.id, label: `${z.name} (${z.code})` })), [zonesQuery.data, allowedZoneTypes], ); return ( onChange({ ...value, yardId: v ?? '', zoneId: '' })} /> onAssignmentChange(it.bookingId, { warehouseId: value ?? undefined })} searchable disabled={!pending} w={210} /> onAssignmentChange(it.bookingId, { ...draft, zoneId: value ?? undefined })} searchable disabled={!pending || !draft.yardId} w={190} /> {it.inspectionStatus ?? 'Not inspected'} {it.currentStatus ?? '—'} {it.lastMileRequested ? 'Yes' : 'No'} {it.pickupOption} ); })} ); } /** Import Arrive Queue: arrived IMPORT trains, with Open (detail) + Auto Unload per train. */ const getPendingUnloadBookings = (train: ImportTrain) => train.pendingUnloadBookings ?? train.totalBookings; const isFullyUnloaded = (train: ImportTrain) => Boolean(train.fullyUnloaded) || (train.totalBookings > 0 && getPendingUnloadBookings(train) === 0); function ImportArriveQueueTab({ enabled, onChanged, }: { enabled: boolean; onChanged?: () => void; }) { const { toast } = useToast(); const { data: trains = [], isLoading } = useQuery( api.warehouses.importArriveQueue.queryOptions({ enabled }), ); const { data: warehouses = [], isLoading: warehousesLoading } = useQuery( api.warehouses.list.queryOptions({ input: { filter: { status: 'ACTIVE' } }, enabled }), ); const { data: yards = [] } = useAllWarehouseYards(); const { data: zones = [] } = useAllWarehouseZones(); const autoUnloadMutation = useMutation( api.warehouses.autoUnloadArrivedBookings.mutationOptions(), ); const [openId, setOpenId] = useState(null); const [busyId, setBusyId] = useState(null); const [assignmentsBySchedule, setAssignmentsBySchedule] = useState< Record> >({}); const [readyBySchedule, setReadyBySchedule] = useState>({}); const autoUnload = async (train: ImportTrain) => { const assignments = Object.entries(assignmentsBySchedule[train.scheduleId] ?? {}) .filter((entry): entry is [string, Required] => Boolean(entry[1].warehouseId && entry[1].yardId && entry[1].zoneId), ) .map(([bookingId, draft]) => ({ bookingId, warehouseId: draft.warehouseId, yardId: draft.yardId, zoneId: draft.zoneId, })); if (!readyBySchedule[train.scheduleId] || assignments.length === 0) { toast({ variant: 'destructive', title: 'Assign locations', description: 'Select warehouse, yard and zone for each pending booking before unloading.', }); return; } if (isFullyUnloaded(train)) { toast({ title: 'Already unloaded', description: `${train.trainNumber ?? 'This train'} has no remaining bookings to auto unload.`, }); return; } setBusyId(train.scheduleId); try { const r = await autoUnloadMutation.mutateAsync({ scheduleId: train.scheduleId, assignments }); const alreadyUnloaded = r.unloadedCount === 0 && r.skippedCount > 0 && r.failedCount === 0; const firstReason = r.results.find((item) => item.reason)?.reason; const extra = [ r.skippedCount ? `${r.skippedCount} skipped` : '', r.failedCount ? `${r.failedCount} failed` : '', ] .filter(Boolean) .join(', '); toast({ title: alreadyUnloaded ? 'Already unloaded' : `${r.unloadedCount} unloaded`, description: alreadyUnloaded ? firstReason ?? 'This train is already in warehouse inventory.' : extra || undefined, }); onChanged?.(); } catch (error) { toast({ variant: 'destructive', title: 'Auto unload failed', description: extractErrorMessage(error) }); } finally { setBusyId(null); } }; return ( {trains.length} arrived import train{trains.length !== 1 ? 's' : ''} {isLoading ? ( ) : trains.length === 0 ? ( No arrived import trains. Trains appear here once their schedule status is ARRIVED. ) : ( Schedule ID Train # Route Origin Destination Arrival Time Bookings Containers Cargoes Status Actions {trains.map((t: ImportTrain) => { const isOpen = openId === t.scheduleId; const fullyUnloaded = isFullyUnloaded(t); const unloadedBookings = t.unloadedBookings ?? t.totalBookings - getPendingUnloadBookings(t); return ( {t.scheduleId.slice(0, 8)}… {t.trainNumber ?? '—'} {t.route ?? '—'} {t.origin ?? '—'} {t.destination ?? '—'} {formatDate(t.arrivalTime)} {t.totalBookings} {t.totalContainers} {t.totalCargoes} {t.status} {Math.max(unloadedBookings, 0)}/{t.totalBookings} unloaded {isOpen && ( setAssignmentsBySchedule((current) => ({ ...current, [t.scheduleId]: { ...(current[t.scheduleId] ?? {}), [bookingId]: draft.warehouseId ? draft : {}, }, })) } onReadyChange={(ready) => setReadyBySchedule((current) => ({ ...current, [t.scheduleId]: ready })) } /> )} ); })}
)}
); } /** * Import Unloaded Queue (Batch 9): all unloaded import items with the destination-inspection columns. * Multi-select + Mark Selected as Inspected (import passed → READY_FOR_PICKUP), and the per-item * Inspect / Report action stays for damage / images / weight-loss detail. */ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) { const { toast } = useToast(); const qc = useQueryClient(); const { data: rows = [], isLoading } = useQuery( api.warehouses.importUnloadedQueue.queryOptions({ enabled }), ); const inspectMutation = useMutation( api.warehouses.bulkMarkInspected.mutationOptions(), ); const readyMutation = useMutation(api.warehouses.markReadyForPickup.mutationOptions()); const [selected, setSelected] = useState>(new Set()); const [inspectId, setInspectId] = useState(null); const [busyId, setBusyId] = useState(null); const [viewItem, setViewItem] = useState(null); const [historyItem, setHistoryItem] = useState(null); const [feeItem, setFeeItem] = useState(null); const [releaseItem, setReleaseItem] = useState(null); const [deliverItem, setDeliverItem] = useState(null); const [containerItemsItem, setContainerItemsItem] = useState(null); const [storeItem, setStoreItem] = useState(null); const [moveItem, setMoveItem] = useState(null); const allSelected = rows.length > 0 && selected.size === rows.length; const someSelected = selected.size > 0 && !allSelected; const selectAll = () => setSelected(new Set(rows.map((r) => r.id))); const unselectAll = () => setSelected(new Set()); const toggleOne = (id: string) => setSelected((prev) => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next; }); const markInspected = async () => { if (selected.size === 0) { toast({ variant: 'destructive', title: 'Select at least one item' }); return; } try { const r = await inspectMutation.mutateAsync({ inventoryIds: [...selected] }); toast({ title: `${r.inspectedCount} marked inspected`, description: r.skippedCount ? `${r.skippedCount} skipped` : undefined, }); setSelected(new Set()); void qc.invalidateQueries({ queryKey: ['warehouse-inventory'] }); } catch (error) { toast({ variant: 'destructive', title: 'Mark inspected failed', description: extractErrorMessage(error) }); } }; const toInventoryItem = (row: ImportUnloadedItem): WarehouseInventoryItem => ({ id: row.id, bookingId: row.bookingId, quantity: 1, weight: Number(row.weight) || 0, grnNumber: row.grnNumber, status: row.currentStatus, arrivedAt: row.arrivalTime, unloadedAt: row.arrivalTime, inspectionStatus: row.inspectionStatus, releaseDate: row.releaseDate, releaseOrderReference: row.releaseOrderReference, handoverDocumentReference: row.handoverDocumentReference, handoverDocumentDate: row.handoverDocumentDate, deliveredAt: row.deliveredAt, booking: row.bookingId ? { id: row.bookingId, reference: row.bookingReference ?? row.bookingId, tradeDirection: 'IMPORT', lastMileDeliveryAddress: row.lastMileRequested ? row.pickupOption : null, customerTruckPlateNumber: row.customerTruckPlateNumber, customerTruckDriverName: row.customerTruckDriverName, customerTruckType: row.customerTruckType, customerTruckContainerNumber: row.customerTruckContainerNumber, customerTruckAssignedAt: row.customerTruckAssignedAt, } : null, }) as unknown as WarehouseInventoryItem; const runRowAction = async (row: ImportUnloadedItem, label: string, fn: () => Promise) => { setBusyId(row.id); try { await fn(); toast({ title: label }); void qc.invalidateQueries({ queryKey: ['warehouse-inventory'] }); } catch (error) { toast({ variant: 'destructive', title: 'Action failed', description: extractErrorMessage(error) }); } finally { setBusyId(null); } }; const openHandoverDocument = async (row: ImportUnloadedItem) => { setBusyId(row.id); const pdfWindow = window.open('', '_blank'); try { const response = await warehouseService.downloadHandoverDocument(row.id); openPdfBlob(response.data, `handover-${row.bookingReference ?? row.id}.pdf`, pdfWindow); void qc.invalidateQueries({ queryKey: ['warehouse-inventory'] }); } catch (error) { pdfWindow?.close(); toast({ variant: 'destructive', title: 'Handover document failed', description: extractErrorMessage(error) }); } finally { setBusyId(null); } }; const openReleaseDocument = async (row: ImportUnloadedItem) => { setBusyId(row.id); const pdfWindow = window.open('', '_blank'); try { const response = await warehouseService.downloadReleaseDocument(row.id); openPdfBlob(response.data, `release-${row.bookingReference ?? row.id}.pdf`, pdfWindow); } catch (error) { pdfWindow?.close(); toast({ variant: 'destructive', title: 'Exit paper failed', description: extractErrorMessage(error) }); } finally { setBusyId(null); } }; return ( Selected: {selected.size} / {rows.length} unloaded {isLoading ? ( ) : rows.length === 0 ? ( No unloaded import items. Items appear here after Auto Unload on an arrived train. ) : ( (allSelected ? unselectAll() : selectAll())} /> Booking ID Booking Ref GRN Customer ID Customer Name Arrival Time Container # Cargo Type Weight Train Schedule Inspection Pickup Option Last Mile Current Status Actions {rows.map((r: ImportUnloadedItem) => ( toggleOne(r.id)} /> {r.bookingId ? `${r.bookingId.slice(0, 8)}…` : '—'} {r.bookingReference ?? '—'} {r.customerId ? `${r.customerId.slice(0, 8)}…` : '—'} {r.customerName ?? '—'} {formatDate(r.arrivalTime)} {r.containerNumber ?? '—'} {r.cargoType ?? '—'} {formatNumber(Number(r.weight))} {r.trainSchedule ?? '—'} {r.inspectionStatus ?? 'Not inspected'} {r.pickupOption} {r.lastMileRequested ? 'Yes' : 'No'} {r.currentStatus} setContainerItemsItem(toInventoryItem(r))}> {/* Primary stage action stays visible; the rest live under the kebab. */} {r.currentStatus === 'READY_FOR_PICKUP' && !r.releaseDate && r.hasAssignedTruck && ( )} {r.currentStatus === 'READY_FOR_PICKUP' && r.releaseDate && ( )} {r.currentStatus === 'UNLOADED' && ( } onClick={() => setStoreItem(toInventoryItem(r))}> Store… )} {r.currentStatus !== 'UNLOADED' && ( } onClick={() => setMoveItem(toInventoryItem(r))}> Move… )} {['UNLOADED', 'STORED'].includes(r.currentStatus) && r.inspectionStatus === 'PASSED' && ( runRowAction(r, 'Ready for pickup', () => readyMutation.mutateAsync(r.id))}> Ready for pickup )} {r.currentStatus === 'READY_FOR_PICKUP' && !r.releaseDate && ( } disabled={!r.hasAssignedTruck} onClick={() => setReleaseItem(toInventoryItem(r))} > {r.hasAssignedTruck ? r.releaseOrderReference ? 'Truck leaving' : 'Truck arrival' : 'Truck arrival — assign a truck first'} )} {r.currentStatus === 'READY_FOR_PICKUP' && r.releaseDate && ( } onClick={() => openReleaseDocument(r)}> Exit paper )} {r.currentStatus === 'READY_FOR_PICKUP' && r.releaseDate && ( setDeliverItem(toInventoryItem(r))}>Deliver )} {r.inspectionStatus === 'PASSED' && ( } onClick={() => openHandoverDocument(r)}> {r.handoverDocumentReference ? 'View handover' : 'Handover'} )} setInspectId(r.id)}>Inspect / report } onClick={() => setFeeItem(toInventoryItem(r))}> Storage / fee preview } onClick={() => setHistoryItem(toInventoryItem(r))}> History ))}
)} setInspectId(null)} inventoryId={inspectId} /> setViewItem(null)} item={viewItem} /> setHistoryItem(null)} item={historyItem} /> setFeeItem(null)} inventoryId={feeItem?.id ?? null} /> setReleaseItem(null)} item={releaseItem} /> setStoreItem(null)} item={storeItem} /> setMoveItem(null)} item={moveItem} /> setDeliverItem(null)} item={deliverItem} /> setContainerItemsItem(null)} bookingId={containerItemsItem?.booking?.id ?? null} bookingReference={containerItemsItem?.booking?.reference ?? null} />
); } /** * Import Dispatch Queue (Batch 10): inspected import items that are PICKUP_READY (READY_FOR_PICKUP), * awaiting Customer Pickup (release → deliver), Store, or Dispatch. Reuses InventoryWorkbench so all * existing actions + modals stay intact; Last Mile shows only when the booking requested door delivery. * Nothing is stored automatically — Store is an explicit operator action. */ function ImportDispatchQueueTab({ enabled }: { enabled: boolean }) { const { toast } = useToast(); const { data: items = [], isLoading } = useQuery( api.warehouses.listInventory.queryOptions({ input: { filter: enabled ? { status: 'READY_FOR_PICKUP' } : undefined }, }), ); return ( {items.length} pickup-ready item{items.length !== 1 ? 's' : ''} toast({ title: 'Last mile delivery', description: `Door delivery for ${it.booking?.reference ?? it.id} — handled in the next batch.`, }) } /> ); } type WarehouseFlowDirection = 'IMPORT' | 'EXPORT' | 'BOTH'; type ImportWarehouseTab = 'arrive-queue' | 'unloaded-queue' | 'dispatch-queue' | 'locate-booking'; type ExportWarehouseTab = 'receive-queue' | 'received' | 'ready-to-load' | 'loaded' | 'dispatch-queue' | 'locate-booking'; interface WarehouseQueueTab { value: TValue; label: string; icon: React.ReactNode; count?: number; } interface WarehouseFlowWorkbenchProps { direction?: WarehouseFlowDirection; enabled?: boolean; onChanged?: () => void; focusedBookingId?: string; focusedBookingLabel?: string; } function WarehouseQueueTabs({ value, onChange, tabs, }: { value: TValue; onChange: (value: TValue) => void; tabs: WarehouseQueueTab[]; }) { return ( onChange((next as TValue) ?? value)} variant="pills" color="edr-green" keepMounted={false} classNames={{ list: 'ov-tablist', tab: 'ov-tab' }} > {tabs.map((tab) => { const active = value === tab.value; return ( {tab.count} ) : undefined } > {tab.label} ); })} ); } function LocateBookingTab({ enabled }: { enabled: boolean }) { const [draft, setDraft] = useState({}); const [applied, setApplied] = useState({}); const [viewResult, setViewResult] = useState(null); const hasSearch = Boolean( applied.bookingReference || applied.containerNumber || applied.goodsName || applied.cargoType || applied.status, ); const { data: results = [], isFetching } = useInventoryInquiry(applied, enabled && hasSearch); const normalizeDraft = (): InventoryInquiryFilter => ({ bookingReference: draft.bookingReference?.trim() || undefined, containerNumber: draft.containerNumber?.trim() || undefined, goodsName: draft.goodsName?.trim() || undefined, cargoType: draft.cargoType?.trim() || undefined, status: draft.status, }); const runSearch = () => setApplied(normalizeDraft()); const reset = () => { setDraft({}); setApplied({}); }; return ( setDraft((filter) => ({ ...filter, bookingReference: e.currentTarget.value || undefined }))} onKeyDown={(e) => { if (e.key === 'Enter') runSearch(); }} w={230} /> setDraft((filter) => ({ ...filter, containerNumber: e.currentTarget.value || undefined }))} onKeyDown={(e) => { if (e.key === 'Enter') runSearch(); }} w={220} /> { const value = e.currentTarget.value || undefined; setDraft((filter) => ({ ...filter, goodsName: value, cargoType: value })); }} onKeyDown={(e) => { if (e.key === 'Enter') runSearch(); }} w={200} />