mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 23:35:42 +00:00
fix
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
ChevronRight,
|
||||
Eye,
|
||||
MoreHorizontal,
|
||||
PackageCheck,
|
||||
Printer,
|
||||
RefreshCw,
|
||||
Ruler,
|
||||
@@ -36,6 +37,7 @@ import {
|
||||
|
||||
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
|
||||
import { FirstMileContainerAllocationTable } from "@/components/FirstMileContainerAllocationTable";
|
||||
import { ReceiveInventoryModal } from "@/components/warehouses/ReceiveInventoryModal";
|
||||
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import {
|
||||
@@ -342,6 +344,8 @@ const FirstMilePage = () => {
|
||||
const [distanceValue, setDistanceValue] = useState("");
|
||||
const [invoiceOpen, setInvoiceOpen] = useState(false);
|
||||
const [invoiceRecord, setInvoiceRecord] = useState<FirstMileRecord | null>(null);
|
||||
const [warehouseReceiveOpen, setWarehouseReceiveOpen] = useState(false);
|
||||
const [warehouseReceiveRecord, setWarehouseReceiveRecord] = useState<FirstMileRecord | null>(null);
|
||||
|
||||
const [containerAllocationOpen, setContainerAllocationOpen] = useState(false);
|
||||
const [containerAllocationFirstMileId, setContainerAllocationFirstMileId] = useState<string | null>(null);
|
||||
@@ -550,6 +554,16 @@ const FirstMilePage = () => {
|
||||
setInvoiceRecord(null);
|
||||
};
|
||||
|
||||
const openWarehouseReceive = (record: FirstMileRecord) => {
|
||||
setWarehouseReceiveRecord(record);
|
||||
setWarehouseReceiveOpen(true);
|
||||
};
|
||||
|
||||
const closeWarehouseReceive = () => {
|
||||
setWarehouseReceiveOpen(false);
|
||||
setWarehouseReceiveRecord(null);
|
||||
};
|
||||
|
||||
const openContainerAllocation = (firstMileId: string) => {
|
||||
setContainerAllocationFirstMileId(firstMileId);
|
||||
setContainerAllocationOpen(true);
|
||||
@@ -853,6 +867,7 @@ const FirstMilePage = () => {
|
||||
const nextStatus = NEXT_STATUS[row.original.status];
|
||||
const canPrint = row.original.status !== "PAYMENT_PENDING";
|
||||
const isPaid = (row.original as any).paid;
|
||||
const canReceiveToWarehouse = row.original.status === "RECEIVED_TO_PORT";
|
||||
return (
|
||||
<Group gap={4} justify="flex-end" wrap="nowrap">
|
||||
<Menu position="bottom-end" width={200} withinPortal>
|
||||
@@ -891,6 +906,13 @@ const FirstMilePage = () => {
|
||||
>
|
||||
View detail
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<PackageCheck size={15} />}
|
||||
disabled={!canReceiveToWarehouse}
|
||||
onClick={() => openWarehouseReceive(row.original)}
|
||||
>
|
||||
Receive to warehouse
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<Ruler size={15} />}
|
||||
onClick={() => openDistance(row.original.id)}
|
||||
@@ -1101,6 +1123,19 @@ const FirstMilePage = () => {
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
<ReceiveInventoryModal
|
||||
opened={warehouseReceiveOpen}
|
||||
onClose={closeWarehouseReceive}
|
||||
mode="bulk"
|
||||
direction="EXPORT"
|
||||
bookingId={warehouseReceiveRecord?.bookingId}
|
||||
bookingLabel={warehouseReceiveRecord ? bookingRef(warehouseReceiveRecord) : undefined}
|
||||
onReceived={() => {
|
||||
void qc.invalidateQueries({ queryKey: QUERY_KEYS.FIRST_MILE.list() });
|
||||
closeWarehouseReceive();
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Accept Booking modal — step 1: booking list, step 2: details + vehicle */}
|
||||
<Modal
|
||||
opened={acceptOpen}
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
TextInput,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import type { ArrivalQueueItem } from "@/types/warehouse";
|
||||
import type { ArrivalQueueItem, ImportUnloadedItem, WarehouseInventoryItem } from "@/types/warehouse";
|
||||
import { warehouseService } from "@/services/warehouse.service";
|
||||
|
||||
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
|
||||
@@ -45,8 +45,10 @@ import {
|
||||
lastMileService,
|
||||
} from "@/services/last-mile.service";
|
||||
import { vehiclesService } from "@/services/vehicles.service";
|
||||
import { driversService, type Driver } from "@/services/drivers.service";
|
||||
import { ratesService } from "@/services/rates.service";
|
||||
import { LastMileContainerAllocationTable, type LastMileContainerRow } from "@/components/LastMileContainerAllocationTable";
|
||||
import { ReleaseOrderModal, type ReleaseOrderTruckPrefill } from "@/components/warehouses/ReleaseOrderModal";
|
||||
import { api } from "@/auth/http";
|
||||
|
||||
const formatPrice = (amount: number) =>
|
||||
@@ -111,6 +113,59 @@ const requestedDate = (r: LastMileRecord) => {
|
||||
const serviceTypeName = (r: LastMileRecord) =>
|
||||
r.booking?.serviceType?.label ?? r.booking?.serviceType?.name ?? "—";
|
||||
|
||||
const toReleaseInventoryItem = (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 releasePrefillFromLastMile = (
|
||||
record: LastMileRecord,
|
||||
row?: ImportUnloadedItem | null,
|
||||
driversById?: Map<string, Driver>,
|
||||
): ReleaseOrderTruckPrefill => {
|
||||
const vehicle = record.vehicle;
|
||||
const truckType = [vehicle?.manufacturer, vehicle?.model].filter(Boolean).join(" ").trim();
|
||||
const assignedDriver = vehicle?.assignedDriverId ? driversById?.get(vehicle.assignedDriverId) : undefined;
|
||||
const assignedDriverName = assignedDriver
|
||||
? `${assignedDriver.firstName ?? ""} ${assignedDriver.lastName ?? ""}`.trim()
|
||||
: "";
|
||||
return {
|
||||
truckPlateNumber: vehicle?.powerPlateNo || vehicle?.plateNumber || null,
|
||||
trailerPlateNumber: vehicle?.trailerPlateNo || null,
|
||||
driverName: vehicle?.assignedDriverName || assignedDriverName || null,
|
||||
driverLicense: assignedDriver?.licenseNumber || null,
|
||||
driverPhone: assignedDriver?.phoneNumber || null,
|
||||
truckType: vehicle?.vehicleType || truckType || null,
|
||||
containerNumber: row?.containerNumber ?? null,
|
||||
};
|
||||
};
|
||||
|
||||
const InfoRow = ({ label, value }: { label: string; value: string }) => (
|
||||
<Stack gap={2}>
|
||||
<Text size="xs" c="dimmed" tt="uppercase" fw={600}>{label}</Text>
|
||||
@@ -329,6 +384,8 @@ const LastMilePage = () => {
|
||||
|
||||
const [allocationOpen, setAllocationOpen] = useState(false);
|
||||
const [allocationContainers, setAllocationContainers] = useState<LastMileContainerRow[]>([]);
|
||||
const [releaseItem, setReleaseItem] = useState<WarehouseInventoryItem | null>(null);
|
||||
const [releaseTruckPrefill, setReleaseTruckPrefill] = useState<ReleaseOrderTruckPrefill | null>(null);
|
||||
|
||||
const { data: listData, isLoading } = useQuery({
|
||||
queryKey: QUERY_KEYS.LAST_MILE.list(),
|
||||
@@ -360,6 +417,27 @@ const LastMilePage = () => {
|
||||
[records],
|
||||
);
|
||||
|
||||
const needsDriverLookup = records.some((record) => record.vehicle?.assignedDriverId);
|
||||
|
||||
const { data: driversData } = useQuery({
|
||||
queryKey: ["drivers", "list", "ACTIVE"],
|
||||
queryFn: async () => {
|
||||
const res = await driversService.getAll({ status: "ACTIVE" });
|
||||
return res.data;
|
||||
},
|
||||
enabled: needsDriverLookup,
|
||||
});
|
||||
|
||||
const driversById = useMemo(
|
||||
() => new Map((driversData ?? []).map((driver) => [driver.id, driver])),
|
||||
[driversData],
|
||||
);
|
||||
|
||||
const { data: pickupReadyRows = [] } = useQuery({
|
||||
queryKey: ["warehouse-inventory", "import-pickup-ready-queue"],
|
||||
queryFn: () => warehouseService.importPickupReadyQueue().then((r) => r.data),
|
||||
});
|
||||
|
||||
const vehicleOptions = useMemo(
|
||||
() =>
|
||||
(Array.isArray(vehiclesData) ? vehiclesData : []).map((v) => {
|
||||
@@ -556,6 +634,15 @@ const LastMilePage = () => {
|
||||
[records, activeId],
|
||||
);
|
||||
|
||||
const pickupReadyByBooking = useMemo(() => {
|
||||
const map = new Map<string, ImportUnloadedItem>();
|
||||
for (const row of pickupReadyRows) {
|
||||
if (row.bookingId) map.set(row.bookingId, row);
|
||||
if (row.bookingReference) map.set(row.bookingReference, row);
|
||||
}
|
||||
return map;
|
||||
}, [pickupReadyRows]);
|
||||
|
||||
const selectedIds = useMemo(
|
||||
() => Object.keys(rowSelection).filter((id) => rowSelection[id]),
|
||||
[rowSelection],
|
||||
@@ -592,6 +679,7 @@ const LastMilePage = () => {
|
||||
const term = search.trim().toLowerCase();
|
||||
return records.filter((r) => {
|
||||
if (!matchesFilter(r)) return false;
|
||||
if (filterPostPaymentPending && !(r.remainingPayment > 0)) return false;
|
||||
if (!term) return true;
|
||||
return [bookingRef(r), customerName(r), deliveryLocation(r), cargoDesc(r)]
|
||||
.join(" ")
|
||||
@@ -672,6 +760,37 @@ const LastMilePage = () => {
|
||||
setTripSlipOpen(true);
|
||||
};
|
||||
|
||||
const openTruckArrival = (record: LastMileRecord) => {
|
||||
if (!isAssigned(record)) {
|
||||
toast({
|
||||
title: "Assign a truck first",
|
||||
description: "Truck arrival opens after a last-mile vehicle is assigned.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const row = pickupReadyByBooking.get(record.bookingId) ?? pickupReadyByBooking.get(bookingRef(record));
|
||||
if (!row) {
|
||||
toast({
|
||||
title: "Import inventory is not pickup-ready",
|
||||
description: `${bookingRef(record)} must be unloaded and pass inspection before truck arrival.`,
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setReleaseTruckPrefill(releasePrefillFromLastMile(record, row, driversById));
|
||||
setReleaseItem(toReleaseInventoryItem(row));
|
||||
};
|
||||
|
||||
const closeTruckArrival = () => {
|
||||
setReleaseItem(null);
|
||||
setReleaseTruckPrefill(null);
|
||||
void qc.invalidateQueries({ queryKey: ["warehouse-inventory"] });
|
||||
void qc.invalidateQueries({ queryKey: QUERY_KEYS.LAST_MILE.ROOT });
|
||||
};
|
||||
|
||||
const printTripSlip = () => {
|
||||
if (!tripSlipRecord) return;
|
||||
const win = window.open("", "_blank", "width=820,height=920");
|
||||
@@ -834,6 +953,9 @@ const LastMilePage = () => {
|
||||
const canPrint = row.original.status !== "PAYMENT_PENDING";
|
||||
const isPaid = (row.original as any).paid;
|
||||
const delivered = row.original.status === "DELIVERED";
|
||||
const releaseRow =
|
||||
pickupReadyByBooking.get(row.original.bookingId) ?? pickupReadyByBooking.get(bookingRef(row.original));
|
||||
const truckArrivalLabel = releaseRow?.releaseOrderReference ? "Truck Leaving" : "Truck Arrival";
|
||||
return (
|
||||
<Group gap={4} justify="flex-end" wrap="nowrap">
|
||||
<Menu position="bottom-end" width={200} withinPortal>
|
||||
@@ -865,6 +987,13 @@ const LastMilePage = () => {
|
||||
>
|
||||
Reassign
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<Truck size={15} />}
|
||||
disabled={!assigned}
|
||||
onClick={() => openTruckArrival(row.original)}
|
||||
>
|
||||
{truckArrivalLabel}
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
leftSection={<Eye size={15} />}
|
||||
@@ -912,7 +1041,7 @@ const LastMilePage = () => {
|
||||
},
|
||||
];
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [vehicleOptions]);
|
||||
}, [vehicleOptions, pickupReadyByBooking]);
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
@@ -1413,6 +1542,13 @@ const LastMilePage = () => {
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
<ReleaseOrderModal
|
||||
opened={Boolean(releaseItem)}
|
||||
onClose={closeTruckArrival}
|
||||
item={releaseItem}
|
||||
truckPrefill={releaseTruckPrefill}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user