mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 12:41:04 +00:00
- Added functionality to cancel contracts, allowing users to provide a reason for cancellation. - Updated contract statuses to include SUSPENDED and changed CLOSED to COMPLETED. - Enhanced the UI to reflect the new cancellation option and updated messaging for contract statuses. - Refactored contract booking actions to accommodate changes in booking logic for ONE_TIME and GENERAL contracts. - Removed clearance document management from the contract detail page, as it is now handled per booking. - Introduced a SQL script to reset bookings and train schedules for development purposes.
145 lines
4.4 KiB
TypeScript
145 lines
4.4 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import { Badge, Center, Group, Loader, Modal, Text, Timeline } from "@mantine/core";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { ArrowRight, PackageCheck, TrainFront, Wrench } from "lucide-react";
|
|
|
|
import { api } from "@/services/api";
|
|
import type { FleetRecord } from "@/services/fleet/fleet.service";
|
|
import type { WagonMovementRecord } from "@/services/wagon.service";
|
|
|
|
export interface WagonMovementHistoryModalProps {
|
|
opened: boolean;
|
|
onClose: () => void;
|
|
record: FleetRecord | null;
|
|
}
|
|
|
|
const asObj = (r: FleetRecord | null) => (r ?? {}) as Record<string, unknown>;
|
|
|
|
/** Chip style per wagon_movements ledger kind. */
|
|
const KIND_META: Record<string, { label: string; color: string; icon: ReactNode }> = {
|
|
LOADED: {
|
|
label: "Loaded leg",
|
|
color: "edr-green",
|
|
icon: <PackageCheck size={14} />,
|
|
},
|
|
EMPTY_REPOSITION: {
|
|
label: "Empty reposition",
|
|
color: "blue",
|
|
icon: <TrainFront size={14} />,
|
|
},
|
|
MANUAL: {
|
|
label: "Manual move",
|
|
color: "orange",
|
|
icon: <Wrench size={14} />,
|
|
},
|
|
MAINTENANCE: {
|
|
label: "Sent to maintenance",
|
|
color: "red",
|
|
icon: <Wrench size={14} />,
|
|
},
|
|
};
|
|
|
|
const yardLabel = (
|
|
yard: { label?: string; code?: string } | null | undefined,
|
|
yardId: string | null,
|
|
) => yard?.label ?? yard?.code ?? yardId ?? "Unknown";
|
|
|
|
const fmt = (iso: string) => {
|
|
const d = new Date(iso);
|
|
return Number.isNaN(d.getTime()) ? iso : d.toLocaleString();
|
|
};
|
|
|
|
/**
|
|
* Movement ledger for one wagon: every relocation between yards — booking legs,
|
|
* empty reposition rides, and manual staff corrections — newest first.
|
|
*/
|
|
const WagonMovementHistoryModal = ({
|
|
opened,
|
|
onClose,
|
|
record,
|
|
}: WagonMovementHistoryModalProps) => {
|
|
const r = asObj(record);
|
|
const id = r.id ? String(r.id) : "";
|
|
const wagonNumber = r.wagonNumber ? String(r.wagonNumber) : "";
|
|
|
|
const { data, isLoading } = useQuery(
|
|
api.wagons.movements.queryOptions({
|
|
input: { id },
|
|
enabled: opened && Boolean(id),
|
|
}),
|
|
);
|
|
|
|
const movements: WagonMovementRecord[] = data ?? [];
|
|
|
|
return (
|
|
<Modal
|
|
opened={opened}
|
|
onClose={onClose}
|
|
title={<Text fw={600}>{`Wagon history — ${wagonNumber}`.trim()}</Text>}
|
|
radius="lg"
|
|
size="lg"
|
|
centered
|
|
>
|
|
{isLoading ? (
|
|
<Center py="xl">
|
|
<Loader size="sm" />
|
|
</Center>
|
|
) : movements.length === 0 ? (
|
|
<Text c="dimmed" ta="center" py="lg" size="sm">
|
|
No movements recorded yet. Every yard-to-yard move appears here — a
|
|
booking's loaded leg, an empty reposition ride, or a manual correction.
|
|
</Text>
|
|
) : (
|
|
<Timeline active={movements.length} bulletSize={24} lineWidth={2}>
|
|
{movements.map((movement) => {
|
|
const meta = KIND_META[movement.kind] ?? {
|
|
label: movement.kind,
|
|
color: "gray",
|
|
icon: <TrainFront size={14} />,
|
|
};
|
|
const from = yardLabel(movement.fromYard, movement.fromYardId);
|
|
const to = yardLabel(movement.toYard, movement.toYardId);
|
|
return (
|
|
<Timeline.Item
|
|
key={movement.id}
|
|
bullet={meta.icon}
|
|
title={
|
|
<Group gap={6} wrap="nowrap">
|
|
<Text size="sm" fw={600}>
|
|
{from}
|
|
</Text>
|
|
{/* Status events (maintenance) sit in one yard — an arrow
|
|
pointing at the same yard reads as a broken row. */}
|
|
{movement.fromYardId !== movement.toYardId && (
|
|
<>
|
|
<ArrowRight size={13} />
|
|
<Text size="sm" fw={600}>
|
|
{to}
|
|
</Text>
|
|
</>
|
|
)}
|
|
<Badge size="xs" variant="light" color={meta.color}>
|
|
{meta.label}
|
|
</Badge>
|
|
</Group>
|
|
}
|
|
>
|
|
{movement.note && (
|
|
<Text size="sm" c="dimmed">
|
|
{movement.note}
|
|
</Text>
|
|
)}
|
|
<Text size="xs" mt={4} c="dimmed">
|
|
{fmt(movement.occurredAt)}
|
|
</Text>
|
|
</Timeline.Item>
|
|
);
|
|
})}
|
|
</Timeline>
|
|
)}
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default WagonMovementHistoryModal;
|