Files
edr-platform/apps/edr-freight-web/backoffice/src/components/warehouses/GateThroughputCard.tsx
Hagernesh ac90b04be0 feat(warehouse): P2 dashboard — gate/dock throughput + live auto-refresh
- 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>
2026-07-15 14:26:02 +00:00

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 &amp; 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>
);
}