import { Freight } from "@edr/types"; import { Badge, Button, Checkbox, Group, ScrollArea, Select, Stack, Text, TextInput, } from "@mantine/core"; import { useQuery } from "@tanstack/react-query"; import { Plus, Search } from "lucide-react"; import { useMemo, useState } from "react"; import { api } from "@/services/api"; /** * AVAILABLE wagons standing in the train's own yard — the only ones that can * be coupled. Pick any number and append them to the consist. */ export default function AvailableWagonsPanel({ yardId, yardLabel, onAssign, assigning, exportTrainNumber, importTrainNumber, }: AvailableWagonsPanelProps) { const [search, setSearch] = useState(""); const [typeFilter, setTypeFilter] = useState("ALL"); const [runOnly, setRunOnly] = useState(false); const [selected, setSelected] = useState([]); // The train's own run, e.g. "8001-8002" — only offered when the train has one. const runLabel = exportTrainNumber ? `${exportTrainNumber}${importTrainNumber ? `-${importTrainNumber}` : ""}` : null; const wagonsQuery = useQuery( api.wagons.list.queryOptions({ input: { filters: { status: Freight.WagonStatus.Available, currentYardId: yardId, // Loose wagons only — one already on another train cannot be coupled. unassigned: true, }, }, enabled: Boolean(yardId), }), ); const wagons = useMemo(() => { const q = search.trim().toLowerCase(); return (wagonsQuery.data ?? []).filter((wagon) => { if (typeFilter !== "ALL" && wagon.wagonTypeId !== typeFilter) return false; // Rostered to this train's run — match on the export run, which fixes the // import run anyway. if (runOnly && wagon.exportTrainNumber !== exportTrainNumber) return false; if (q && !wagon.wagonNumber.toLowerCase().includes(q)) return false; return true; }); }, [wagonsQuery.data, search, typeFilter, runOnly, exportTrainNumber]); const runMatchCount = useMemo( () => exportTrainNumber ? (wagonsQuery.data ?? []).filter( (w) => w.exportTrainNumber === exportTrainNumber, ).length : 0, [wagonsQuery.data, exportTrainNumber], ); const typeOptions = useMemo(() => { const byId = new Map(); for (const wagon of wagonsQuery.data ?? []) { if (wagon.wagonType) { // e.g. "Flat wagon (NW5)" — name with its type code. byId.set( wagon.wagonType.id, wagon.wagonType.code ? `${wagon.wagonType.name} (${wagon.wagonType.code})` : wagon.wagonType.name, ); } } return [ { value: "ALL", label: "All types" }, ...[...byId.entries()].map(([value, label]) => ({ value, label })), ]; }, [wagonsQuery.data]); const toggle = (wagonId: string, checked: boolean) => { setSelected((prev) => checked ? [...prev, wagonId] : prev.filter((id) => id !== wagonId), ); }; const allSelected = wagons.length > 0 && wagons.every((w) => selected.includes(w.id)); const someSelected = wagons.some((w) => selected.includes(w.id)); const toggleAll = (checked: boolean) => { setSelected((prev) => { if (checked) { const ids = new Set(prev); wagons.forEach((w) => ids.add(w.id)); return [...ids]; } const visible = new Set(wagons.map((w) => w.id)); return prev.filter((id) => !visible.has(id)); }); }; const handleAssign = () => { if (!selected.length) return; onAssign(selected); setSelected([]); }; return ( } value={search} onChange={(e) => setSearch(e.currentTarget.value)} />