Files
edr-platform/apps/edr-freight-web/backoffice/src/components/trainScheduling/FleetAvailabilitySummary.tsx
2026-06-09 23:25:52 +00:00

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