import { useRef, useCallback, useEffect } from 'react'; import { MapContainer, TileLayer, useMapEvents, useMap } from 'react-leaflet'; import 'leaflet/dist/leaflet.css'; import type { Map as LeafletMap } from 'leaflet'; import type { Site } from '@/types/index.ts'; import { useSitesStore } from '@/store/sites.ts'; import { useSettingsStore } from '@/store/settings.ts'; import { useToastStore } from '@/components/ui/Toast.tsx'; import SiteMarker from './SiteMarker.tsx'; import MapExtras from './MapExtras.tsx'; import CoordinateGrid from './CoordinateGrid.tsx'; import MeasurementTool from './MeasurementTool.tsx'; import ElevationDisplay from './ElevationDisplay.tsx'; interface MapViewProps { onMapClick: (lat: number, lon: number) => void; onEditSite: (site: Site) => void; children?: React.ReactNode; } function MapClickHandler({ onMapClick, }: { onMapClick: (lat: number, lon: number) => void; }) { const isPlacingMode = useSitesStore((s) => s.isPlacingMode); useMapEvents({ click: (e) => { if (isPlacingMode) { onMapClick(e.latlng.lat, e.latlng.lng); } }, }); return null; } /** * Inner component that exposes the map instance via ref callback */ function MapRefSetter({ mapRef }: { mapRef: React.MutableRefObject }) { const map = useMap(); useEffect(() => { mapRef.current = map; }, [map, mapRef]); return null; } export default function MapView({ onMapClick, onEditSite, children }: MapViewProps) { const sites = useSitesStore((s) => s.sites); const isPlacingMode = useSitesStore((s) => s.isPlacingMode); const showTerrain = useSettingsStore((s) => s.showTerrain); const terrainOpacity = useSettingsStore((s) => s.terrainOpacity); const setShowTerrain = useSettingsStore((s) => s.setShowTerrain); const showGrid = useSettingsStore((s) => s.showGrid); const setShowGrid = useSettingsStore((s) => s.setShowGrid); const measurementMode = useSettingsStore((s) => s.measurementMode); const setMeasurementMode = useSettingsStore((s) => s.setMeasurementMode); const showElevationInfo = useSettingsStore((s) => s.showElevationInfo); const showElevationOverlay = useSettingsStore((s) => s.showElevationOverlay); const setShowElevationOverlay = useSettingsStore((s) => s.setShowElevationOverlay); const addToast = useToastStore((s) => s.addToast); const mapRef = useRef(null); const handleFitToSites = useCallback(() => { if (sites.length === 0 || !mapRef.current) return; const bounds = sites.map((site) => [site.lat, site.lon] as [number, number]); mapRef.current.fitBounds(bounds, { padding: [50, 50] }); }, [sites]); const handleResetView = useCallback(() => { mapRef.current?.setView([48.4, 35.0], 7); }, []); return ( <> {/* Base OSM layer */} {/* Terrain overlay (OpenTopoMap, above base map, below heatmap) */} {showTerrain && ( )} {/* Elevation color overlay (Stamen Terrain via Stadia Maps) */} {showElevationOverlay && ( )} {showElevationInfo && } { addToast(`Distance: ${distKm.toFixed(2)} km (${(distKm * 1000).toFixed(0)} m)`, 'info'); setMeasurementMode(false); }} /> {sites .filter((s) => s.visible) .map((site) => ( ))} {children} {/* Map control buttons */}
); }