import { useEffect, useMemo, useState } from 'react'; import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; import type { BusRoute } from '@/types'; import { getBackendUrl } from '@/lib/utils'; // Standard Leaflet Icon fix for Vite import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png'; import markerIcon from 'leaflet/dist/images/marker-icon.png'; import markerShadow from 'leaflet/dist/images/marker-shadow.png'; delete (L.Icon.Default.prototype as any)._getIconUrl; L.Icon.Default.mergeOptions({ iconUrl: markerIcon, iconRetinaUrl: markerIcon2x, shadowUrl: markerShadow, }); const liveBusIcon = L.divIcon({ html: `
🚌
`, className: '', iconSize: [40, 40], iconAnchor: [20, 20], }); const stoppedBusIcon = L.divIcon({ html: `
🚌
`, className: '', iconSize: [40, 40], iconAnchor: [20, 20], }); // Helper component to pan map to active bus marker when selected function MapPanFocus({ center }: { center: [number, number] | null }) { const map = useMap(); useEffect(() => { if (center) { map.setView(center, 13, { animate: true }); } }, [center, map]); return null; } interface BusRouteMapProps { selectedRoute: BusRoute | null; allRoutes?: BusRoute[]; } const DEFAULT_CENTER: [number, number] = [13.0118, 80.0214]; // RIT Campus default export default function BusRouteMap({ selectedRoute }: BusRouteMapProps) { const [allLiveLocations, setAllLiveLocations] = useState< Record >({}); useEffect(() => { const fetchLiveLocations = () => { fetch(getBackendUrl('/api/bus-locations')) .then((res) => { if (res.ok) return res.json(); return []; }) .then( ( data: Array<{ routeNumber: string; latitude: number; longitude: number; lastUpdated: string; stopped: boolean; }> ) => { if (Array.isArray(data)) { const locMap: Record< string, { latitude: number; longitude: number; lastUpdated: string; stopped: boolean } > = {}; data.forEach((item) => { if (item.routeNumber && item.latitude && item.longitude) { locMap[item.routeNumber] = { latitude: item.latitude, longitude: item.longitude, lastUpdated: item.lastUpdated, stopped: item.stopped, }; } }); setAllLiveLocations(locMap); } } ) .catch(() => { setAllLiveLocations({}); }); }; fetchLiveLocations(); const interval = setInterval(fetchLiveLocations, 3000); return () => clearInterval(interval); }, []); // Determine center focus point if a bus is live or selected const activeFocusCenter = useMemo<[number, number] | null>(() => { if (!selectedRoute) return null; const normalizeRouteNum = (str: string) => (str || '').replace(/^[Rr]0*/, '').trim().toUpperCase(); const matchLocKey = Object.keys(allLiveLocations).find( (k) => normalizeRouteNum(k) === normalizeRouteNum(selectedRoute.number) ); if (matchLocKey && allLiveLocations[matchLocKey]) { const loc = allLiveLocations[matchLocKey]; return [loc.latitude, loc.longitude]; } if (selectedRoute.from_lat && selectedRoute.from_lng) { return [selectedRoute.from_lat, selectedRoute.from_lng]; } return DEFAULT_CENTER; }, [selectedRoute, allLiveLocations]); return (
{/* Live GPS Bus Markers ONLY - No polyline routes */} {Object.entries(allLiveLocations).map(([rNum, loc]) => { const normalizeRouteNum = (str: string) => (str || '').replace(/^[Rr]0*/, '').trim().toUpperCase(); if (selectedRoute && normalizeRouteNum(selectedRoute.number) !== normalizeRouteNum(rNum)) return null; const isStale = loc.stopped || (loc.lastUpdated ? new Date().getTime() - new Date(loc.lastUpdated).getTime() > 120000 : false); return (
{loc.stopped ? ( LOCATION SHARING ENDED ) : isStale ? ( LOCATION SHARING STOPPED ) : ( LIVE TRACKING )}

Bus {rNum}

{loc.stopped ? 'Sharing ended by driver' : isStale ? 'Last known location' : 'Broadcasting live coordinates'}

); })}
{/* Floating Info Card on Selected Route */} {selectedRoute && (
{selectedRoute.number} {selectedRoute.name}

🏁 Start: {selectedRoute.from} ({selectedRoute.departureTime})

🏫 Destination: RIT Campus ({selectedRoute.arrivalTime})

📍 Stops: {selectedRoute.stops.length} mapped stops

)}
); }