feat(warehouses): add import-ops stat tiles and arrival-queue filters

Adds Arrived/Unloaded/Dispatch Ready/Total Bookings stat tiles above the
import queue tabs, wires search+date filters and pagination into the
arrival queue table (reusing useListControls/ListControls/
RuleEngineListFooter already used by Inventory Inquiry), and adds a
copy-to-clipboard action on the truncated schedule ID.
This commit is contained in:
Hagernesh
2026-08-20 10:15:27 +00:00
parent 2f35b0a717
commit d4c51ec63a

View File

@@ -4,7 +4,9 @@ import {
Alert,
Badge,
Button,
Card,
Checkbox,
CopyButton,
Group,
Loader,
Menu,
@@ -12,20 +14,25 @@ import {
NumberInput,
ScrollArea,
Select,
SimpleGrid,
Stack,
Table,
Tabs,
Text,
Textarea,
TextInput,
ThemeIcon,
Tooltip,
} from '@mantine/core';
import {
ArrowRightLeft,
Calendar,
Check,
CheckCheck,
ChevronDown,
ChevronRight,
ClipboardCheck,
Copy,
Eye,
FileText,
History,
@@ -83,6 +90,9 @@ import { StoreInventoryModal } from './StoreInventoryModal';
import { WarehouseInquiryTable } from './WarehouseInquiryTable';
import { extractDownloadErrorMessage, extractErrorMessage, formatDate, formatNumber, inventoryStatusOptions, warehousesAtStation, yardsForBooking } from './options';
import { openPdfBlob } from './pdf';
import ListControls from '@/components/common/ListControls';
import RuleEngineListFooter from '@/components/ruleEngine/RuleEngineListFooter';
import { useListControls } from '@/hooks/useListControls';
import '@/components/overview/overview.css';
type ImportUnloadAssignment = { bookingId: string; warehouseId: string; yardId: string; zoneId: string };
@@ -2218,6 +2228,11 @@ export function ImportArriveQueueTab({
>({});
const [readyBySchedule, setReadyBySchedule] = useState<Record<string, boolean>>({});
const controls = useListControls(trains, {
searchKeys: ['trainNumber', 'route', 'origin', 'destination', 'status'],
dateKey: 'arrivalTime',
});
const autoUnload = async (train: ImportTrain) => {
const assignments = Object.entries(assignmentsBySchedule[train.scheduleId] ?? {})
.filter((entry): entry is [string, Required<ImportUnloadAssignmentDraft>] =>
@@ -2272,10 +2287,6 @@ export function ImportArriveQueueTab({
return (
<Stack gap="sm" mt="sm">
<Text size="sm" c="dimmed">
<b>{trains.length}</b> arrived import train{trains.length !== 1 ? 's' : ''}
</Text>
{isLoading ? (
<Group justify="center" py="lg">
<Loader size="sm" />
@@ -2285,6 +2296,25 @@ export function ImportArriveQueueTab({
No arrived import trains. Trains appear here once their schedule status is ARRIVED.
</Text>
) : (
<Stack gap="sm">
<Group justify="space-between" align="flex-end" wrap="wrap">
<Text size="sm" c="dimmed">
<b>{controls.totalCount}</b> arrived import train{controls.totalCount !== 1 ? 's' : ''}
</Text>
<ListControls
search={controls.search}
onSearchChange={controls.setSearch}
searchPlaceholder="Train #, route, origin, destination…"
dateFrom={controls.dateFrom}
onDateFromChange={controls.setDateFrom}
dateTo={controls.dateTo}
onDateToChange={controls.setDateTo}
dateLabel="Arrival"
hasFilters={controls.hasFilters}
onReset={controls.reset}
/>
</Group>
<Table.ScrollContainer minWidth={1500}>
<Table highlightOnHover verticalSpacing="xs" striped>
<Table.Thead>
@@ -2303,7 +2333,16 @@ export function ImportArriveQueueTab({
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{trains.map((t: ImportTrain) => {
{controls.pagedRows.length === 0 ? (
<Table.Tr>
<Table.Td colSpan={11}>
<Text c="dimmed" ta="center" py="lg" size="sm">
No trains match the current filters.
</Text>
</Table.Td>
</Table.Tr>
) : (
controls.pagedRows.map((t: ImportTrain) => {
const isOpen = openId === t.scheduleId;
const fullyUnloaded = isFullyUnloaded(t);
const unloadedBookings = t.unloadedBookings ?? t.totalBookings - getPendingUnloadBookings(t);
@@ -2311,7 +2350,18 @@ export function ImportArriveQueueTab({
<Fragment key={t.scheduleId}>
<Table.Tr>
<Table.Td>
<Text size="xs" c="dimmed">{t.scheduleId.slice(0, 8)}</Text>
<Group gap={4} wrap="nowrap">
<Text size="xs" c="dimmed">{t.scheduleId.slice(0, 8)}</Text>
<CopyButton value={t.scheduleId}>
{({ copied, copy }) => (
<Tooltip label={copied ? 'Copied' : 'Copy schedule ID'} withArrow>
<ActionIcon size="xs" variant="subtle" color={copied ? 'teal' : 'gray'} onClick={copy}>
{copied ? <CheckCheck size={12} /> : <Copy size={12} />}
</ActionIcon>
</Tooltip>
)}
</CopyButton>
</Group>
</Table.Td>
<Table.Td>
<Text size="sm" fw={600}>{t.trainNumber ?? '—'}</Text>
@@ -2390,10 +2440,19 @@ export function ImportArriveQueueTab({
)}
</Fragment>
);
})}
}))}
</Table.Tbody>
</Table>
</Table.ScrollContainer>
<RuleEngineListFooter
pagination={controls.pagination}
pageCount={controls.pageCount}
totalCount={controls.totalCount}
itemLabel="trains"
onPaginationChange={controls.setPagination}
/>
</Stack>
)}
<ConfirmActionModal action={confirmAction} onClose={() => setConfirmAction(null)} />
</Stack>
@@ -2872,6 +2931,41 @@ interface WarehouseFlowWorkbenchProps {
focusedBookingLabel?: string;
}
function ImportStatCard({
icon,
label,
value,
sub,
color,
}: {
icon: React.ReactNode;
label: string;
value: React.ReactNode;
sub: string;
color: string;
}) {
return (
<Card withBorder radius="md" padding="md">
<Group gap="sm" wrap="nowrap">
<ThemeIcon color={color} variant="light" size={40} radius="md">
{icon}
</ThemeIcon>
<Stack gap={0} style={{ minWidth: 0 }}>
<Text fw={800} fz={22} lh={1.1}>
{value}
</Text>
<Text size="sm" fw={600}>
{label}
</Text>
<Text size="xs" c="dimmed">
{sub}
</Text>
</Stack>
</Group>
</Card>
);
}
function WarehouseQueueTabs<TValue extends string>({
value,
onChange,
@@ -3039,6 +3133,7 @@ function ImportWarehouseTabs({ enabled, onChanged }: { enabled: boolean; onChang
const { data: arriveRows = [] } = useQuery(api.warehouses.importArriveQueue.queryOptions({ enabled }));
const { data: unloadedRows = [] } = useQuery(api.warehouses.importUnloadedQueue.queryOptions({ enabled }));
const { data: dispatchRows = [] } = useQuery(api.warehouses.importPickupReadyQueue.queryOptions({ enabled }));
const totalBookings = arriveRows.reduce((sum, t) => sum + t.totalBookings, 0);
const tabs: WarehouseQueueTab<ImportWarehouseTab>[] = [
{
value: 'arrive-queue',
@@ -3067,6 +3162,13 @@ function ImportWarehouseTabs({ enabled, onChanged }: { enabled: boolean; onChang
return (
<Stack gap="md">
<SimpleGrid cols={{ base: 1, xs: 2, md: 4 }} spacing="sm">
<ImportStatCard icon={<PackageOpen size={18} />} label="Arrived" value={arriveRows.length} sub="Import trains" color="edr-green" />
<ImportStatCard icon={<ClipboardCheck size={18} />} label="Unloaded" value={unloadedRows.length} sub="Import trains" color="blue" />
<ImportStatCard icon={<Send size={18} />} label="Dispatch Ready" value={dispatchRows.length} sub="Import trains" color="violet" />
<ImportStatCard icon={<Calendar size={18} />} label="Total Bookings" value={totalBookings} sub="Across arrived trains" color="orange" />
</SimpleGrid>
<WarehouseQueueTabs value={activeTab} onChange={setActiveTab} tabs={tabs} />
{activeTab === 'arrive-queue' && (