import { Button, Group, Modal, MultiSelect, Select, Stack, Text, TextInput, Textarea, } from "@mantine/core"; import { useMutation, useQuery } from "@tanstack/react-query"; import { isAxiosError } from "axios"; 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; }; /** * Step one of the Train Builder: give the train its operator code, pick the * yard it is being assembled in, and couple at least two locomotives from that * yard. Wagons are attached afterwards on the composition page. */ export default function BuildTrainModal({ opened, onClose, onBuilt }: BuildTrainModalProps) { const { toast } = useToast(); const [code, setCode] = useState(""); const [trainName, setTrainName] = useState(""); const [yardId, setYardId] = useState(""); const [locomotiveIds, setLocomotiveIds] = useState([]); const [notes, setNotes] = useState(""); const yardsQuery = useQuery(api.routes.yards.queryOptions({ staleTime: 5 * 60_000 })); // Only serviceable locomotives standing in the selected yard can be coupled. const locomotivesQuery = useQuery( api.locomotives.listFiltered.queryOptions({ input: { filters: { status: "AVAILABLE", currentYardId: yardId } }, enabled: Boolean(yardId), }), ); const build = useMutation(api.trainBuilder.build.mutationOptions()); // A locomotive belongs to one yard — switching yards invalidates the pick. useEffect(() => { setLocomotiveIds([]); }, [yardId]); useEffect(() => { if (!opened) { setCode(""); setTrainName(""); setYardId(""); setLocomotiveIds([]); setNotes(""); } }, [opened]); const handleBuild = async () => { if (!code.trim() || !yardId || locomotiveIds.length < 2) { toast({ title: "Enter a train code, pick a yard, and couple at least two locomotives", variant: "destructive", }); return; } try { const composition = await build.mutateAsync({ code: code.trim(), currentYardId: yardId, locomotiveIds, ...(trainName.trim() ? { trainName: trainName.trim() } : {}), ...(notes.trim() ? { notes: notes.trim() } : {}), }); toast({ title: `Train ${composition.code} built` }); onClose(); onBuilt(composition); } catch (err) { toast({ title: "Build failed", description: parseError(err, "Could not build the train"), variant: "destructive", }); } }; const locomotiveOptions = (locomotivesQuery.data ?? []).map((loco) => ({ value: loco.id, label: `${loco.code}${loco.name ? ` — ${loco.name}` : ""} · pulls ${loco.maxPullWeightTons}T`, })); return ( Build a train} radius="lg" centered > A train is assembled in one yard: two or more locomotives plus wagons standing in that same yard. Wagons are attached on the next screen. setCode(e.currentTarget.value)} maxLength={32} /> setTrainName(e.currentTarget.value)} maxLength={100} />