import { useEffect } from "react"; import { MapContainer, TileLayer, Marker, Polyline, Tooltip, useMap, } from "react-leaflet"; import L from "leaflet"; import "leaflet/dist/leaflet.css"; // This component is loaded via React.lazy from the route planner page so that // Leaflet + react-leaflet land in a separate code-split chunk and never weigh // down the main admin bundle. export interface RouteMapStop { id: string; stopOrder: number; latitude: number; longitude: number; clientName: string; } interface RouteMapProps { stops: RouteMapStop[]; primaryColor: string; } /** A numbered teardrop pin rendered as an inline-SVG divIcon (no image assets). */ function numberedIcon(order: number, color: string): L.DivIcon { return L.divIcon({ className: "route-stop-pin", html: `
${order}
`, iconSize: [28, 40], iconAnchor: [14, 40], tooltipAnchor: [0, -34], }); } /** Keeps the viewport framed around all stops whenever the route changes. */ function FitBounds({ stops }: { stops: RouteMapStop[] }) { const map = useMap(); useEffect(() => { if (stops.length === 0) return; const latlngs = stops.map((s) => [s.latitude, s.longitude] as [number, number]); if (latlngs.length === 1) { map.setView(latlngs[0]!, 14); } else { map.fitBounds(L.latLngBounds(latlngs), { padding: [40, 40] }); } }, [map, stops]); return null; } export default function RouteMap({ stops, primaryColor }: RouteMapProps) { // Fallback centre (London) only used briefly before FitBounds runs or when the // route has no geocoded stops. const center: [number, number] = stops[0] ? [stops[0].latitude, stops[0].longitude] : [51.505, -0.09]; const line = stops.map((s) => [s.latitude, s.longitude] as [number, number]); return ( {line.length >= 2 && ( )} {stops.map((s) => ( {s.stopOrder}. {s.clientName} ))} ); }