diff --git a/drb-frontend/components/MapView.tsx b/drb-frontend/components/MapView.tsx index 8c6ac98..744c678 100644 --- a/drb-frontend/components/MapView.tsx +++ b/drb-frontend/components/MapView.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { FeatureGroup, LayersControl, @@ -10,9 +10,7 @@ import { TileLayer, useMap, } from "react-leaflet"; -import { createLayerComponent } from "@react-leaflet/core"; import L from "leaflet"; -import "leaflet.gridlayer.googlemutant"; import type { CallRecord, IncidentRecord, NodeRecord } from "@/lib/types"; // ── Leaflet icon fix ────────────────────────────────────────────────────────── @@ -148,17 +146,53 @@ function computeGroups( } // ── Google Maps traffic layer via leaflet-google-mutant ─────────────────────── -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const GoogleTrafficOverlay = createLayerComponent( - (_props, ctx) => { - // leaflet-google-mutant augments L.gridLayer after the side-effect import +// Uses dynamic import so the side-effect augments the same L instance at runtime, +// avoiding the "GoogleMutant is not a constructor" error from static top-level imports. +function GoogleTrafficManager({ enabled }: { enabled: boolean }) { + const map = useMap(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const layerRef = useRef(null); + const scriptLoadedRef = useRef(false); + + useEffect(() => { + if (!enabled) { + if (layerRef.current) { map.removeLayer(layerRef.current); layerRef.current = null; } + return; + } + + const key = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY; + if (!key) return; + + function mountLayer() { + import("leaflet.gridlayer.googlemutant").then(() => { + if (layerRef.current) return; // already mounted + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const layer = (L as any).gridLayer.googleMutant({ type: "roadmap" }); + layer.addGoogleLayer("TrafficLayer"); + map.addLayer(layer); + layerRef.current = layer; + }); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any - const instance = (L as any).gridLayer.googleMutant({ type: "roadmap" }); - instance.addGoogleLayer("TrafficLayer"); - return { instance, context: ctx }; - }, - () => {} -); + if ((window as any).google?.maps) { + mountLayer(); + } else if (!scriptLoadedRef.current) { + scriptLoadedRef.current = true; + const script = document.createElement("script"); + script.src = `https://maps.googleapis.com/maps/api/js?key=${key}&loading=async`; + script.async = true; + script.onload = mountLayer; + document.head.appendChild(script); + } + + return () => { + if (layerRef.current) { map.removeLayer(layerRef.current); layerRef.current = null; } + }; + }, [enabled, map]); + + return null; +} // ── MapRefCapture — exposes L.Map instance to parent ───────────────────────── function MapRefCapture({ onReady }: { onReady: (m: L.Map) => void }) { @@ -357,20 +391,7 @@ export default function MapView({ nodes, activeCalls, incidents = [], lastUpdate return () => clearInterval(id); }, []); - // Load Google Maps JS API for leaflet-google-mutant traffic layer - const [googleReady, setGoogleReady] = useState(false); - useEffect(() => { - const key = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY; - if (!key) return; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((window as any).google?.maps) { setGoogleReady(true); return; } - if (document.querySelector('script[src*="maps.googleapis.com/maps/api/js"]')) return; - const script = document.createElement("script"); - script.src = `https://maps.googleapis.com/maps/api/js?key=${key}`; - script.async = true; - script.onload = () => setGoogleReady(true); - document.head.appendChild(script); - }, []); + const [trafficEnabled, setTrafficEnabled] = useState(false); // Live clock for TOC situational awareness useEffect(() => { @@ -468,10 +489,8 @@ export default function MapView({ nodes, activeCalls, incidents = [], lastUpdate - {/* Overlay: Traffic — Google Maps via leaflet-google-mutant */} - - {googleReady ? : } - + {/* Traffic managed outside LayersControl to avoid L-instance conflicts */} + {/* Overlay: Weather Radar — NEXRAD via Iowa Env Mesonet; key forces remount on refresh */} @@ -504,6 +523,21 @@ export default function MapView({ nodes, activeCalls, incidents = [], lastUpdate )} + {/* ── Traffic toggle ───────────────────────────────────────────────────── */} + {process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY && ( + + )} + {/* ── Auto-fit button ──────────────────────────────────────────────────── */} {mapInstance && allPositions.length > 0 && (