mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 14:08:11 +00:00
- gateStats(): items cleared through the gate today, average arrival→gate turnaround (hours, 30d), and clearances per hour over the last 24h → new GateThroughputCard in the dashboard Performance section. - Live board: every dashboard query (dashboard, ops, throughput, dwell, cycle, on-time, zone occupancy, accrual, gate) now auto-refreshes on a 60s interval, with a "Live" indicator in the header. New endpoint GET /warehouse-inventory/gate-stats (guarded). Deferred (need upstream data): capacity forecast, labour productivity, WebSocket push, yard map. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
91 lines
3.0 KiB
TypeScript
91 lines
3.0 KiB
TypeScript
import { Card, Group, Loader, SimpleGrid, Stack, Text, ThemeIcon } from '@mantine/core';
|
|
import { DoorOpen } from 'lucide-react';
|
|
import {
|
|
Bar,
|
|
BarChart,
|
|
CartesianGrid,
|
|
ResponsiveContainer,
|
|
Tooltip,
|
|
XAxis,
|
|
YAxis,
|
|
} from 'recharts';
|
|
|
|
import { useWarehouseGateStats } from '@/hooks/useWarehouses';
|
|
|
|
/**
|
|
* Gate / dock throughput: items cleared through the gate today, the average
|
|
* arrival→gate-clearance turnaround, and clearances per hour over the last 24h.
|
|
*/
|
|
export function GateThroughputCard() {
|
|
const { data, isLoading } = useWarehouseGateStats();
|
|
const byHour = data?.byHour ?? [];
|
|
const hasActivity = byHour.some((h) => h.count > 0);
|
|
|
|
return (
|
|
<Card withBorder radius="lg" padding="lg" h="100%">
|
|
<Group gap="sm" mb="md">
|
|
<ThemeIcon size="lg" radius="md" color="indigo" variant="light">
|
|
<DoorOpen size={20} />
|
|
</ThemeIcon>
|
|
<div>
|
|
<Text fw={700}>Gate & dock throughput</Text>
|
|
<Text size="xs" c="dimmed">
|
|
Gate clearances over the last 24 hours
|
|
</Text>
|
|
</div>
|
|
</Group>
|
|
|
|
{isLoading ? (
|
|
<Group justify="center" h={220}>
|
|
<Loader />
|
|
</Group>
|
|
) : (
|
|
<Stack gap="md">
|
|
<SimpleGrid cols={2} spacing="sm">
|
|
<Stack gap={2}>
|
|
<Text size="xs" c="dimmed" tt="uppercase" fw={700}>
|
|
Cleared today
|
|
</Text>
|
|
<Text fw={800} fz={30} lh={1.1}>
|
|
{data?.clearedToday ?? 0}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
through the gate
|
|
</Text>
|
|
</Stack>
|
|
<Stack gap={2}>
|
|
<Text size="xs" c="dimmed" tt="uppercase" fw={700}>
|
|
Avg turnaround
|
|
</Text>
|
|
<Text fw={800} fz={30} lh={1.1}>
|
|
{data?.avgTurnaroundHours == null ? '—' : `${data.avgTurnaroundHours} h`}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
arrival → gate (30d)
|
|
</Text>
|
|
</Stack>
|
|
</SimpleGrid>
|
|
|
|
{hasActivity ? (
|
|
<ResponsiveContainer width="100%" height={170}>
|
|
<BarChart data={byHour} margin={{ top: 4, right: 8, left: -20, bottom: 0 }}>
|
|
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="var(--mantine-color-gray-2)" />
|
|
<XAxis dataKey="hour" interval={3} tick={{ fontSize: 11 }} />
|
|
<YAxis allowDecimals={false} tick={{ fontSize: 12 }} />
|
|
<Tooltip cursor={{ fill: 'var(--mantine-color-gray-1)' }} />
|
|
<Bar dataKey="count" name="Cleared" fill="#4c6ef5" radius={[6, 6, 0, 0]} />
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
) : (
|
|
<Group justify="center" align="center" h={170}>
|
|
<Text c="dimmed" size="sm">
|
|
No gate clearances in the last 24 hours.
|
|
</Text>
|
|
</Group>
|
|
)}
|
|
</Stack>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|