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
)}
);
}