This commit is contained in:
Marshal
2026-06-12 12:59:01 +00:00
parent db39e77e90
commit 6b79348074
6 changed files with 166 additions and 399 deletions

View File

@@ -13,14 +13,12 @@ import {
Stack,
Text,
ThemeIcon,
Title,
} from "@mantine/core";
import {
AlertTriangle,
ArrowRight,
CalendarDays,
Inbox,
LayoutGrid,
Package,
RefreshCw,
Ruler,
@@ -323,82 +321,38 @@ export default function BatchBoardPage() {
<Container fluid py="lg" px="xl">
<Breadcrumbs items={[{ label: "Operations" }, { label: "Batch board" }]} />
{/* Hero — clean white */}
<Paper
radius="xl"
p="xl"
mt="md"
withBorder
style={{
borderColor: "var(--mantine-color-gray-2)",
background: "white",
boxShadow: "0 1px 3px rgba(15,23,42,0.04)",
}}
>
<Group justify="space-between" align="flex-start" wrap="wrap" gap="md">
<Group gap="md" align="center" wrap="nowrap">
<ThemeIcon size={56} radius="lg" variant="light" color="green">
<LayoutGrid size={28} />
</ThemeIcon>
<Stack gap={2}>
<Group gap={8}>
<Text
size="xs"
fw={700}
c="green.7"
tt="uppercase"
style={{ letterSpacing: 1.2 }}
>
Allocation · Live
</Text>
<Box
w={7}
h={7}
className="bb-pulse-dot"
style={{ borderRadius: 999, background: FREIGHT_BRAND }}
/>
</Group>
<Title order={2} fw={800} style={{ color: "#0f172a", letterSpacing: 0.2 }}>
Batch board
</Title>
<Text size="sm" c="dimmed" maw={580}>
Active schedules open a card to see EAT 3-hour batch windows, bookings,
and live wagon allocation.
</Text>
</Stack>
</Group>
<Button
variant="default"
radius="lg"
leftSection={<RefreshCw size={16} />}
loading={isFetching}
onClick={() => void refetch()}
>
Refresh
</Button>
</Group>
<Group justify="flex-end" mt="md">
<Button
variant="default"
radius="lg"
leftSection={<RefreshCw size={16} />}
loading={isFetching}
onClick={() => void refetch()}
>
Refresh
</Button>
</Group>
<SimpleGrid cols={{ base: 1, xs: 3 }} spacing="md" mt="lg">
<StatTile
icon={Train}
label="Active schedules"
value={isLoading ? "…" : schedules.length}
hint="on the board right now"
/>
<StatTile
icon={CalendarDays}
label="Open windows"
value={isLoading ? "…" : summary.openWindows}
hint="accepting bookings"
/>
<StatTile
icon={Package}
label="Bookings in play"
value={isLoading ? "…" : summary.totalBookings}
hint={`${summary.totalWagons} wagons allocated`}
/>
</SimpleGrid>
</Paper>
<SimpleGrid cols={{ base: 1, xs: 3 }} spacing="md" mt="md">
<StatTile
icon={Train}
label="Active schedules"
value={isLoading ? "…" : schedules.length}
hint="on the board right now"
/>
<StatTile
icon={CalendarDays}
label="Open windows"
value={isLoading ? "…" : summary.openWindows}
hint="accepting bookings"
/>
<StatTile
icon={Package}
label="Bookings in play"
value={isLoading ? "…" : summary.totalBookings}
hint={`${summary.totalWagons} wagons allocated`}
/>
</SimpleGrid>
{isLoading ? (
<SimpleGrid cols={{ base: 1, md: 2, lg: 3, xl: 4 }} spacing="lg" mt="lg">

View File

@@ -33,7 +33,6 @@ import {
PlayCircle,
RefreshCw,
Ruler,
Train,
TrainFront,
Weight,
XCircle,
@@ -540,69 +539,50 @@ export default function BatchScheduleDetailPage() {
]}
/>
{/* Hero — clean white */}
<Paper
radius="xl"
p="xl"
mt="md"
withBorder
style={{
borderColor: "var(--mantine-color-gray-2)",
background: "white",
boxShadow: "0 1px 3px rgba(15,23,42,0.04)",
}}
>
<Stack gap="lg">
<Stack gap="lg" mt="md">
<Group justify="space-between" align="flex-start" wrap="wrap" gap="md">
<Group gap="md" align="flex-start" wrap="nowrap" style={{ minWidth: 0 }}>
<Button
variant="subtle"
color="gray"
radius="md"
px="sm"
leftSection={<ArrowLeft size={16} />}
onClick={() => navigate("/dashboard/operations/batch-board")}
>
Back
</Button>
<Group gap="sm" wrap="nowrap" style={{ minWidth: 0 }}>
<ThemeIcon size={52} radius="md" variant="light" color="green">
<Train size={26} />
</ThemeIcon>
<Stack gap={7} style={{ minWidth: 0 }}>
<Group gap="sm" wrap="wrap">
<Title order={2} fw={800} style={{ color: "#0f172a", letterSpacing: 0.2 }}>
{data.trainNumber ?? data.routeName ?? "Schedule"}
</Title>
<WindowStatusPill status={data.bookingWindowStatus} />
<HeroChip>{data.status}</HeroChip>
</Group>
<RouteCorridor origin={data.origin} destination={data.destination} />
<Group gap={6} wrap="wrap">
<HeroChip icon={<CalendarDays size={12} />}>
{data.scheduleDate
? new Intl.DateTimeFormat("en-GB", {
weekday: "short",
day: "2-digit",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Africa/Addis_Ababa",
}).format(new Date(data.scheduleDate)) + " EAT"
: "No date"}
</HeroChip>
{data.locomotive ? (
<HeroChip icon={<TrainFront size={12} />}>
Loco {data.locomotive.code} · {fmtTons(data.locomotive.maxPullWeightTons)} ·{" "}
{data.locomotive.maxTrainLengthMeters} m
</HeroChip>
) : null}
</Group>
</Stack>
<Stack gap={9} style={{ minWidth: 0 }}>
<Group gap="sm" wrap="wrap" align="center">
<Button
variant="subtle"
color="gray"
radius="md"
px="sm"
leftSection={<ArrowLeft size={16} />}
onClick={() => navigate("/dashboard/operations/batch-board")}
>
Back
</Button>
<Title order={2} fw={800} style={{ color: "#0f172a", letterSpacing: 0.2 }}>
{data.trainNumber ?? data.routeName ?? "Schedule"}
</Title>
<WindowStatusPill status={data.bookingWindowStatus} />
<HeroChip>{data.status}</HeroChip>
</Group>
</Group>
<RouteCorridor origin={data.origin} destination={data.destination} />
<Group gap={6} wrap="wrap">
<HeroChip icon={<CalendarDays size={12} />}>
{data.scheduleDate
? new Intl.DateTimeFormat("en-GB", {
weekday: "short",
day: "2-digit",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Africa/Addis_Ababa",
}).format(new Date(data.scheduleDate)) + " EAT"
: "No date"}
</HeroChip>
{data.locomotive ? (
<HeroChip icon={<TrainFront size={12} />}>
Loco {data.locomotive.code} · {fmtTons(data.locomotive.maxPullWeightTons)} ·{" "}
{data.locomotive.maxTrainLengthMeters} m
</HeroChip>
) : null}
</Group>
</Stack>
<Group gap="sm">
<Button
@@ -677,8 +657,7 @@ export default function BatchScheduleDetailPage() {
sub={`${data.counts.allocated} allocated · ${data.counts.expired} expired`}
/>
</SimpleGrid>
</Stack>
</Paper>
</Stack>
{/* Booking pipeline */}
<Paper

View File

@@ -8,14 +8,12 @@ import {
Card,
Group,
Modal,
Paper,
Select,
SimpleGrid,
Stack,
Text,
TextInput,
ThemeIcon,
Title,
} from "@mantine/core";
import { ArrowRight, CalendarClock, Navigation, Send, Train, Weight } from "lucide-react";
@@ -345,53 +343,24 @@ export default function TrainScheduleV2ListPage() {
return (
<Stack gap="lg">
{/* Hero banner */}
<Paper
radius="xl"
p="xl"
style={{
position: "relative",
overflow: "hidden",
background: "#ffffff",
border: "1px solid var(--mantine-color-gray-2)",
boxShadow: "0 1px 3px rgba(15,23,42,0.04)",
}}
>
<Stack gap="lg" style={{ position: "relative" }}>
<Group justify="space-between" align="flex-start" wrap="wrap">
<Group gap="md" align="center" wrap="nowrap">
<ThemeIcon size={56} radius="lg" variant="light" color="green">
<Train size={28} />
</ThemeIcon>
<Stack gap={4}>
<Title order={2} fw={700} style={{ color: "#0f172a" }}>
Train Schedules
</Title>
<Text size="sm" c="dimmed" maw={520}>
Plan departures, allocate bookings, and dispatch trains across
every corridor.
</Text>
</Stack>
</Group>
<Button
size="md"
radius="lg"
color="green"
leftSection={<Train size={18} />}
onClick={() => setCreateOpen(true)}
>
New schedule
</Button>
</Group>
<Group justify="flex-end">
<Button
size="md"
radius="lg"
color="green"
leftSection={<Train size={18} />}
onClick={() => setCreateOpen(true)}
>
New schedule
</Button>
</Group>
<SimpleGrid cols={{ base: 2, sm: 4 }} spacing="md">
<StatTile icon={Train} label="Total trains" value={stats.total} />
<StatTile icon={CalendarClock} label="Scheduled" value={stats.scheduled} />
<StatTile icon={Send} label="Dispatched" value={stats.dispatched} />
<StatTile icon={Weight} label="Planned load" value={`${Math.round(stats.weight)}T`} />
</SimpleGrid>
</Stack>
</Paper>
<SimpleGrid cols={{ base: 2, sm: 4 }} spacing="md">
<StatTile icon={Train} label="Total trains" value={stats.total} />
<StatTile icon={CalendarClock} label="Scheduled" value={stats.scheduled} />
<StatTile icon={Send} label="Dispatched" value={stats.dispatched} />
<StatTile icon={Weight} label="Planned load" value={`${Math.round(stats.weight)}T`} />
</SimpleGrid>
<Card radius="lg" padding={0} withBorder style={{ borderColor: "var(--mantine-color-gray-2)" }}>
<Stack gap={0}>