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}` : ""}
);
}