'use client'; import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Plus, Edit, Trash2, X, Search } from 'lucide-react'; import DataTable from '@/components/ui/DataTable'; import Badge from '@/components/ui/Badge'; import ActionButton from '@/components/ui/ActionButton'; import Modal from '@/components/ui/Modal'; import ConfirmDialog from '@/components/ui/ConfirmDialog'; import { routesApi } from '@/lib/api/routes'; import { stationsApi } from '@/lib/api'; interface RouteStop { stationId: string; sequence: number; distanceKm?: number; distanceFromOrigin?: number; } export default function RoutesPage() { const [showModal, setShowModal] = useState(false); const [editingRoute, setEditingRoute] = useState(null); const [stops, setStops] = useState([]); const [originStationId, setOriginStationId] = useState(''); const [destinationStationId, setDestinationStationId] = useState(''); const [destinationDistance, setDestinationDistance] = useState(undefined); const [deleteConfirm, setDeleteConfirm] = useState<{ isOpen: boolean; route: any | null }>({ isOpen: false, route: null }); const [search, setSearch] = useState(''); const queryClient = useQueryClient(); const { data: routes, isLoading: routesLoading } = useQuery({ queryKey: ['routes'], queryFn: async () => { const result = await routesApi.getAll(); console.log('Routes query result:', result); return result; }, }); const { data: stations } = useQuery({ queryKey: ['stations'], queryFn: stationsApi.getAll, }); const createMutation = useMutation({ mutationFn: routesApi.create, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['routes'] }); setShowModal(false); setEditingRoute(null); }, }); const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: string; data: any }) => routesApi.update(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['routes'] }); setShowModal(false); setEditingRoute(null); }, }); const deleteMutation = useMutation({ mutationFn: routesApi.delete, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['routes'] }); }, }); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); if (!originStationId || !destinationStationId) { alert('Please select origin and destination stations'); return; } if (originStationId === destinationStationId) { alert('Origin and destination must be different'); return; } // Keep current stop order (already rearranged by user) const sortedMiddleStops = stops; // Calculate distanceKm (distance from previous stop) const stopsArray = [ { stationId: originStationId, sequence: 1, distanceKm: 0 }, ...sortedMiddleStops.map((stop, idx) => { const prevDistance = idx === 0 ? 0 : (sortedMiddleStops[idx - 1].distanceFromOrigin || 0); return { stationId: stop.stationId, sequence: idx + 2, distanceKm: (stop.distanceFromOrigin || 0) - prevDistance, }; }), { stationId: destinationStationId, sequence: sortedMiddleStops.length + 2, distanceKm: (destinationDistance || 0) - (sortedMiddleStops.length > 0 ? (sortedMiddleStops[sortedMiddleStops.length - 1].distanceFromOrigin || 0) : 0), }, ]; const routeData = { code: formData.get('code') as string, name: formData.get('name') as string, description: formData.get('description') as string || undefined, effectiveFrom: formData.get('effectiveFrom') as string, effectiveUntil: formData.get('effectiveUntil') as string || undefined, stops: stopsArray, }; console.log('Submitting route data:', JSON.stringify(routeData, null, 2)); if (editingRoute) { await updateMutation.mutateAsync({ id: editingRoute.id, data: routeData }); } else { await createMutation.mutateAsync(routeData); } }; const addStop = () => { setStops([...stops, { stationId: '', sequence: stops.length + 1, distanceFromOrigin: 0 }]); }; const removeStop = (index: number) => { setStops(stops.filter((_, i) => i !== index)); }; const updateStop = (index: number, field: keyof RouteStop, value: any) => { const updated = [...stops]; updated[index] = { ...updated[index], [field]: value }; setStops(updated); }; const handleDragStart = (e: React.DragEvent, index: number) => { e.dataTransfer.setData('text/plain', index.toString()); }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); (e.currentTarget as HTMLElement).style.opacity = '0.5'; }; const handleDragLeave = (e: React.DragEvent) => { (e.currentTarget as HTMLElement).style.opacity = '1'; }; const handleDrop = (e: React.DragEvent, targetIndex: number) => { e.preventDefault(); (e.currentTarget as HTMLElement).style.opacity = '1'; const sourceIndex = parseInt(e.dataTransfer.getData('text/plain')); if (sourceIndex === targetIndex) return; const newStops = [...stops]; const [draggedStop] = newStops.splice(sourceIndex, 1); newStops.splice(targetIndex, 0, draggedStop); setStops(newStops); }; const generateRouteCode = (originId: string, destId: string) => { if (!originId || !destId) return ''; const origin = stations?.items?.find((s: any) => s.id === originId); const dest = stations?.items?.find((s: any) => s.id === destId); return origin && dest ? `${origin.code}-${dest.code}` : ''; }; const generateRouteName = (originId: string, destId: string) => { if (!originId || !destId) return ''; const origin = stations?.items?.find((s: any) => s.id === originId); const dest = stations?.items?.find((s: any) => s.id === destId); return origin && dest ? `${origin.name} - ${dest.name}` : ''; }; const handleDelete = (route: any) => { setDeleteConfirm({ isOpen: true, route }); }; const confirmDelete = async () => { if (deleteConfirm.route) { await deleteMutation.mutateAsync(deleteConfirm.route.id); setDeleteConfirm({ isOpen: false, route: null }); } }; const routeColumns = [ { key: 'code', label: 'Route Code', sortable: true }, { key: 'name', label: 'Route Name', sortable: true }, { key: 'description', label: 'Description', render: (route: any) => route.description || 'N/A' }, { key: 'active', label: 'Status', render: (route: any) => ( {route.active ? 'Active' : 'Inactive'} ), }, ]; const filteredRoutes = (routes as any)?.items || (Array.isArray(routes) ? routes : []); const displayedRoutes = filteredRoutes.filter((route: any) => { if (!search) return true; const searchLower = search.toLowerCase(); return ( route.code?.toLowerCase().includes(searchLower) || route.name?.toLowerCase().includes(searchLower) || route.description?.toLowerCase().includes(searchLower) ); }); const routeActions = [ { label: 'Edit', onClick: (route: any) => { setEditingRoute(route); const routeStops = route.stops || []; if (routeStops.length >= 2) { setOriginStationId(routeStops[0].stationId); setDestinationStationId(routeStops[routeStops.length - 1].stationId); // Last stop's distanceKm is already cumulative from origin setDestinationDistance(routeStops[routeStops.length - 1].distanceKm || 0); const middleStops = routeStops.slice(1, -1).map((stop: any) => ({ stationId: stop.stationId, sequence: stop.sequence, distanceKm: stop.distanceKm, distanceFromOrigin: stop.distanceKm || 0, })); setStops(middleStops); } setShowModal(true); }, variant: 'secondary' as const, icon: Edit, }, { label: 'Delete', onClick: handleDelete, variant: 'danger' as const, icon: Trash2, }, ]; return (

Routes

Manage railway routes

{ setEditingRoute(null); setOriginStationId(''); setDestinationStationId(''); setDestinationDistance(undefined); setStops([]); setSearch(''); setShowModal(true); }} > Add Route
setSearch(e.target.value)} className="input pl-10 w-full" />
setDeleteConfirm({ isOpen: false, route: null })} onConfirm={confirmDelete} title="Delete Route" message={`Are you sure you want to delete ${deleteConfirm.route?.name}?`} confirmText="Delete" isDanger={true} warning="This route may be referenced by schedules and bookings. Deleting it may impact these systems." /> { setShowModal(false); setEditingRoute(null); setOriginStationId(''); setDestinationStationId(''); setDestinationDistance(undefined); setStops([]); setSearch(''); }} title={`${editingRoute ? 'Edit' : 'Add'} Route`} size="lg" >
{editingRoute && (

⚠ Warning

Editing this route may impact schedules, trips, and bookings that reference it. Proceed with caution.

)}