From 2e1c720b86970942945a8b0c7b648386c839694d Mon Sep 17 00:00:00 2001 From: natib21 Date: Tue, 30 Jun 2026 14:08:21 +0000 Subject: [PATCH] feat: add vehicle tracking/GPS map New TrackingPage with: - Interactive map grid showing vehicle locations - Real-time GPS coordinates (mock data) - Vehicle speed & heading display - Vehicle selector dropdown - Live status indicators - All vehicles list with speed - Click-to-track functionality - Location details sidebar: * Latitude/Longitude * Current speed * Heading direction * Last update timestamp * View history button Features: - Grid-based map (no external dependencies) - Vehicle markers (color-coded selected/inactive) - SVG grid background (lat/lng lines) - Responsive layout (map + sidebar) - Mantine UI + brand colors - Mock GPS generation per vehicle Route: /dashboard/tracking Sidebar: "Track Vehicles" in Fleet Management Co-Authored-By: Claude Haiku 4.5 --- apps/edr-freight-web/backoffice/src/App.tsx | 16 + .../src/pages/fleet/TrackingPage.tsx | 353 ++++++++++++++++++ 2 files changed, 369 insertions(+) create mode 100644 apps/edr-freight-web/backoffice/src/pages/fleet/TrackingPage.tsx diff --git a/apps/edr-freight-web/backoffice/src/App.tsx b/apps/edr-freight-web/backoffice/src/App.tsx index 7d8d07ab5..8c1562bc4 100644 --- a/apps/edr-freight-web/backoffice/src/App.tsx +++ b/apps/edr-freight-web/backoffice/src/App.tsx @@ -6,6 +6,7 @@ import { FileText, LayoutDashboard, LayoutGrid, + MapPin, Network, Package, PackageCheck, @@ -62,6 +63,7 @@ import FuelStatsPage from "./pages/fleet/FuelStatsPage"; import { MaintenancePage } from "./pages/fleet/MaintenancePage"; import { FinancialReportsPage } from "./pages/fleet/FinancialReportsPage"; import { FleetDashboard } from "./pages/fleet/FleetDashboard"; +import { TrackingPage } from "./pages/fleet/TrackingPage"; import { getCategorySidebarChildren } from "./pages/ruleEngine/config/resources"; import RuleEngineLegacyRedirect from "./pages/ruleEngine/RuleEngineLegacyRedirect"; import RuleEngineResourcePage from "./pages/ruleEngine/RuleEngineResourcePage"; @@ -211,6 +213,12 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [ icon: , permission: FREIGHT_PERMS.fleet.view, }, + { + label: "Track Vehicles", + href: "/dashboard/tracking", + icon: , + permission: FREIGHT_PERMS.fleet.view, + }, { label: "Fuel Purchases", href: "/dashboard/fuel-purchases", @@ -800,6 +808,14 @@ const App = () => { } /> + + + + } + /> ({ + lat: 9.0 + Math.random() * 0.5, + lng: 38.7 + Math.random() * 0.5, + speed: Math.floor(Math.random() * 120), + heading: Math.floor(Math.random() * 360), + lastUpdate: new Date(Date.now() - Math.random() * 300000).toLocaleTimeString(), +}); + +export function TrackingPage() { + const [selectedVehicleId, setSelectedVehicleId] = useState(null); + const [mapCenter] = useState({ lat: 9.0, lng: 38.8 }); + const mapZoom = 10; + + const { data: vehicles = [] } = useQuery({ + queryKey: QUERY_KEYS.VEHICLES.list(), + queryFn: async () => { + const res = await vehiclesService.getAll({ limit: 1000 }); + return res.data || []; + }, + }); + + // Generate mock GPS data for each vehicle + const vehiclesWithGPS = useMemo(() => { + return (vehicles as Vehicle[]).map((v, idx) => ({ + ...v, + gps: generateMockGPS(idx), + })); + }, [vehicles]); + + const selectedVehicle = vehiclesWithGPS.find(v => v.id === selectedVehicleId); + const vehicleOptions = useMemo( + () => vehiclesWithGPS.map(v => ({ label: v.registrationNumber, value: v.id })), + [vehiclesWithGPS] + ); + + // Map dimensions + const mapWidth = 800; + const mapHeight = 500; + const pixelsPerLat = mapHeight / 0.6; + const pixelsPerLng = mapWidth / 0.6; + + const getMapCoords = (lat: number, lng: number) => ({ + x: ((lng - (mapCenter.lng - 0.3)) * pixelsPerLng), + y: ((mapCenter.lat + 0.3 - lat) * pixelsPerLat), + }); + + return ( + + + + + +
+ + Real-Time Vehicle Tracking + + + Monitor vehicle locations, speed, and status + +
+
+ + + {/* Map Section */} + + + + + Map View + + }> + {vehiclesWithGPS.filter(v => v.status === 'ACTIVE').length} Active + + + + + + + + {/* Grid background */} + + {/* Latitude lines */} + {[0, 1, 2, 3, 4, 5, 6].map(i => ( + + ))} + {/* Longitude lines */} + {[0, 1, 2, 3, 4, 5, 6].map(i => ( + + ))} + + + {/* Vehicle markers */} + {vehiclesWithGPS.map((vehicle) => { + const coords = getMapCoords(vehicle.gps.lat, vehicle.gps.lng); + const isSelected = vehicle.id === selectedVehicleId; + + return ( + setSelectedVehicleId(vehicle.id)} + title={vehicle.registrationNumber} + > + + + + + ); + })} + + {/* Map labels */} + + + 📍 Addis Ababa, Ethiopia + + + + + + + + {/* Sidebar */} + + + {/* Vehicle Selector */} + + +