diff --git a/src/pages/StormIntelPage.jsx b/src/pages/StormIntelPage.jsx index 8b9039d..4ba9478 100644 --- a/src/pages/StormIntelPage.jsx +++ b/src/pages/StormIntelPage.jsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useRef, useEffect, memo } from 'react'; +import React, { useState, useCallback, useRef, useEffect, memo, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; import { @@ -16,7 +16,7 @@ import { Home, AlertTriangle, TrendingUp, Users, Calendar, MapPin, Flame, Snowflake, Clock, ArrowRight, Eye, UserPlus, Loader2, DollarSign, Target, BarChart2, Map, Pencil, Sun, Cloud, - CloudRain, CloudDrizzle, CheckCircle, XCircle, CalendarRange, + CloudRain, CloudDrizzle, CheckCircle, XCircle, CalendarRange, Pin, } from 'lucide-react'; import { toast } from 'sonner'; import { useTheme } from '../context/ThemeContext'; @@ -211,7 +211,7 @@ const ZoneDrawingLayer = ({ draftPolygon, onAddPoint, onFinish }) => { }; const StormMap = memo(({ - events, selectedId, onSelect, theme, flyCount, selectedStorm, + events, selectedId, pinnedIds, onSelect, theme, flyCount, selectedStorm, stormZones, isDrawing, draftPolygon, onAddPoint, onFinishPolygon, }) => { return ( @@ -233,10 +233,11 @@ const StormMap = memo(({ - {/* Existing storm polygons */} + {/* Storm polygons — only selected + pinned (or all if none active) */} {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 isPinned = pinnedIds?.has(storm.id); return ( !isDrawing && onSelect(storm), mouseover: (e) => { e.target.setStyle({ fillOpacity: Math.min(sty.fillOpacity + 0.1, 0.4), weight: sty.weight + 1 }); }, mouseout: (e) => { if (storm.id === selectedId) return; - e.target.setStyle({ fillOpacity: sty.fillOpacity, weight: sty.weight }); + e.target.setStyle({ + fillOpacity: isPinned ? Math.min(sty.fillOpacity + 0.06, 0.36) : sty.fillOpacity, + weight: isPinned ? sty.weight + 0.5 : sty.weight, + }); }, }} > @@ -439,7 +447,7 @@ const ForecastStrip = ({ forecast, loading, alertDay, hasStormAlert }) => { // ─── Storm card ─────────────────────────────────────────────────────────────── -const StormCard = ({ storm, isSelected, onSelect }) => { +const StormCard = ({ storm, isSelected, isPinned, onSelect, onTogglePin }) => { const sty = SEVERITY_STYLES[storm.severity] ?? SEVERITY_STYLES.trace; const conv = conversionWindow(storm.date); const TypeIcon = TYPE_ICONS[storm.type] ?? CloudLightning; @@ -452,6 +460,8 @@ const StormCard = ({ storm, isSelected, onSelect }) => { className={`w-full text-left rounded-xl border transition-all duration-150 overflow-hidden ${ isSelected ? 'border-amber-400 dark:border-amber-500 bg-amber-50/60 dark:bg-amber-500/5 shadow-md' + : isPinned + ? 'border-amber-300/60 dark:border-amber-500/30 bg-amber-50/30 dark:bg-amber-500/[0.03]' : 'border-zinc-200 dark:border-white/[0.07] bg-white dark:bg-zinc-900/60 hover:border-zinc-300 dark:hover:border-white/15 hover:shadow-sm' }`} > @@ -467,9 +477,23 @@ const StormCard = ({ storm, isSelected, onSelect }) => {

{storm.areaName}

- - {days === 0 ? 'Today' : `${days}d ago`} - +
+ + {days === 0 ? 'Today' : `${days}d ago`} + + {/* Pin button */} + +

{dateLabel}

@@ -889,6 +913,7 @@ const StormIntelPage = () => { const [severityFilter, setSeverityFilter] = useState('all'); const [sortBy, setSortBy] = useState('date'); const [selectedStorm, setSelectedStorm] = useState(null); + const [pinnedIds, setPinnedIds] = useState(new Set()); const [drawerOpen, setDrawerOpen] = useState(false); const [flyCount, setFlyCount] = useState(0); const [showFilters, setShowFilters] = useState(false); @@ -903,6 +928,20 @@ const StormIntelPage = () => { const { events: allEvents } = useStormEvents({ dateRange: '36m' }); const { forecast, loading: forecastLoading, alertDay, hasStormAlert } = useStormForecast(); + const handleTogglePin = useCallback((id) => { + setPinnedIds(prev => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); else next.add(id); + return next; + }); + }, []); + + // Map shows only selected + pinned events; if nothing active, show all + const mapEvents = useMemo(() => { + if (!selectedStorm && pinnedIds.size === 0) return events; + return events.filter(e => pinnedIds.has(e.id) || e.id === selectedStorm?.id); + }, [events, selectedStorm, pinnedIds]); + const handleSelect = useCallback((storm) => { setSelectedStorm(storm); setDrawerOpen(true); @@ -1168,8 +1207,9 @@ const StormIntelPage = () => { ) : ( {

{events.length} Event{events.length !== 1 ? 's' : ''} + {pinnedIds.size > 0 && ( + · {pinnedIds.size} pinned + )}

- {selectedStorm && ( - - )} +
+ {pinnedIds.size > 0 && ( + + )} + {selectedStorm && ( + + )} +
{/* Zone summary strip */} @@ -1246,7 +1299,9 @@ const StormIntelPage = () => { key={storm.id} storm={storm} isSelected={selectedStorm?.id === storm.id} + isPinned={pinnedIds.has(storm.id)} onSelect={handleSelect} + onTogglePin={handleTogglePin} /> )) )}