mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
@@ -1414,8 +1414,30 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
|
||||
const { data: rows = [], isLoading } = useQuery(
|
||||
api.warehouses.readyToLoadExport.queryOptions({ enabled }),
|
||||
);
|
||||
const loadPassed = useMutation(api.warehouses.loadPassedExport.mutationOptions());
|
||||
const qc = useQueryClient();
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [trainPickerOpen, setTrainPickerOpen] = useState(false);
|
||||
const [targetScheduleId, setTargetScheduleId] = useState<string | null>(null);
|
||||
// Loading is always onto a SPECIFIC pre-dispatch train. No train -> no auto-load.
|
||||
const { data: trains = [], isLoading: trainsLoading } = useQuery({
|
||||
queryKey: ['warehouse-inventory', 'loadable-trains'],
|
||||
queryFn: () => warehouseService.getLoadableTrains(),
|
||||
enabled: enabled && trainPickerOpen,
|
||||
});
|
||||
const loadOntoTrain = useMutation({
|
||||
mutationFn: async (scheduleId: string) => {
|
||||
const items = await warehouseService.getTrainLoadableItems(scheduleId);
|
||||
const loadableIds = items.filter((i) => i.loadable).map((i) => i.id);
|
||||
if (!loadableIds.length) {
|
||||
throw new Error('No ready items with an allocated wagon on this train');
|
||||
}
|
||||
return warehouseService.loadItemsOntoTrain(scheduleId, loadableIds);
|
||||
},
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
||||
void qc.invalidateQueries({ queryKey: ['warehouse-loadings'] });
|
||||
},
|
||||
});
|
||||
|
||||
const allSelected = rows.length > 0 && selected.size === rows.length;
|
||||
const someSelected = selected.size > 0 && !allSelected;
|
||||
@@ -1427,13 +1449,22 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
|
||||
return next;
|
||||
});
|
||||
|
||||
const autoLoad = async () => {
|
||||
const confirmLoad = async () => {
|
||||
if (!targetScheduleId) {
|
||||
toast({ variant: 'destructive', title: 'Select a train to load onto' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const r = await loadPassed.mutateAsync(undefined);
|
||||
const r = await loadOntoTrain.mutateAsync(targetScheduleId);
|
||||
const train = trains.find((t) => t.scheduleId === targetScheduleId);
|
||||
toast({
|
||||
title: `${r.loadedCount} items loaded`,
|
||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
||||
title: `${r.loadedCount} item(s) loaded onto train ${train?.trainNumber ?? ''}`.trim(),
|
||||
description: r.skippedCount
|
||||
? `${r.skippedCount} skipped — ${r.results.find((x) => x.reason)?.reason ?? 'see results'}`
|
||||
: undefined,
|
||||
});
|
||||
setTrainPickerOpen(false);
|
||||
setTargetScheduleId(null);
|
||||
setSelected(new Set());
|
||||
onChanged?.();
|
||||
} catch (error) {
|
||||
@@ -1452,14 +1483,58 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
|
||||
variant="filled"
|
||||
color="teal"
|
||||
leftSection={<Truck size={14} />}
|
||||
loading={loadPassed.isPending}
|
||||
disabled={rows.length === 0}
|
||||
onClick={autoLoad}
|
||||
onClick={() => setTrainPickerOpen(true)}
|
||||
>
|
||||
Auto Load Ready Items
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Modal
|
||||
opened={trainPickerOpen}
|
||||
onClose={() => setTrainPickerOpen(false)}
|
||||
title="Load ready items onto a train"
|
||||
centered
|
||||
size="lg"
|
||||
>
|
||||
<Stack gap="md">
|
||||
{trainsLoading ? (
|
||||
<Group justify="center" py="md"><Loader size="sm" /></Group>
|
||||
) : trains.length === 0 ? (
|
||||
<Alert color="yellow" variant="light" icon={<Info size={16} />}>
|
||||
No train available. Auto-loading needs a scheduled (not yet dispatched) train with
|
||||
these bookings assigned — schedule the train and allocate wagons first.
|
||||
</Alert>
|
||||
) : (
|
||||
<Select
|
||||
label="Available trains"
|
||||
placeholder="Select the train to load onto"
|
||||
data={trains.map((t) => ({
|
||||
value: t.scheduleId,
|
||||
label: `${t.trainNumber ?? t.scheduleId.slice(0, 8)} · ${t.origin ?? '?'} → ${t.destination ?? '?'} · dep ${t.departureTime ? formatDate(t.departureTime) : '—'} · ${t.readyCount} ready`,
|
||||
}))}
|
||||
value={targetScheduleId}
|
||||
onChange={setTargetScheduleId}
|
||||
searchable
|
||||
/>
|
||||
)}
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={() => setTrainPickerOpen(false)} disabled={loadOntoTrain.isPending}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
color="teal"
|
||||
leftSection={<Truck size={14} />}
|
||||
loading={loadOntoTrain.isPending}
|
||||
disabled={!targetScheduleId}
|
||||
onClick={confirmLoad}
|
||||
>
|
||||
Load onto this train
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
{isLoading ? (
|
||||
<Group justify="center" py="lg">
|
||||
<Loader size="sm" />
|
||||
|
||||
@@ -112,7 +112,9 @@ const parseInspectionNote = (notes: string | null | undefined) => {
|
||||
export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: ReleaseOrderModalProps) {
|
||||
const { toast } = useToast();
|
||||
const releaseMutation = useMutation(api.warehouses.release.mutationOptions());
|
||||
const bookingId = item?.booking?.id;
|
||||
// Some openers (inventory workbench) supply bookingId without the booking
|
||||
// relation — fall back to it, or the truck/container-weight queries never run.
|
||||
const bookingId = item?.booking?.id ?? item?.bookingId ?? undefined;
|
||||
// Customer self-haul trucks assigned to this booking via the portal.
|
||||
const { data: customerTrucks = [] } = useQuery({
|
||||
queryKey: ['release-customer-trucks', bookingId],
|
||||
|
||||
@@ -236,8 +236,6 @@ export function useEligibleBookings(enabled = true) {
|
||||
}
|
||||
export const useBulkReceive = () =>
|
||||
useInventoryMutation((payload: BulkReceivePayload) => warehouseService.receiveBulk(payload));
|
||||
export const useLoadPassedExport = () =>
|
||||
useInventoryMutation(() => warehouseService.loadPassedExport());
|
||||
export const useBulkMarkInspected = () =>
|
||||
useInventoryMutation((payload: BulkInspectPayload) => warehouseService.bulkMarkInspected(payload));
|
||||
|
||||
|
||||
@@ -101,7 +101,6 @@ import type {
|
||||
InitiateWarehouseInvoicePaymentPayload,
|
||||
LoadableWagon,
|
||||
LoadInventoryPayload,
|
||||
LoadPassedExportResult,
|
||||
MoveInventoryPayload,
|
||||
StoreInventoryPayload,
|
||||
PayInvoicePayload,
|
||||
@@ -1158,14 +1157,6 @@ export const api = {
|
||||
() => INVENTORY_INVALIDATIONS,
|
||||
),
|
||||
|
||||
loadPassedExport: endpoint<void, LoadPassedExportResult>(
|
||||
"warehouse-inventory",
|
||||
"load-passed-export",
|
||||
() => warehouseService.loadPassedExport().then((r) => r.data),
|
||||
undefined,
|
||||
() => INVENTORY_INVALIDATIONS,
|
||||
),
|
||||
|
||||
bulkMarkInspected: endpoint<BulkInspectPayload, BulkInspectResult>(
|
||||
"warehouse-inventory",
|
||||
"bulk-mark-inspected",
|
||||
|
||||
@@ -37,7 +37,6 @@ import type {
|
||||
EligibleBooking,
|
||||
BulkReceivePayload,
|
||||
BulkReceiveResult,
|
||||
LoadPassedExportResult,
|
||||
BulkInspectPayload,
|
||||
BulkInspectResult,
|
||||
ReadyToLoadRow,
|
||||
@@ -300,8 +299,6 @@ export const warehouseService = {
|
||||
apiClient.get<EligibleBooking[]>(URL_CONSTANTS.WAREHOUSE_INVENTORY.ELIGIBLE_BOOKINGS(direction)),
|
||||
receiveBulk: (payload: BulkReceivePayload) =>
|
||||
apiClient.post<BulkReceiveResult>(URL_CONSTANTS.WAREHOUSE_INVENTORY.RECEIVE_BULK, payload),
|
||||
loadPassedExport: () =>
|
||||
apiClient.post<LoadPassedExportResult>(URL_CONSTANTS.WAREHOUSE_INVENTORY.LOAD_PASSED_EXPORT, {}),
|
||||
bulkMarkInspected: (payload: BulkInspectPayload) =>
|
||||
apiClient.post<BulkInspectResult>(URL_CONSTANTS.WAREHOUSE_INVENTORY.BULK_MARK_INSPECTED, payload),
|
||||
receivedExport: () =>
|
||||
|
||||
Reference in New Issue
Block a user