mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 19:58:11 +00:00
104 lines
3.4 KiB
TypeScript
104 lines
3.4 KiB
TypeScript
import { Alert, Button, Group, Modal, Select, Stack, Text } from "@mantine/core";
|
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
import { isAxiosError } from "axios";
|
|
import { MapPin } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
|
|
import { api } from "@/services/api";
|
|
import type { TrainComposition } from "@/services/trainBuilder.service";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
|
|
const parseError = (error: unknown, fallback: string) => {
|
|
if (isAxiosError(error)) {
|
|
const message = error.response?.data?.message;
|
|
if (Array.isArray(message)) return message.join(", ");
|
|
if (typeof message === "string") return message;
|
|
}
|
|
return fallback;
|
|
};
|
|
|
|
/**
|
|
* Relocate the train to another yard. The consist moves as one unit — every
|
|
* coupled locomotive and wagon follows, so their current yards always match
|
|
* the train's.
|
|
*/
|
|
export default function ChangeYardModal({ composition, opened, onClose }: ChangeYardModalProps) {
|
|
const { toast } = useToast();
|
|
const [yardId, setYardId] = useState("");
|
|
|
|
const yardsQuery = useQuery(api.routes.yards.queryOptions({ staleTime: 5 * 60_000 }));
|
|
const setYard = useMutation(api.trainBuilder.setYard.mutationOptions());
|
|
|
|
useEffect(() => {
|
|
if (opened) setYardId(composition?.currentYard?.id ?? "");
|
|
}, [opened, composition]);
|
|
|
|
const handleSave = async () => {
|
|
if (!composition || !yardId) return;
|
|
try {
|
|
await setYard.mutateAsync({ id: composition.id, currentYardId: yardId });
|
|
toast({ title: "Train relocated" });
|
|
onClose();
|
|
} catch (err) {
|
|
toast({
|
|
title: "Relocation failed",
|
|
description: parseError(err, "Could not change the yard"),
|
|
variant: "destructive",
|
|
});
|
|
}
|
|
};
|
|
|
|
const memberCount =
|
|
(composition?.locomotives.length ?? 0) + (composition?.totals.wagonCount ?? 0);
|
|
|
|
return (
|
|
<Modal
|
|
opened={opened}
|
|
onClose={onClose}
|
|
title={<Text fw={600}>Change yard — train {composition?.code}</Text>}
|
|
radius="lg"
|
|
centered
|
|
>
|
|
<Stack gap="md">
|
|
<Alert color="yellow" icon={<MapPin size={16} />}>
|
|
The whole consist moves with the train: {composition?.locomotives.length ?? 0}{" "}
|
|
locomotive{(composition?.locomotives.length ?? 0) === 1 ? "" : "s"} and{" "}
|
|
{composition?.totals.wagonCount ?? 0} wagon
|
|
{(composition?.totals.wagonCount ?? 0) === 1 ? "" : "s"} ({memberCount} vehicles)
|
|
are relocated so their current yard always matches the train's. Wagon moves are
|
|
recorded in the movement ledger.
|
|
</Alert>
|
|
<Select
|
|
label="New yard"
|
|
placeholder="Select yard"
|
|
data={(yardsQuery.data ?? []).map((y) => ({
|
|
value: y.id,
|
|
label: y.label ?? y.code,
|
|
}))}
|
|
value={yardId || null}
|
|
onChange={(v) => setYardId(v ?? "")}
|
|
searchable
|
|
/>
|
|
<Group justify="flex-end">
|
|
<Button variant="default" onClick={onClose}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
loading={setYard.isPending}
|
|
disabled={!yardId || yardId === composition?.currentYard?.id}
|
|
onClick={handleSave}
|
|
>
|
|
Relocate train
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
export interface ChangeYardModalProps {
|
|
composition: TrainComposition | null;
|
|
opened: boolean;
|
|
onClose: () => void;
|
|
}
|