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 95 ? "red" : utilization > 80 ? "yellow" : "edr-green"} /> ) : 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 )} ); })} ); }