mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
fix(warehouses): finish queue-table ergonomics batch
- statuses render through the shared InventoryStatusBadge (humanized label,
consistent per-status color) instead of raw enums in ad-hoc badges
- Ready-To-Load drops its selection checkboxes: nothing consumed the
selection since auto-load became train-scoped
- bulk toasts now say WHY items were skipped ("3 skipped — Booking not PAID")
via a shared skippedSummary helper
- one-click irreversible bulk actions (Dispatch All/Selected, Mark Selected
as Inspected x2, Auto Unload train) now ask for confirmation through a
small local ConfirmActionModal - no @mantine/modals dependency added.
Receive All stays unconfirmed: it already funnels through the
truck-entrance modal with an explicit Save.
- table minWidths retuned for the slimmed column sets
(1700->1350, 1600->1200, 2000->1650)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,7 @@ import { warehouseService } from '@/services/warehouse.service';
|
|||||||
import type {
|
import type {
|
||||||
EligibleBooking,
|
EligibleBooking,
|
||||||
InventoryInquiryFilter,
|
InventoryInquiryFilter,
|
||||||
|
InventoryStatus,
|
||||||
InventoryInquiryResult,
|
InventoryInquiryResult,
|
||||||
ImportTrain,
|
ImportTrain,
|
||||||
ImportTrainItem,
|
ImportTrainItem,
|
||||||
@@ -63,6 +64,7 @@ import type {
|
|||||||
WarehouseYard,
|
WarehouseYard,
|
||||||
WarehouseZone,
|
WarehouseZone,
|
||||||
} from '@/types/warehouse';
|
} from '@/types/warehouse';
|
||||||
|
import { InventoryStatusBadge } from './badges';
|
||||||
import { BookingSelect } from './BookingSelect';
|
import { BookingSelect } from './BookingSelect';
|
||||||
import { DeliverInventoryModal } from './DeliverInventoryModal';
|
import { DeliverInventoryModal } from './DeliverInventoryModal';
|
||||||
import { ContainerItemsModal } from './ContainerItemsModal';
|
import { ContainerItemsModal } from './ContainerItemsModal';
|
||||||
@@ -253,6 +255,50 @@ const toTruckEntrancePayload = (form: TruckEntranceFormState): TruckEntrancePayl
|
|||||||
warehouseManagerName: form.warehouseManagerName.trim() || undefined,
|
warehouseManagerName: form.warehouseManagerName.trim() || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type ConfirmAction = { title: string; message: string; confirmLabel: string; run: () => void };
|
||||||
|
|
||||||
|
/** One-click bulk actions are irreversible — make the click deliberate. */
|
||||||
|
function ConfirmActionModal({
|
||||||
|
action,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
action: ConfirmAction | null;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Modal opened={Boolean(action)} onClose={onClose} title={action?.title ?? ''} centered size="sm">
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text size="sm">{action?.message}</Text>
|
||||||
|
<Group justify="flex-end">
|
||||||
|
<Button variant="default" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
action?.run();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{action?.confirmLabel}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** "3 skipped — Booking not PAID" instead of a bare count. */
|
||||||
|
const skippedSummary = (
|
||||||
|
skippedCount: number,
|
||||||
|
results: Array<{ reason?: string; message?: string }>,
|
||||||
|
): string | undefined => {
|
||||||
|
if (!skippedCount) return undefined;
|
||||||
|
const reason = results.find((x) => x.reason || x.message);
|
||||||
|
return `${skippedCount} skipped${reason ? ` — ${reason.reason ?? reason.message}` : ''}`;
|
||||||
|
};
|
||||||
|
|
||||||
const commonNonEmptyValue = (values: Array<string | null | undefined>) => {
|
const commonNonEmptyValue = (values: Array<string | null | undefined>) => {
|
||||||
const unique = [...new Set(values.map((value) => value?.trim()).filter(Boolean))] as string[];
|
const unique = [...new Set(values.map((value) => value?.trim()).filter(Boolean))] as string[];
|
||||||
return unique.length === 1 ? unique[0] : '';
|
return unique.length === 1 ? unique[0] : '';
|
||||||
@@ -860,7 +906,7 @@ function EligibleTab({
|
|||||||
});
|
});
|
||||||
toast({
|
toast({
|
||||||
title: `${r.receivedCount} received at ${direction === 'EXPORT' ? 'facility' : 'warehouse'}`,
|
title: `${r.receivedCount} received at ${direction === 'EXPORT' ? 'facility' : 'warehouse'}`,
|
||||||
description: r.results.find((item) => item.grnNumber)?.grnNumber ?? (r.skippedCount ? `${r.skippedCount} skipped` : undefined),
|
description: r.results.find((item) => item.grnNumber)?.grnNumber ?? skippedSummary(r.skippedCount, r.results),
|
||||||
});
|
});
|
||||||
const firstGrn = r.results.find((item) => item.inventoryId && item.grnNumber);
|
const firstGrn = r.results.find((item) => item.inventoryId && item.grnNumber);
|
||||||
if (focusedBookingId && firstGrn?.inventoryId && firstGrn.grnNumber) {
|
if (focusedBookingId && firstGrn?.inventoryId && firstGrn.grnNumber) {
|
||||||
@@ -1030,7 +1076,7 @@ function EligibleTab({
|
|||||||
: `No eligible PAID ${direction.toLowerCase()} bookings to receive.`}
|
: `No eligible PAID ${direction.toLowerCase()} bookings to receive.`}
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Table.ScrollContainer minWidth={1700}>
|
<Table.ScrollContainer minWidth={1350}>
|
||||||
<Table highlightOnHover verticalSpacing="xs" striped>
|
<Table highlightOnHover verticalSpacing="xs" striped>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
@@ -1248,6 +1294,7 @@ function ExportReceivedTab({ enabled, onChanged }: { enabled: boolean; onChanged
|
|||||||
api.warehouses.bulkMarkInspected.mutationOptions(),
|
api.warehouses.bulkMarkInspected.mutationOptions(),
|
||||||
);
|
);
|
||||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||||
|
const [confirmAction, setConfirmAction] = useState<ConfirmAction | null>(null);
|
||||||
const [inspectId, setInspectId] = useState<string | null>(null);
|
const [inspectId, setInspectId] = useState<string | null>(null);
|
||||||
|
|
||||||
const pendingRows = rows.filter((r) => r.inspectionStatus !== 'PASSED');
|
const pendingRows = rows.filter((r) => r.inspectionStatus !== 'PASSED');
|
||||||
@@ -1271,7 +1318,7 @@ function ExportReceivedTab({ enabled, onChanged }: { enabled: boolean; onChanged
|
|||||||
const r = await inspectMutation.mutateAsync({ inventoryIds: [...selected] });
|
const r = await inspectMutation.mutateAsync({ inventoryIds: [...selected] });
|
||||||
toast({
|
toast({
|
||||||
title: `${r.inspectedCount} marked inspected`,
|
title: `${r.inspectedCount} marked inspected`,
|
||||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
description: skippedSummary(r.skippedCount, r.results),
|
||||||
});
|
});
|
||||||
setSelected(new Set());
|
setSelected(new Set());
|
||||||
onChanged?.();
|
onChanged?.();
|
||||||
@@ -1292,7 +1339,14 @@ function ExportReceivedTab({ enabled, onChanged }: { enabled: boolean; onChanged
|
|||||||
leftSection={<ClipboardCheck size={14} />}
|
leftSection={<ClipboardCheck size={14} />}
|
||||||
disabled={selected.size === 0}
|
disabled={selected.size === 0}
|
||||||
loading={inspectMutation.isPending}
|
loading={inspectMutation.isPending}
|
||||||
onClick={markInspected}
|
onClick={() =>
|
||||||
|
setConfirmAction({
|
||||||
|
title: 'Mark inspected',
|
||||||
|
message: `Mark ${selected.size} selected item(s) as inspection PASSED?`,
|
||||||
|
confirmLabel: `Mark ${selected.size} inspected`,
|
||||||
|
run: markInspected,
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Mark Selected as Inspected
|
Mark Selected as Inspected
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1307,7 +1361,7 @@ function ExportReceivedTab({ enabled, onChanged }: { enabled: boolean; onChanged
|
|||||||
No received export items awaiting inspection.
|
No received export items awaiting inspection.
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Table.ScrollContainer minWidth={1700}>
|
<Table.ScrollContainer minWidth={1350}>
|
||||||
<Table highlightOnHover verticalSpacing="xs" striped>
|
<Table highlightOnHover verticalSpacing="xs" striped>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
@@ -1363,9 +1417,7 @@ function ExportReceivedTab({ enabled, onChanged }: { enabled: boolean; onChanged
|
|||||||
</Badge>
|
</Badge>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Badge color="blue" variant="light" size="sm">
|
<InventoryStatusBadge status={r.status as InventoryStatus} />
|
||||||
{r.status}
|
|
||||||
</Badge>
|
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td ta="right">
|
<Table.Td ta="right">
|
||||||
<Button size="compact-xs" variant="light" color="orange" onClick={() => setInspectId(r.id)}>
|
<Button size="compact-xs" variant="light" color="orange" onClick={() => setInspectId(r.id)}>
|
||||||
@@ -1385,6 +1437,7 @@ function ExportReceivedTab({ enabled, onChanged }: { enabled: boolean; onChanged
|
|||||||
opened={Boolean(inspectId)}
|
opened={Boolean(inspectId)}
|
||||||
onClose={() => setInspectId(null)}
|
onClose={() => setInspectId(null)}
|
||||||
/>
|
/>
|
||||||
|
<ConfirmActionModal action={confirmAction} onClose={() => setConfirmAction(null)} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1396,7 +1449,6 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
|
|||||||
api.warehouses.readyToLoadExport.queryOptions({ enabled }),
|
api.warehouses.readyToLoadExport.queryOptions({ enabled }),
|
||||||
);
|
);
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
|
||||||
const [trainPickerOpen, setTrainPickerOpen] = useState(false);
|
const [trainPickerOpen, setTrainPickerOpen] = useState(false);
|
||||||
const [targetScheduleId, setTargetScheduleId] = useState<string | null>(null);
|
const [targetScheduleId, setTargetScheduleId] = useState<string | null>(null);
|
||||||
// Loading is always onto a SPECIFIC pre-dispatch train. No train -> no auto-load.
|
// Loading is always onto a SPECIFIC pre-dispatch train. No train -> no auto-load.
|
||||||
@@ -1420,15 +1472,6 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const allSelected = rows.length > 0 && selected.size === rows.length;
|
|
||||||
const someSelected = selected.size > 0 && !allSelected;
|
|
||||||
const toggleAll = () => setSelected(allSelected ? new Set() : new Set(rows.map((r) => r.id)));
|
|
||||||
const toggleOne = (id: string) =>
|
|
||||||
setSelected((prev) => {
|
|
||||||
const next = new Set(prev);
|
|
||||||
next.has(id) ? next.delete(id) : next.add(id);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
|
|
||||||
const confirmLoad = async () => {
|
const confirmLoad = async () => {
|
||||||
if (!targetScheduleId) {
|
if (!targetScheduleId) {
|
||||||
@@ -1446,7 +1489,6 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
|
|||||||
});
|
});
|
||||||
setTrainPickerOpen(false);
|
setTrainPickerOpen(false);
|
||||||
setTargetScheduleId(null);
|
setTargetScheduleId(null);
|
||||||
setSelected(new Set());
|
|
||||||
onChanged?.();
|
onChanged?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({ variant: 'destructive', title: 'Load failed', description: extractErrorMessage(error) });
|
toast({ variant: 'destructive', title: 'Load failed', description: extractErrorMessage(error) });
|
||||||
@@ -1525,18 +1567,10 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
|
|||||||
No EXPORT items with inspection PASSED waiting to be loaded.
|
No EXPORT items with inspection PASSED waiting to be loaded.
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Table.ScrollContainer minWidth={1600}>
|
<Table.ScrollContainer minWidth={1200}>
|
||||||
<Table highlightOnHover verticalSpacing="xs" striped>
|
<Table highlightOnHover verticalSpacing="xs" striped>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Th w={40}>
|
|
||||||
<Checkbox
|
|
||||||
aria-label="Select all"
|
|
||||||
checked={allSelected}
|
|
||||||
indeterminate={someSelected}
|
|
||||||
onChange={toggleAll}
|
|
||||||
/>
|
|
||||||
</Table.Th>
|
|
||||||
<Table.Th>Booking Ref</Table.Th>
|
<Table.Th>Booking Ref</Table.Th>
|
||||||
<Table.Th>GRN</Table.Th>
|
<Table.Th>GRN</Table.Th>
|
||||||
<Table.Th>Customer Name</Table.Th>
|
<Table.Th>Customer Name</Table.Th>
|
||||||
@@ -1551,13 +1585,6 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
|
|||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{rows.map((r: ReadyToLoadRow) => (
|
{rows.map((r: ReadyToLoadRow) => (
|
||||||
<Table.Tr key={r.id}>
|
<Table.Tr key={r.id}>
|
||||||
<Table.Td>
|
|
||||||
<Checkbox
|
|
||||||
aria-label={`Select ${r.bookingReference ?? r.id}`}
|
|
||||||
checked={selected.has(r.id)}
|
|
||||||
onChange={() => toggleOne(r.id)}
|
|
||||||
/>
|
|
||||||
</Table.Td>
|
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Text size="sm" fw={600}>{r.bookingReference ?? '—'}</Text>
|
<Text size="sm" fw={600}>{r.bookingReference ?? '—'}</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
@@ -1577,9 +1604,7 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
|
|||||||
</Badge>
|
</Badge>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Badge color="teal" variant="light" size="sm">
|
<InventoryStatusBadge status={r.status as InventoryStatus} />
|
||||||
{r.status}
|
|
||||||
</Badge>
|
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
@@ -1608,6 +1633,7 @@ function LoadedExportTab({
|
|||||||
const { data: rows = [], isLoading } = useQuery(
|
const { data: rows = [], isLoading } = useQuery(
|
||||||
api.warehouses.loadedExport.queryOptions({ enabled }),
|
api.warehouses.loadedExport.queryOptions({ enabled }),
|
||||||
);
|
);
|
||||||
|
const [confirmAction, setConfirmAction] = useState<ConfirmAction | null>(null);
|
||||||
const bulkDispatch = useMutation(
|
const bulkDispatch = useMutation(
|
||||||
api.warehouses.bulkDispatchExport.mutationOptions(),
|
api.warehouses.bulkDispatchExport.mutationOptions(),
|
||||||
);
|
);
|
||||||
@@ -1632,7 +1658,7 @@ function LoadedExportTab({
|
|||||||
const r = await bulkDispatch.mutateAsync(inventoryIds);
|
const r = await bulkDispatch.mutateAsync(inventoryIds);
|
||||||
toast({
|
toast({
|
||||||
title: `${r.dispatchedCount} dispatched`,
|
title: `${r.dispatchedCount} dispatched`,
|
||||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
description: skippedSummary(r.skippedCount, r.results),
|
||||||
});
|
});
|
||||||
setSelected(new Set());
|
setSelected(new Set());
|
||||||
onChanged?.();
|
onChanged?.();
|
||||||
@@ -1662,7 +1688,14 @@ function LoadedExportTab({
|
|||||||
variant="default"
|
variant="default"
|
||||||
disabled={rows.length === 0}
|
disabled={rows.length === 0}
|
||||||
loading={bulkDispatch.isPending}
|
loading={bulkDispatch.isPending}
|
||||||
onClick={() => dispatch(rows.map((r) => r.id))}
|
onClick={() =>
|
||||||
|
setConfirmAction({
|
||||||
|
title: 'Dispatch all',
|
||||||
|
message: `Dispatch all ${rows.length} loaded item(s)? They leave warehouse inventory for the train.`,
|
||||||
|
confirmLabel: `Dispatch ${rows.length}`,
|
||||||
|
run: () => dispatch(rows.map((r) => r.id)),
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Dispatch All
|
Dispatch All
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1672,7 +1705,14 @@ function LoadedExportTab({
|
|||||||
leftSection={<Truck size={14} />}
|
leftSection={<Truck size={14} />}
|
||||||
disabled={selected.size === 0}
|
disabled={selected.size === 0}
|
||||||
loading={bulkDispatch.isPending}
|
loading={bulkDispatch.isPending}
|
||||||
onClick={() => dispatch([...selected])}
|
onClick={() =>
|
||||||
|
setConfirmAction({
|
||||||
|
title: 'Dispatch selected',
|
||||||
|
message: `Dispatch ${selected.size} selected item(s)? They leave warehouse inventory for the train.`,
|
||||||
|
confirmLabel: `Dispatch ${selected.size}`,
|
||||||
|
run: () => dispatch([...selected]),
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Dispatch Selected
|
Dispatch Selected
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1689,7 +1729,7 @@ function LoadedExportTab({
|
|||||||
No LOADED export items {dispatchable ? 'waiting to dispatch' : 'yet'}.
|
No LOADED export items {dispatchable ? 'waiting to dispatch' : 'yet'}.
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Table.ScrollContainer minWidth={1600}>
|
<Table.ScrollContainer minWidth={1200}>
|
||||||
<Table highlightOnHover verticalSpacing="xs" striped>
|
<Table highlightOnHover verticalSpacing="xs" striped>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
@@ -1739,9 +1779,7 @@ function LoadedExportTab({
|
|||||||
{r.origin || r.destination ? `${r.origin ?? '?'} → ${r.destination ?? '?'}` : '—'}
|
{r.origin || r.destination ? `${r.origin ?? '?'} → ${r.destination ?? '?'}` : '—'}
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Badge color="blue" variant="light" size="sm">
|
<InventoryStatusBadge status={r.status as InventoryStatus} />
|
||||||
{r.status}
|
|
||||||
</Badge>
|
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
@@ -1749,6 +1787,7 @@ function LoadedExportTab({
|
|||||||
</Table>
|
</Table>
|
||||||
</Table.ScrollContainer>
|
</Table.ScrollContainer>
|
||||||
)}
|
)}
|
||||||
|
<ConfirmActionModal action={confirmAction} onClose={() => setConfirmAction(null)} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1976,6 +2015,7 @@ function ImportArriveQueueTab({
|
|||||||
api.warehouses.autoUnloadArrivedBookings.mutationOptions(),
|
api.warehouses.autoUnloadArrivedBookings.mutationOptions(),
|
||||||
);
|
);
|
||||||
const [openId, setOpenId] = useState<string | null>(null);
|
const [openId, setOpenId] = useState<string | null>(null);
|
||||||
|
const [confirmAction, setConfirmAction] = useState<ConfirmAction | null>(null);
|
||||||
const [busyId, setBusyId] = useState<string | null>(null);
|
const [busyId, setBusyId] = useState<string | null>(null);
|
||||||
const [assignmentsBySchedule, setAssignmentsBySchedule] = useState<
|
const [assignmentsBySchedule, setAssignmentsBySchedule] = useState<
|
||||||
Record<string, Record<string, ImportUnloadAssignmentDraft>>
|
Record<string, Record<string, ImportUnloadAssignmentDraft>>
|
||||||
@@ -2017,7 +2057,7 @@ function ImportArriveQueueTab({
|
|||||||
const alreadyUnloaded = r.unloadedCount === 0 && r.skippedCount > 0 && r.failedCount === 0;
|
const alreadyUnloaded = r.unloadedCount === 0 && r.skippedCount > 0 && r.failedCount === 0;
|
||||||
const firstReason = r.results.find((item) => item.reason)?.reason;
|
const firstReason = r.results.find((item) => item.reason)?.reason;
|
||||||
const extra = [
|
const extra = [
|
||||||
r.skippedCount ? `${r.skippedCount} skipped` : '',
|
skippedSummary(r.skippedCount, r.results) ?? '',
|
||||||
r.failedCount ? `${r.failedCount} failed` : '',
|
r.failedCount ? `${r.failedCount} failed` : '',
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@@ -2113,7 +2153,14 @@ function ImportArriveQueueTab({
|
|||||||
leftSection={<Truck size={14} />}
|
leftSection={<Truck size={14} />}
|
||||||
loading={busyId === t.scheduleId}
|
loading={busyId === t.scheduleId}
|
||||||
disabled={fullyUnloaded || t.totalBookings === 0 || !readyBySchedule[t.scheduleId] || warehousesLoading}
|
disabled={fullyUnloaded || t.totalBookings === 0 || !readyBySchedule[t.scheduleId] || warehousesLoading}
|
||||||
onClick={() => autoUnload(t)}
|
onClick={() =>
|
||||||
|
setConfirmAction({
|
||||||
|
title: 'Auto unload train',
|
||||||
|
message: `Unload all arrived bookings from train ${t.trainNumber ?? t.scheduleId.slice(0, 8)} into their assigned warehouse locations?`,
|
||||||
|
confirmLabel: 'Unload train',
|
||||||
|
run: () => autoUnload(t),
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{fullyUnloaded ? 'Already Unloaded' : 'Auto Unload Arrived Bookings'}
|
{fullyUnloaded ? 'Already Unloaded' : 'Auto Unload Arrived Bookings'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -2152,6 +2199,7 @@ function ImportArriveQueueTab({
|
|||||||
</Table>
|
</Table>
|
||||||
</Table.ScrollContainer>
|
</Table.ScrollContainer>
|
||||||
)}
|
)}
|
||||||
|
<ConfirmActionModal action={confirmAction} onClose={() => setConfirmAction(null)} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2172,6 +2220,7 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
|||||||
);
|
);
|
||||||
const readyMutation = useMutation(api.warehouses.markReadyForPickup.mutationOptions());
|
const readyMutation = useMutation(api.warehouses.markReadyForPickup.mutationOptions());
|
||||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||||
|
const [confirmAction, setConfirmAction] = useState<ConfirmAction | null>(null);
|
||||||
const [inspectId, setInspectId] = useState<string | null>(null);
|
const [inspectId, setInspectId] = useState<string | null>(null);
|
||||||
const [busyId, setBusyId] = useState<string | null>(null);
|
const [busyId, setBusyId] = useState<string | null>(null);
|
||||||
const [viewItem, setViewItem] = useState<WarehouseInventoryItem | null>(null);
|
const [viewItem, setViewItem] = useState<WarehouseInventoryItem | null>(null);
|
||||||
@@ -2203,7 +2252,7 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
|||||||
const r = await inspectMutation.mutateAsync({ inventoryIds: [...selected] });
|
const r = await inspectMutation.mutateAsync({ inventoryIds: [...selected] });
|
||||||
toast({
|
toast({
|
||||||
title: `${r.inspectedCount} marked inspected`,
|
title: `${r.inspectedCount} marked inspected`,
|
||||||
description: r.skippedCount ? `${r.skippedCount} skipped` : undefined,
|
description: skippedSummary(r.skippedCount, r.results),
|
||||||
});
|
});
|
||||||
setSelected(new Set());
|
setSelected(new Set());
|
||||||
void qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
void qc.invalidateQueries({ queryKey: ['warehouse-inventory'] });
|
||||||
@@ -2307,7 +2356,14 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
|||||||
leftSection={<ClipboardCheck size={14} />}
|
leftSection={<ClipboardCheck size={14} />}
|
||||||
disabled={selected.size === 0}
|
disabled={selected.size === 0}
|
||||||
loading={inspectMutation.isPending}
|
loading={inspectMutation.isPending}
|
||||||
onClick={markInspected}
|
onClick={() =>
|
||||||
|
setConfirmAction({
|
||||||
|
title: 'Mark inspected',
|
||||||
|
message: `Mark ${selected.size} selected item(s) as inspection PASSED? Passed import items become ready for pickup.`,
|
||||||
|
confirmLabel: `Mark ${selected.size} inspected`,
|
||||||
|
run: markInspected,
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Mark Selected as Inspected
|
Mark Selected as Inspected
|
||||||
</Button>
|
</Button>
|
||||||
@@ -2323,7 +2379,7 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
|||||||
No unloaded import items. Items appear here after Auto Unload on an arrived train.
|
No unloaded import items. Items appear here after Auto Unload on an arrived train.
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Table.ScrollContainer minWidth={2000}>
|
<Table.ScrollContainer minWidth={1650}>
|
||||||
<Table highlightOnHover verticalSpacing="xs" striped>
|
<Table highlightOnHover verticalSpacing="xs" striped>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
@@ -2384,7 +2440,7 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Badge color="indigo" variant="light" size="sm">{r.currentStatus}</Badge>
|
<InventoryStatusBadge status={r.currentStatus as InventoryStatus} />
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td ta="right">
|
<Table.Td ta="right">
|
||||||
<Group gap="xs" justify="flex-end" wrap="nowrap">
|
<Group gap="xs" justify="flex-end" wrap="nowrap">
|
||||||
@@ -2506,6 +2562,7 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) {
|
|||||||
bookingId={containerItemsItem?.booking?.id ?? null}
|
bookingId={containerItemsItem?.booking?.id ?? null}
|
||||||
bookingReference={containerItemsItem?.booking?.reference ?? null}
|
bookingReference={containerItemsItem?.booking?.reference ?? null}
|
||||||
/>
|
/>
|
||||||
|
<ConfirmActionModal action={confirmAction} onClose={() => setConfirmAction(null)} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user