mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
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:
@@ -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' && (
|
||||
|
||||
Reference in New Issue
Block a user