fix(storm-intel): map shows only selected/pinned events; add Show all toggle + auto-zoom to pins
- Location pins filter the sidebar only; map no longer auto-renders all nearby polygons (NWS warnings span entire counties, cluttering the view) - Map shows event polygons only for explicitly selected or event-pinned items - "Show all on map" toggle in sidebar renders the full sidebar event list - FitToPins auto-zooms to the pin radius circle when a new pin is dropped; clears back to Plano center when all pins are removed
This commit is contained in:
@@ -242,6 +242,35 @@ const PinDropLayer = ({ onDrop }) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FitToPins = ({ pins, pinRadius }) => {
|
||||||
|
const map = useMap();
|
||||||
|
const prevLen = useRef(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pins.length === 0) {
|
||||||
|
if (prevLen.current > 0) map.flyTo(PLANO_CENTER, DEFAULT_ZOOM, { animate: true, duration: 0.6 });
|
||||||
|
prevLen.current = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only zoom when a pin is added (length increased)
|
||||||
|
if (pins.length <= prevLen.current) { prevLen.current = pins.length; return; }
|
||||||
|
prevLen.current = pins.length;
|
||||||
|
try {
|
||||||
|
const radiusM = pinRadius * 1609.34;
|
||||||
|
const latOffset = radiusM / 111319;
|
||||||
|
let bounds = null;
|
||||||
|
pins.forEach(([lat, lng]) => {
|
||||||
|
const lngOffset = latOffset / Math.cos(lat * Math.PI / 180);
|
||||||
|
const pb = L.latLngBounds([lat - latOffset, lng - lngOffset], [lat + latOffset, lng + lngOffset]);
|
||||||
|
bounds = bounds ? bounds.extend(pb) : pb;
|
||||||
|
});
|
||||||
|
if (bounds) map.fitBounds(bounds, { padding: [50, 50], maxZoom: 12, animate: true, duration: 0.6 });
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}, [pins, pinRadius, map]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const StormMap = memo(({
|
const StormMap = memo(({
|
||||||
events, selectedId, pinnedIds, onSelect, theme, flyCount, selectedStorm,
|
events, selectedId, pinnedIds, onSelect, theme, flyCount, selectedStorm,
|
||||||
stormZones, isDrawing, draftPolygon, onAddPoint, onFinishPolygon,
|
stormZones, isDrawing, draftPolygon, onAddPoint, onFinishPolygon,
|
||||||
@@ -265,8 +294,9 @@ const StormMap = memo(({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<MapFlyTo storm={selectedStorm} flyCount={flyCount} />
|
<MapFlyTo storm={selectedStorm} flyCount={flyCount} />
|
||||||
|
<FitToPins pins={pins} pinRadius={pinRadius} />
|
||||||
|
|
||||||
{/* Storm polygons — only selected + pinned (or all if none active) */}
|
{/* Storm polygons — only selected + pinned events */}
|
||||||
{events.map(storm => {
|
{events.map(storm => {
|
||||||
const sty = SEVERITY_STYLES[storm.severity] ?? SEVERITY_STYLES.trace;
|
const sty = SEVERITY_STYLES[storm.severity] ?? SEVERITY_STYLES.trace;
|
||||||
const isSelected = storm.id === selectedId;
|
const isSelected = storm.id === selectedId;
|
||||||
@@ -993,6 +1023,7 @@ const StormIntelPage = () => {
|
|||||||
const [pins, setPins] = useState([]); // array of [lat,lng]
|
const [pins, setPins] = useState([]); // array of [lat,lng]
|
||||||
const [pinRadius, setPinRadius] = useState(5); // applied radius (miles)
|
const [pinRadius, setPinRadius] = useState(5); // applied radius (miles)
|
||||||
const [radiusInput, setRadiusInput] = useState('5'); // draft value in input
|
const [radiusInput, setRadiusInput] = useState('5'); // draft value in input
|
||||||
|
const [showAllOnMap, setShowAllOnMap] = useState(false);
|
||||||
const MAX_PINS = 5;
|
const MAX_PINS = 5;
|
||||||
|
|
||||||
const applyRadius = useCallback(() => {
|
const applyRadius = useCallback(() => {
|
||||||
@@ -1042,12 +1073,15 @@ const StormIntelPage = () => {
|
|||||||
// Sidebar shows nearby events when pins exist, otherwise full filtered list
|
// Sidebar shows nearby events when pins exist, otherwise full filtered list
|
||||||
const sidebarEvents = pins.length > 0 ? nearbyEvents : displayEvents;
|
const sidebarEvents = pins.length > 0 ? nearbyEvents : displayEvents;
|
||||||
|
|
||||||
// Map shows: when pins exist → all nearby; else → selected+pinned only (or none until pins placed)
|
// Map shows: if showAllOnMap → all sidebar events; else → only selected/event-pinned.
|
||||||
|
// Location pins filter the sidebar list but don't auto-render all nearby polygons —
|
||||||
|
// NWS warning polygons can be 60-80 mi across and would visually flood the map.
|
||||||
|
const mapPool = pins.length > 0 ? nearbyEvents : displayEvents;
|
||||||
const mapEvents = useMemo(() => {
|
const mapEvents = useMemo(() => {
|
||||||
if (pins.length > 0) return nearbyEvents;
|
if (showAllOnMap) return mapPool;
|
||||||
if (!selectedStorm && pinnedIds.size === 0) return []; // nothing until pin placed
|
if (!selectedStorm && pinnedIds.size === 0) return [];
|
||||||
return displayEvents.filter(e => pinnedIds.has(e.id) || e.id === selectedStorm?.id);
|
return mapPool.filter(e => pinnedIds.has(e.id) || e.id === selectedStorm?.id);
|
||||||
}, [displayEvents, nearbyEvents, pins, selectedStorm, pinnedIds]);
|
}, [mapPool, showAllOnMap, selectedStorm, pinnedIds]);
|
||||||
|
|
||||||
const handleDropPin = useCallback((latlng) => {
|
const handleDropPin = useCallback((latlng) => {
|
||||||
setPins(prev => {
|
setPins(prev => {
|
||||||
@@ -1407,8 +1441,8 @@ const StormIntelPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search */}
|
{/* Search + Show all toggle */}
|
||||||
<div className="px-3 pt-2 pb-1.5 border-b border-zinc-100 dark:border-white/[0.06] shrink-0">
|
<div className="px-3 pt-2 pb-1.5 border-b border-zinc-100 dark:border-white/[0.06] shrink-0 space-y-1.5">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search size={12} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-zinc-400 pointer-events-none" />
|
<Search size={12} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-zinc-400 pointer-events-none" />
|
||||||
<input
|
<input
|
||||||
@@ -1424,6 +1458,18 @@ const StormIntelPage = () => {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Show all on map toggle */}
|
||||||
|
<button
|
||||||
|
onClick={() => setShowAllOnMap(v => !v)}
|
||||||
|
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-[11px] font-bold transition-colors w-full justify-center border ${
|
||||||
|
showAllOnMap
|
||||||
|
? 'bg-amber-500 border-amber-600 text-white'
|
||||||
|
: 'border-zinc-200 dark:border-white/[0.07] text-zinc-500 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:bg-white/5'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Map size={11} />
|
||||||
|
{showAllOnMap ? 'Showing all on map' : 'Show all on map'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Active pins strip */}
|
{/* Active pins strip */}
|
||||||
|
|||||||
Reference in New Issue
Block a user