import { Badge, Card, Group, Progress, SimpleGrid, Stack, Text, ThemeIcon } from "@mantine/core";
import { Box, Package } from "lucide-react";
import type { TrainScheduleWagonAllocation, WagonPlanRow } from "@/types/trainScheduling";
type WagonSlot = (WagonPlanRow & { physicalWagonNumber?: string | null }) | {
sequenceNo: number;
capacityTons: number;
assignedWeightTons: number;
tareWeightTons?: number | null;
slotLoadType?: string;
wagonType?: { code: string } | null;
wagonTypeCode?: string;
physicalWagonNumber?: string | null;
allocations?: TrainScheduleWagonAllocation[] | Array<{
id?: string;
bookingId: string;
bookingReference?: string | null;
allocatedWeightTons: number;
loadType?: string | null;
containerItems?: Array<{ containerNumber: string | null; grossWeightTons?: number | null }>;
bulkLoad?: { weightTons: number; cargoDescription: string | null } | null;
}>;
};
const round1 = (n: number) => Math.round(n * 10) / 10;
function loadTypeColor(loadType: string | undefined, freightType?: string | null) {
const normalized = loadType?.toUpperCase() ?? "";
if (normalized.includes("BULK")) return "orange";
if (normalized.includes("CONTAINER")) return "cyan";
return freightType === "BULK" ? "orange" : "cyan";
}
function slotLabel(slot: WagonSlot, freightType?: string | null) {
if ("slotLoadType" in slot && slot.slotLoadType) return slot.slotLoadType;
const fromAlloc = slot.allocations?.[0]?.loadType?.toString().toUpperCase();
if (fromAlloc) return fromAlloc;
if (freightType === "MIXED") return "MIXED";
return freightType ?? "SLOT";
}
function wagonTypeLabel(slot: WagonSlot) {
if ("wagonType" in slot && slot.wagonType?.code) return slot.wagonType.code;
if ("wagonTypeCode" in slot && slot.wagonTypeCode) return slot.wagonTypeCode;
return null;
}
export function WagonPlanGrid({
wagonPlan,
freightType,
}: {
wagonPlan: WagonSlot[];
freightType?: string | null;
}) {
if (!wagonPlan?.length) {
return (
No wagon plan yet
Select bookings and run Preview plan to generate wagon slots and
allocations.
);
}
// GROSS on both sides: cargo + tare vs rated payload + tare.
const totalTare = round1(
wagonPlan.reduce((sum, w) => sum + (Number(w.tareWeightTons) || 0), 0),
);
const totalCapacity = round1(
wagonPlan.reduce((sum, w) => sum + w.capacityTons, 0) + totalTare,
);
const totalAssigned = round1(
wagonPlan.reduce((sum, w) => sum + w.assignedWeightTons, 0) + totalTare,
);
const usedSlots = wagonPlan.filter((w) => (w.allocations?.length ?? 0) > 0).length;
const isBulk = freightType === "BULK" || wagonPlan.every((w) => w.slotLoadType === "BULK" || (!w.slotLoadType && w.allocations?.[0]?.loadType === "Bulk"));
return (
{wagonPlan.length} wagons · {usedSlots} in use
{isBulk ? (
Gross: {totalAssigned} / {totalCapacity}T
) : null}
{wagonPlan.map((wagon) => {
const seq = wagon.sequenceNo;
const tare = Number(wagon.tareWeightTons) || 0;
const capacity = round1(wagon.capacityTons + tare);
const assigned = round1(wagon.assignedWeightTons + tare);
const allocations = wagon.allocations ?? [];
const utilization = capacity > 0 ? Math.min(100, Math.round((assigned / capacity) * 100)) : 0;
const label = slotLabel(wagon, freightType);
const typeCode = wagonTypeLabel(wagon);
return (
{wagon.physicalWagonNumber ?? `Wagon #${seq}`}
{[typeCode, `Pos ${seq}`].filter(Boolean).join(" · ")}
{label}
{label === "BULK" ? (
Capacity
{assigned} / {capacity}T
) : null}
{allocations.length ? (
allocations.map((alloc, index) => (
{alloc.bookingReference ?? alloc.bookingId}
{label === "BULK" ? (
{alloc.allocatedWeightTons}T cargo
) : null}
{"containerItems" in alloc && alloc.containerItems?.length ? (
{alloc.containerItems.length} container{alloc.containerItems.length > 1 ? "s" : ""}
) : null}
{"bulkLoad" in alloc && alloc.bulkLoad ? (
Bulk · {alloc.bulkLoad.weightTons}T
{alloc.bulkLoad.cargoDescription
? ` — ${alloc.bulkLoad.cargoDescription}`
: ""}
) : null}
))
) : (
Empty slot
)}
);
})}
);
}