diff --git a/src/components/BusRouteMap/BusRouteMap.tsx b/src/components/BusRouteMap/BusRouteMap.tsx index a89d288..c470ad9 100644 --- a/src/components/BusRouteMap/BusRouteMap.tsx +++ b/src/components/BusRouteMap/BusRouteMap.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from 'react'; -import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet'; +import { MapContainer, TileLayer, Marker, Popup, Polyline, useMap } from 'react-leaflet'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; import type { BusRoute } from '@/types'; @@ -31,35 +31,33 @@ const isValidCoordinate = (lat?: number, lng?: number): boolean => { const isRealStopCoordinate = (stop: { name: string; lat?: number; lng?: number }): boolean => { if (stop.lat === undefined || stop.lng === undefined) return false; if (!isValidCoordinate(stop.lat, stop.lng)) return false; - - // Check for RIT Campus coordinate fallback (13.0118, 80.0214) + const isRITCoord = Math.abs(stop.lat - 13.0118) < 0.001 && Math.abs(stop.lng - 80.0214) < 0.001; - const isRITName = stop.name.toLowerCase().includes("rit") || - stop.name.toLowerCase().includes("campus") || - stop.name.toLowerCase().includes("college") || - stop.name.toLowerCase().includes("rajalakshmi"); - + const isRITName = + stop.name.toLowerCase().includes('rit') || + stop.name.toLowerCase().includes('campus') || + stop.name.toLowerCase().includes('college') || + stop.name.toLowerCase().includes('rajalakshmi'); + if (isRITCoord && !isRITName) { - return false; // Exclude defaulted RIT Campus coordinates + return false; } - + return true; }; - - const liveBusIcon = L.divIcon({ - html: `
🚌
`, + html: `
🚌
`, className: '', - iconSize: [36, 36], - iconAnchor: [18, 18], + iconSize: [40, 40], + iconAnchor: [20, 20], }); const stoppedBusIcon = L.divIcon({ - html: `
🚌
`, + html: `
🚌
`, className: '', - iconSize: [36, 36], - iconAnchor: [18, 18], + iconSize: [40, 40], + iconAnchor: [20, 20], }); // Helper component to auto-pan and fit the map bounds to the active route @@ -67,7 +65,7 @@ function MapUpdater({ bounds }: { bounds: L.LatLngBoundsExpression | null }) { const map = useMap(); useEffect(() => { if (bounds && (bounds as any).length > 0) { - map.fitBounds(bounds, { padding: [50, 50], maxZoom: 14, animate: true }); + map.fitBounds(bounds, { padding: [60, 60], maxZoom: 14, animate: true }); } }, [bounds, map]); return null; @@ -80,8 +78,10 @@ interface BusRouteMapProps { const DEFAULT_CENTER = [13.0118, 80.0214]; // RIT Campus default -export default function BusRouteMap({ selectedRoute, allRoutes }: BusRouteMapProps) { - const [allLiveLocations, setAllLiveLocations] = useState>({}); +export default function BusRouteMap({ selectedRoute }: BusRouteMapProps) { + const [allLiveLocations, setAllLiveLocations] = useState< + Record + >({}); useEffect(() => { const fetchLiveLocations = () => { @@ -90,114 +90,119 @@ export default function BusRouteMap({ selectedRoute, allRoutes }: BusRouteMapPro 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 = {}; - 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); + .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({}); }); }; - // Initial fetch fetchLiveLocations(); - - // Poll every 3 seconds const interval = setInterval(fetchLiveLocations, 3000); return () => clearInterval(interval); }, []); - // Collect coordinates for the polyline path, filtering out any invalid outliers + + // Collect coordinates for polyline path const pathCoordinates = useMemo(() => { if (!selectedRoute) return []; - - // If route has pre-scraped polyline road path, use it! + if (selectedRoute.polyline && selectedRoute.polyline.length > 0) { return selectedRoute.polyline; } - + const coords: [number, number][] = []; - - // Add start stop coords if valid and real + if (isValidCoordinate(selectedRoute.from_lat, selectedRoute.from_lng)) { - const isStartRIT = Math.abs(selectedRoute.from_lat! - 13.0118) < 0.001 && Math.abs(selectedRoute.from_lng! - 80.0214) < 0.001; - const isStartRITName = selectedRoute.from.toLowerCase().includes("rit") || - selectedRoute.from.toLowerCase().includes("campus") || - selectedRoute.from.toLowerCase().includes("college"); - if (!isStartRIT || isStartRITName) { - coords.push([selectedRoute.from_lat!, selectedRoute.from_lng!]); - } + coords.push([selectedRoute.from_lat!, selectedRoute.from_lng!]); } - - // Add all intermediary stop coords that are valid and real - selectedRoute.stops.forEach(stop => { + + selectedRoute.stops.forEach((stop) => { if (isRealStopCoordinate(stop)) { coords.push([stop.lat!, stop.lng!]); } }); - // Add end stop coords if valid and real if (isValidCoordinate(selectedRoute.to_lat, selectedRoute.to_lng)) { - const isEndRIT = Math.abs(selectedRoute.to_lat! - 13.0118) < 0.001 && Math.abs(selectedRoute.to_lng! - 80.0214) < 0.001; - const isEndRITName = selectedRoute.to.toLowerCase().includes("rit") || - selectedRoute.to.toLowerCase().includes("campus") || - selectedRoute.to.toLowerCase().includes("college"); - if (!isEndRIT || isEndRITName) { - coords.push([selectedRoute.to_lat!, selectedRoute.to_lng!]); - } + coords.push([selectedRoute.to_lat!, selectedRoute.to_lng!]); } return coords; }, [selectedRoute]); - // Determine map bounds const mapBounds = useMemo(() => { if (pathCoordinates.length === 0) return null; return pathCoordinates as L.LatLngBoundsExpression; }, [pathCoordinates]); - - return ( -
- + - + + {/* Polyline Route Path */} + {pathCoordinates.length > 0 && selectedRoute && ( + + )} + + {/* Live GPS Bus Markers */} {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); - + + const isStale = + loc.stopped || + (loc.lastUpdated ? new Date().getTime() - new Date(loc.lastUpdated).getTime() > 120000 : false); + return ( - +
{loc.stopped ? ( @@ -215,34 +220,39 @@ export default function BusRouteMap({ selectedRoute, allRoutes }: BusRouteMapPro )}

Bus {rNum}

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

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

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

-

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

-

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

+
+

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

+

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

+

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

)} diff --git a/src/pages/BusRoutes/BusRoutes.tsx b/src/pages/BusRoutes/BusRoutes.tsx index faf32fa..9e89725 100644 --- a/src/pages/BusRoutes/BusRoutes.tsx +++ b/src/pages/BusRoutes/BusRoutes.tsx @@ -1,9 +1,8 @@ import { useState, useEffect } from 'react'; -import { motion } from 'framer-motion'; -import { Search, Loader2, ChevronRight } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Search, Loader2, ChevronRight, Menu, X, MapPin, Navigation, Compass, Radio } from 'lucide-react'; import BusCard from '@/components/BusCard/BusCard'; import BusRouteMap from '@/components/BusRouteMap/BusRouteMap'; -import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer'; import type { BusRoute } from '@/types'; import { getBackendUrl } from '@/lib/utils'; import { Link } from 'react-router-dom'; @@ -13,6 +12,7 @@ export default function BusRoutes() { const [busRoutes, setBusRoutes] = useState([]); const [loadingBus, setLoadingBus] = useState(true); const [selectedBusRoute, setSelectedBusRoute] = useState(null); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); useEffect(() => { setLoadingBus(true); @@ -44,88 +44,173 @@ export default function BusRoutes() { }); }, []); - const filteredRoutes = busRoutes.filter((r) => - r.name.toLowerCase().includes(busSearch.toLowerCase()) || - r.from.toLowerCase().includes(busSearch.toLowerCase()) || - r.number.toLowerCase().includes(busSearch.toLowerCase()) + const filteredRoutes = busRoutes.filter( + (r) => + r.name.toLowerCase().includes(busSearch.toLowerCase()) || + r.from.toLowerCase().includes(busSearch.toLowerCase()) || + r.number.toLowerCase().includes(busSearch.toLowerCase()) ); return ( -
-
- {/* Header */} -
-
-
- Home +
+ {/* Sleek Dark Header */} +
+
+
+
+ Home - Bus Routes + Live Bus Tracking
-

- RIT{' '} - - Bus Routes +
+

+ RIT Live Transport Map +

+ + + Live GPS Sync -

-

- View all campus bus routes, timings, and pickup locations at a glance. +

+
+ + {/* Controls: Search + Toggle Drawer Button */} +
+
+ + setBusSearch(e.target.value)} + className="w-full pl-9 pr-4 py-2 rounded-xl bg-slate-800 border border-slate-700 text-xs text-white placeholder-slate-400 focus:outline-none focus:border-orange-500 transition-colors" + style={{ fontFamily: 'Inter, sans-serif' }} + /> +
+ + +
+
+
+ + {/* Main Map Viewport */} +
+ {loadingBus ? ( +
+ +

+ Initializing Live GPS Bus Tracking Map...

-
+ ) : ( + <> + {/* Quick Horizontal Route Chips Bar on top of Map */} +
+ -
-
- - setBusSearch(e.target.value)} - className="w-full pl-11 pr-4 py-3 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors" - style={{ fontFamily: 'Inter, sans-serif' }} - /> -
+ {filteredRoutes.map((route) => { + const isSelected = selectedBusRoute?.number === route.number; + return ( + + ); + })} +
- {loadingBus ? ( -
- -

- Loading live bus routes from RIT Transport... -

-
- ) : filteredRoutes.length === 0 ? ( -
-

- No bus routes found matching "{busSearch}" -

-
- ) : ( -
- {/* Routes List */} -
- - {filteredRoutes.map((route) => ( - - setSelectedBusRoute(route)} - /> - - ))} - -
+ {/* The Fullscreen Map Component */} + - {/* Interactive Map */} -
- -
-
- )} -
+ {/* Sidebar / Bottom Sheet Overlay */} + + {isSidebarOpen && ( + <> + {/* Backdrop on mobile */} + setIsSidebarOpen(false)} + className="absolute inset-0 bg-slate-950/60 backdrop-blur-xs z-30 lg:hidden" + /> + + {/* Drawer Panel: Floating Panel on Desktop, Bottom Sheet on Mobile */} + + {/* Drawer Header */} +
+
+ +

+ Select Bus Route ({filteredRoutes.length}) +

+
+ +
+ + {/* Route List inside Drawer */} +
+ {filteredRoutes.map((route) => ( + { + setSelectedBusRoute(route); + // Auto close sidebar on mobile for instant map view + if (window.innerWidth < 1024) { + setIsSidebarOpen(false); + } + }} + /> + ))} +
+
+ + )} +
+ + )}
);