import type { ReactNode } from "react"; import { Box, Group, Progress, Text, Tooltip } from "@mantine/core"; import type { BookingWindowPhase } from "@/types/trainScheduling"; import "./batchVisuals.css"; /** * Shared visual building blocks for the batch board surfaces (list + detail). * Everything keys off the same booking-pipeline color language so the two * pages read as one clean, professional product on a white surface. */ export type BatchCounts = { allocated: number; selectedForBatch: number; ready: number; waiting: number; pendingContract: number; expired: number; }; export const PIPELINE_STAGES: ReadonlyArray<{ key: keyof BatchCounts; label: string; color: string; hint: string; }> = [ { key: "allocated", label: "Allocated", color: "edr-green", hint: "Assigned to wagons on the train", }, { key: "selectedForBatch", label: "Selected", color: "orange", hint: "Picked by batch — customer notified to pay", }, { key: "ready", label: "Ready", color: "teal", hint: "Contract signed — waiting for batch pick", }, { key: "waiting", label: "Waiting", color: "blue", hint: "Paid — waiting for a slot", }, { key: "pendingContract", label: "Pending contract", color: "gray", hint: "Contract not signed yet", }, { key: "expired", label: "Expired", color: "red", hint: "Payment deadline missed", }, ]; export function totalBookingCount(counts: BatchCounts): number { return PIPELINE_STAGES.reduce((sum, stage) => sum + counts[stage.key], 0); } /** * Stacked booking-pipeline bar: one colored segment per batch state, with an * optional dot legend underneath. Reads as a single glanceable funnel. */ export function BookingPipeline({ counts, size = 10, showLegend = true, }: { counts: BatchCounts; size?: number; showLegend?: boolean; }) { const total = totalBookingCount(counts); const stages = PIPELINE_STAGES.filter((s) => counts[s.key] > 0); if (!total) { return ( No bookings yet ); } return ( {stages.map((stage) => ( ))} {showLegend ? ( {stages.map((stage) => ( {counts[stage.key]} {" "} {stage.label.toLowerCase()} ))} ) : null} ); } const WINDOW_META: Record = { OPEN: { color: "edr-green", label: "Window open", pulse: true }, FULL: { color: "orange", label: "Full", pulse: false }, CLOSED: { color: "gray", label: "Closed", pulse: false }, }; /** * Booking-window status pill with a status dot (pulsing while OPEN), styled for * a clean white surface. */ export function WindowStatusPill({ status }: { status: string }) { const meta = WINDOW_META[status] ?? { color: "gray", label: status, pulse: false }; return ( {meta.label} ); } /** * Small neutral info chip used in the page headers (date, loco, status, …). * Clean light surface that reads well on white. */ export function HeroChip({ icon, children, }: { icon?: ReactNode; children: ReactNode; }) { return ( {icon} {children} ); } const PHASE_META: Record< BookingWindowPhase, { color: string; label: string; pulse: boolean } > = { PRE_WINDOW: { color: "gray", label: "Pre-window", pulse: false }, OPEN: { color: "edr-green", label: "Booking open", pulse: true }, DOC_REVIEW: { color: "yellow", label: "Doc review", pulse: true }, PAYMENT: { color: "blue", label: "Payment", pulse: true }, CLOSED_FOR_DAY: { color: "dark", label: "Closed for day", pulse: false }, DONE: { color: "dark", label: "Done", pulse: false }, }; /** * Import booking-cycle phase pill (OPEN → DOC_REVIEW → PAYMENT → …) with an * optional cycle number. Same visual language as `WindowStatusPill`. */ export function WindowPhasePill({ phase, cycleNo, size = "md", }: { phase: BookingWindowPhase; cycleNo?: number; size?: "sm" | "md"; }) { const meta = PHASE_META[phase] ?? { color: "gray", label: phase, pulse: false, }; const compact = size === "sm"; return ( {meta.label} {cycleNo && cycleNo > 1 ? ` · cycle ${cycleNo}` : ""} ); }