import type { ColumnDef } from "@edr/ui-common"; import { Box, Button, Card, Container, Group, Modal, Select, Stack, Text, Title } from "@mantine/core"; import { useMutation, useQuery } from "@tanstack/react-query"; import { api } from "@/services/api"; import Breadcrumbs from "@/components/ui/Breadcrumbs"; import { Plus } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { Navigate, useLocation } from "react-router-dom"; import FleetCardGrid from "@/components/fleet/FleetCardGrid"; import FleetFormDialog from "@/components/fleet/FleetFormDialog"; import FleetRecordActions from "@/components/fleet/FleetRecordActions"; import FleetToolbar from "@/components/fleet/FleetToolbar"; import { formatFleetCell, registerFleetOptionLabels } from "@/components/fleet/fleetFormat"; import { useFleetViewMode } from "@/components/fleet/useFleetViewMode"; import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles"; import { useToast } from "@/hooks/use-toast"; import { FLEET_SELECT_NONE, getFleetResource, getFleetSlugFromPath, type FleetFormFieldDef, type FleetResourceSlug, } from "@/pages/fleet/config/resources"; import type { FleetListFilters, FleetRecord } from "@/services/fleet/fleet.service"; import { DataTable, DataTableFooter, usePagination } from "@edr/ui-common"; const DEFAULT_SLUG: FleetResourceSlug = "locomotives"; const FleetResourcePage = () => { const location = useLocation(); const slug = getFleetSlugFromPath(location.pathname) ?? DEFAULT_SLUG; const config = getFleetResource(slug); const { toast } = useToast(); const { pagination, setPagination } = usePagination({ pageSize: 10 }); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("ALL"); const [listFilterValues, setListFilterValues] = useState>({}); const [formOpen, setFormOpen] = useState(false); const [editing, setEditing] = useState(null); const [removeTarget, setRemoveTarget] = useState(null); const [assigningDriver, setAssigningDriver] = useState(null); const [selectedDriver, setSelectedDriver] = useState(""); const { viewMode, setViewMode } = useFleetViewMode(slug); const serverListFilters = useMemo((): FleetListFilters | undefined => { if (slug !== "wagons" && slug !== "locomotives") return undefined; const filters: FleetListFilters = {}; const status = listFilterValues.status; const currentYardId = listFilterValues.currentYardId; if (status && status !== "ALL") { (filters as { status?: string }).status = status; } if (currentYardId && currentYardId !== "ALL") { filters.currentYardId = currentYardId; } if (slug === "wagons" && search.trim()) { filters.search = search.trim(); } return filters; }, [slug, listFilterValues, search]); const { data: allRows = [], isLoading, isError, error } = useQuery( api.fleet.list.queryOptions({ input: { slug, filters: serverListFilters } }), ); const create = useMutation(api.fleet.create.mutationOptions()); const update = useMutation(api.fleet.update.mutationOptions()); const remove = useMutation(api.fleet.remove.mutationOptions()); const { data: wagonTypes = [], isLoading: wagonTypesLoading } = useQuery( api.wagonTypes.list.queryOptions(), ); const { data: containerTypes = [], isLoading: containerTypesLoading } = useQuery( api.containerTypes.list.queryOptions({ staleTime: Infinity }), ); const { data: cargoTypes = [], isLoading: cargoTypesLoading } = useQuery( api.cargoTypes.list.queryOptions({ staleTime: Infinity }), ); const { data: wagons = [], isLoading: wagonsLoading } = useQuery( api.wagons.list.queryOptions({ input: {} }), ); const { data: containers = [], isLoading: containersLoading } = useQuery( api.containers.list.queryOptions(), ); const { data: yards = [], isLoading: yardsLoading } = useQuery( api.routes.yards.queryOptions(), ); const { data: drivers = [] } = useQuery( api.fleet.list.queryOptions({ input: { slug: "drivers" } }), ); useEffect(() => { setPagination((prev) => ({ pageIndex: 0, pageSize: prev.pageSize })); setSearch(""); setStatusFilter("ALL"); setListFilterValues({}); }, [slug, setPagination]); useEffect(() => { setPagination((prev) => ({ pageIndex: 0, pageSize: prev.pageSize })); }, [search, listFilterValues, setPagination]); const hasStatusColumn = Boolean(config?.columns.some((col) => col.accessorKey === "status")); const usesServerListFilters = Boolean(config?.listFilters?.length); const statusFilterOptions = useMemo(() => { if (!hasStatusColumn || usesServerListFilters) return []; if (slug === "vehicles" || slug === "drivers") { return [ { value: "ALL", label: "All statuses" }, { value: "ACTIVE", label: "Active" }, { value: "INACTIVE", label: "Inactive" }, ]; } const statuses = new Set( allRows .map((row) => String((row as unknown as Record).status ?? "")) .filter(Boolean), ); return [ { value: "ALL", label: "All statuses" }, ...[...statuses].sort().map((status) => ({ value: status, label: status })), ]; }, [allRows, hasStatusColumn, usesServerListFilters, slug]); const dynamicOptions = useMemo(() => { const wagonTypeOpts = (wagonTypes as Array<{ id: string; code: string; name?: string }>).map( (t) => ({ value: t.id, label: `${t.code}${t.name ? ` - ${t.name}` : ""}` }), ); const containerTypeOpts = ( containerTypes as Array<{ id: string; label?: string; code?: string }> ).map((t) => ({ value: t.id, label: t.label ?? t.code ?? t.id })); const cargoTypeOpts = ( cargoTypes as Array<{ id: string; cargoTypeName?: string; code?: string }> ).map((t) => ({ value: t.id, label: t.cargoTypeName ?? t.code ?? t.id })); const wagonOpts = (wagons as Array<{ id: string; wagonNumber: string }>).map((w) => ({ value: w.id, label: w.wagonNumber, })); const containerOpts = (containers as Array<{ id: string; containerNumber: string }>).map( (c) => ({ value: c.id, label: c.containerNumber }), ); const yardOpts = (yards as Array<{ id: string; label?: string; code?: string }>).map( (y) => ({ value: y.id, label: y.label ?? y.code ?? y.id }), ); registerFleetOptionLabels("currentYardId", yardOpts); return { wagonTypes: wagonTypeOpts, containerTypes: containerTypeOpts, cargoTypes: [{ label: "None", value: FLEET_SELECT_NONE }, ...cargoTypeOpts], wagons: [{ label: "Unassigned", value: FLEET_SELECT_NONE }, ...wagonOpts], containers: containerOpts, yards: yardOpts, }; }, [wagonTypes, containerTypes, cargoTypes, wagons, containers, yards]); const listFilterSelects = useMemo(() => { if (!config?.listFilters?.length) return null; return config.listFilters.map((filter) => { const dynamicOpts = filter.dynamicOptions ? (dynamicOptions[filter.dynamicOptions] ?? []) : []; const staticOpts = filter.options?.map((opt) => ({ value: opt.value, label: opt.label })) ?? []; const opts = filter.dynamicOptions ? dynamicOpts : staticOpts; return { ...filter, value: listFilterValues[filter.key] ?? "ALL", data: [ { value: "ALL", label: filter.allLabel ?? `All ${filter.label.toLowerCase()}` }, ...opts, ], }; }); }, [config?.listFilters, listFilterValues, dynamicOptions]); useEffect(() => { registerFleetOptionLabels("wagonTypeId", dynamicOptions.wagonTypes); registerFleetOptionLabels("containerTypeId", dynamicOptions.containerTypes); registerFleetOptionLabels( "cargoTypeId", dynamicOptions.cargoTypes.filter((o) => o.value !== FLEET_SELECT_NONE), ); registerFleetOptionLabels("wagonId", dynamicOptions.wagons); registerFleetOptionLabels("containerId", dynamicOptions.containers); registerFleetOptionLabels("currentYardId", dynamicOptions.yards); }, [dynamicOptions]); const formFields = useMemo((): FleetFormFieldDef[] => { if (!config) return []; return config.formFields.map((field) => { if (!field.dynamicOptions) return field; const options = dynamicOptions[field.dynamicOptions] ?? []; return { ...field, type: "select" as const, options }; }); }, [config, dynamicOptions]); const selectOptionsLoading = wagonTypesLoading || containerTypesLoading || cargoTypesLoading || wagonsLoading || containersLoading || yardsLoading; const filteredRows = useMemo(() => { if (!config) return allRows; if (usesServerListFilters) return allRows; const term = search.trim().toLowerCase(); return allRows.filter((row) => { const record = row as unknown as Record; if (statusFilter !== "ALL" && String(record.status ?? "") !== statusFilter) { return false; } if (!term) return true; return config.searchKeys.some((key) => String(record[key] ?? "") .toLowerCase() .includes(term), ); }); }, [allRows, search, statusFilter, config, usesServerListFilters]); const pageCount = Math.max(1, Math.ceil(filteredRows.length / pagination.pageSize)); const pagedRows = useMemo(() => { const start = pagination.pageIndex * pagination.pageSize; return filteredRows.slice(start, start + pagination.pageSize); }, [filteredRows, pagination.pageIndex, pagination.pageSize]); const columns = useMemo((): ColumnDef[] => { if (!config) return []; const headerClassName = ruleEngineTable.headerCell; const cellClassName = ruleEngineTable.bodyCell; const base: ColumnDef[] = config.columns.map((col) => ({ id: col.id, accessorKey: col.accessorKey, header: col.header, size: col.size || 150, minSize: col.size ? Math.max(col.size - 20, 80) : 80, meta: { headerClassName, cellClassName }, cell: ({ row }) => { const value = (row.original as unknown as Record)[col.accessorKey]; console.log(`${col.accessorKey}:`, value, 'format:', col.format); return formatFleetCell(value, col.format, col.accessorKey); }, })); base.push({ id: "actions", header: "Actions", size: 160, meta: { headerClassName, cellClassName: `${cellClassName} whitespace-nowrap` }, cell: ({ row }) => (
e.stopPropagation()} data-stop-row-click> { setEditing(record); setFormOpen(true); }} onRemove={setRemoveTarget} onAssignDriver={setAssigningDriver} />
), }); return base; }, [config, dynamicOptions.yards]); const tableStatus = isLoading ? "loading" : isError ? "error" : "success"; if (!config) { return ; } const handleFormSubmit = async (values: Record) => { try { if (editing && "id" in editing) { await update.mutateAsync({ slug, id: String(editing.id), data: values }); toast({ title: `${config.entityLabel} updated` }); } else { await create.mutateAsync({ slug, data: values }); toast({ title: `${config.entityLabel} created` }); } setFormOpen(false); setEditing(null); } catch (err: unknown) { const message = (err as { response?: { data?: { message?: string } } })?.response?.data?.message ?? "Save failed"; toast({ title: "Save failed", description: String(message), variant: "destructive" }); } }; const handleRemove = async () => { if (!removeTarget || !("id" in removeTarget)) return; try { await remove.mutateAsync({ slug, id: String(removeTarget.id) }); toast({ title: config.removeSuccessMessage ?? `${config.entityLabel} removed`, }); setRemoveTarget(null); } catch (err: unknown) { const message = (err as { response?: { data?: { message?: string } } })?.response?.data?.message ?? "Remove failed"; toast({ title: "Remove failed", description: String(message), variant: "destructive" }); } }; const handleAssignDriver = async () => { if (!assigningDriver || !("id" in assigningDriver) || !selectedDriver) return; try { const selectedDriverRecord = (drivers as unknown as Array>).find( (d) => String(d.id) === selectedDriver ); if (!selectedDriverRecord) return; const driverName = `${selectedDriverRecord.firstName} ${selectedDriverRecord.lastName}`; await update.mutateAsync({ slug, id: String(assigningDriver.id), data: { assignedDriverId: selectedDriver, assignedDriverName: driverName, }, }); toast({ title: "Driver assigned successfully" }); setAssigningDriver(null); setSelectedDriver(""); } catch (err: unknown) { const message = (err as { response?: { data?: { message?: string } } })?.response?.data?.message ?? "Assignment failed"; toast({ title: "Assignment failed", description: String(message), variant: "destructive" }); } }; const itemLabel = config.label.toLowerCase(); return (
{config.label} {config.subtitle}
{listFilterSelects.map((filter) => ( {filter.label}: {[{ value: "ALL", label: "All" }, ...filter.data].map((option) => ( ))} ))} ) : hasStatusColumn && statusFilterOptions.length > 1 ? ( Status: {[{ value: "ALL", label: "All" }, ...statusFilterOptions].map((option) => ( ))} ) : undefined } /> {viewMode === "table" ? ( ( )} /> ) : ( { setEditing(record); setFormOpen(true); }} onRemove={setRemoveTarget} /> )}
{ setFormOpen(open); if (!open) setEditing(null); }} title={editing ? `Edit ${config.entityLabel}` : config.addLabel} fields={formFields} initialRecord={editing} emptyValues={config.emptyValues} isSubmitting={create.isPending || update.isPending} selectOptionsLoading={selectOptionsLoading} onSubmit={handleFormSubmit} /> setRemoveTarget(null)} title={{config.removeActionLabel ?? "Delete"}} radius="lg" centered > {config.removeConfirmMessage ?? `Are you sure you want to ${config.removeAction} this ${config.entityLabel.toLowerCase()}?`} { setAssigningDriver(null); setSelectedDriver(""); }} title={Assign Driver} radius="lg" centered > {assigningDriver && "id" in assigningDriver ? `Assign a driver to vehicle: ${(assigningDriver as unknown as Record).plateNumber}` : "Select a driver to assign"}