diff --git a/frontend/src/components/VehicleMap.jsx b/frontend/src/components/VehicleMap.jsx new file mode 100644 index 0000000..1b6210e --- /dev/null +++ b/frontend/src/components/VehicleMap.jsx @@ -0,0 +1,95 @@ +import React, { useEffect, useState } from 'react'; +import { supabase } from '../supabaseClient'; + +// Load Leaflet CSS dynamically +const loadLeafletCss = () => { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'; + document.head.appendChild(link); +}; + +// Load Leaflet JS dynamically (as a module) +const loadLeafletJs = () => { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'; + script.onload = () => resolve(window.L); + script.onerror = reject; + document.body.appendChild(script); + }); +}; + +export default function VehicleMap() { + const [vehicles, setVehicles] = useState([]); + const [map, setMap] = useState(null); + + // Initialise map once Leaflet is loaded + useEffect(() => { + loadLeafletCss(); + let L; + loadLeafletJs() + .then((leaflet) => { + L = leaflet; + const mapInstance = L.map('vehicle-map').setView([20, 0], 2); + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors', + }).addTo(mapInstance); + setMap({ L, mapInstance }); + }) + .catch((err) => console.error('Failed to load Leaflet', err)); + }, []); + + // Subscribe to realtime changes on vehicles table + useEffect(() => { + const channel = supabase + .channel('public:vehicles') + .on('postgres_changes', { event: '*', schema: 'public', table: 'vehicles' }, (payload) => { + // payload.new contains the new row data + setVehicles((prev) => { + const filtered = prev.filter((v) => v.id !== payload.new.id); + return [...filtered, payload.new]; + }); + }) + .subscribe(); + + // Initial fetch + supabase + .from('vehicles') + .select('id, latitude, longitude, number') + .then(({ data, error }) => { + if (!error && data) setVehicles(data); + else console.error('Error fetching vehicles', error); + }); + + return () => { + supabase.removeChannel(channel); + }; + }, []); + + // Update markers when vehicles or map change + useEffect(() => { + if (!map) return; + const { L, mapInstance } = map; + // Clear existing markers layer group + if (mapInstance._vehicleLayer) { + mapInstance.removeLayer(mapInstance._vehicleLayer); + } + const layer = L.layerGroup(); + vehicles.forEach((v) => { + if (v.latitude && v.longitude) { + const marker = L.marker([v.latitude, v.longitude]).bindPopup(`Vehicle ${v.number}`); + layer.addLayer(marker); + } + }); + layer.addTo(mapInstance); + mapInstance._vehicleLayer = layer; + }, [vehicles, map]); + + return ( +
+

Live Vehicle Tracking

+
+
+ ); +} diff --git a/frontend/src/router.jsx b/frontend/src/router.jsx index 4b2280c..d5fc73a 100644 --- a/frontend/src/router.jsx +++ b/frontend/src/router.jsx @@ -1,7 +1,8 @@ import React from 'react'; -import { createRootRoute, RouterProvider, createBrowserRouter } from '@tanstack/react-router'; +import { createRootRoute, RouterProvider, createBrowserRouter, Link } from '@tanstack/react-router'; import LoadsList from './components/LoadsList'; import ShippersList from './components/ShippersList'; +import VehicleMap from './components/VehicleMap'; // Root layout – can later include a navbar or sidebar function RootLayout({ children }) { @@ -11,6 +12,14 @@ function RootLayout({ children }) {

FreightDesk Dashboard

+ + {/* Navigation */} + +
{children}
); @@ -33,6 +42,12 @@ const shippersRoute = rootRoute.createRoute({ component: ShippersList, }); +// Vehicle tracking page route +const vehiclesRoute = rootRoute.createRoute({ + path: '/vehicles', + component: VehicleMap, +}); + // Default route – redirect to /loads const indexRoute = rootRoute.createRoute({ path: '/', @@ -46,7 +61,7 @@ const indexRoute = rootRoute.createRoute({ }); // Build the router -const routeTree = rootRoute.addChildren([loadsRoute, shippersRoute, indexRoute]); +const routeTree = rootRoute.addChildren([loadsRoute, shippersRoute, vehiclesRoute, indexRoute]); export const router = createBrowserRouter({ routeTree });