mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 21:08:12 +00:00
136 lines
4.5 KiB
TypeScript
136 lines
4.5 KiB
TypeScript
import {
|
|
Alert,
|
|
Badge,
|
|
Group,
|
|
Paper,
|
|
Progress,
|
|
SimpleGrid,
|
|
Stack,
|
|
Table,
|
|
Text,
|
|
} from "@mantine/core";
|
|
import { AlertTriangle, Train } from "lucide-react";
|
|
|
|
import type { DeferredBookingRow, FleetAvailabilityRow } from "@/types/trainScheduling";
|
|
|
|
export function FleetAvailabilitySummary({
|
|
fleetAvailability = [],
|
|
deferredBookings = [],
|
|
}: {
|
|
fleetAvailability?: FleetAvailabilityRow[];
|
|
deferredBookings?: DeferredBookingRow[];
|
|
}) {
|
|
if (!fleetAvailability.length && !deferredBookings.length) return null;
|
|
|
|
const totalNeeded = fleetAvailability.reduce((sum, row) => sum + row.needed, 0);
|
|
const totalAvailable = fleetAvailability.reduce((sum, row) => sum + row.available, 0);
|
|
const totalShortfall = fleetAvailability.reduce((sum, row) => sum + row.shortfall, 0);
|
|
const fillRate =
|
|
totalNeeded > 0 ? Math.round((Math.min(totalAvailable, totalNeeded) / totalNeeded) * 100) : 100;
|
|
|
|
return (
|
|
<Paper p="md" radius="xl" withBorder>
|
|
<Stack gap="md">
|
|
<Group justify="space-between" align="flex-start" wrap="wrap">
|
|
<Group gap="xs">
|
|
<Train size={18} />
|
|
<Stack gap={2}>
|
|
<Text fw={600} size="sm">
|
|
Fleet wagon availability
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
Plan is capped to available physical wagons by type
|
|
</Text>
|
|
</Stack>
|
|
</Group>
|
|
<Badge variant="light" color={totalShortfall > 0 ? "yellow" : "green"}>
|
|
{fillRate}% fleet coverage
|
|
</Badge>
|
|
</Group>
|
|
|
|
{totalNeeded > 0 ? (
|
|
<Stack gap={6}>
|
|
<Group justify="space-between">
|
|
<Text size="xs" c="dimmed">
|
|
{Math.min(totalAvailable, totalNeeded)} of {totalNeeded} wagon slots can be filled
|
|
</Text>
|
|
</Group>
|
|
<Progress
|
|
value={fillRate}
|
|
size="sm"
|
|
radius="xl"
|
|
color={totalShortfall > 0 ? "yellow" : "green"}
|
|
/>
|
|
</Stack>
|
|
) : null}
|
|
|
|
{fleetAvailability.length > 0 ? (
|
|
<Table striped highlightOnHover withTableBorder>
|
|
<Table.Thead>
|
|
<Table.Tr>
|
|
<Table.Th>Wagon type</Table.Th>
|
|
<Table.Th>Needed</Table.Th>
|
|
<Table.Th>Available</Table.Th>
|
|
<Table.Th>Shortfall</Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>
|
|
{fleetAvailability.map((row) => (
|
|
<Table.Tr key={row.wagonTypeId}>
|
|
<Table.Td>{row.wagonTypeCode}</Table.Td>
|
|
<Table.Td>{row.needed}</Table.Td>
|
|
<Table.Td>{row.available}</Table.Td>
|
|
<Table.Td>
|
|
{row.shortfall > 0 ? (
|
|
<Badge color="red" variant="light" size="sm">
|
|
{row.shortfall}
|
|
</Badge>
|
|
) : (
|
|
<Text size="sm" c="green">
|
|
0
|
|
</Text>
|
|
)}
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
))}
|
|
</Table.Tbody>
|
|
</Table>
|
|
) : null}
|
|
|
|
{totalShortfall > 0 || deferredBookings.length > 0 ? (
|
|
<Alert color="yellow" variant="light" radius="lg" icon={<AlertTriangle size={16} />}>
|
|
<Text size="sm">
|
|
Train will depart with available wagons only.
|
|
{deferredBookings.length
|
|
? ` ${deferredBookings.length} booking(s) will wait for the next train.`
|
|
: ""}
|
|
</Text>
|
|
</Alert>
|
|
) : null}
|
|
|
|
{deferredBookings.length > 0 ? (
|
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="sm">
|
|
{deferredBookings.map((booking) => (
|
|
<Paper key={booking.id} p="sm" radius="lg" withBorder bg="gray.0">
|
|
<Group justify="space-between" align="flex-start" wrap="nowrap">
|
|
<Stack gap={2}>
|
|
<Text size="sm" fw={600}>
|
|
{booking.reference}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
{booking.reason}
|
|
</Text>
|
|
</Stack>
|
|
<Badge variant="light" color="orange" size="sm">
|
|
Next train
|
|
</Badge>
|
|
</Group>
|
|
</Paper>
|
|
))}
|
|
</SimpleGrid>
|
|
) : null}
|
|
</Stack>
|
|
</Paper>
|
|
);
|
|
}
|