import { useState, useEffect, useCallback, useRef } from 'react'; import { MapContainer, TileLayer, CircleMarker, Popup, Polyline, useMap } from 'react-leaflet'; import 'leaflet/dist/leaflet.css'; import { api } from '../api/client'; import MapFilterPanel from '../components/MapFilterPanel'; import L from 'leaflet'; delete L.Icon.Default.prototype._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png', iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png', shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png', }); function MapUpdater({ center, zoom }) { const map = useMap(); useEffect(() => { if (center) map.flyTo(center, zoom, { duration: 1.2 }); }, [center, zoom, map]); return null; } const RISK_COLORS = { critical: '#f43f5e', high: '#f59e0b', medium: '#60a5fa', low: '#10b981', }; function getRiskColor(score) { if (score >= 80) return RISK_COLORS.critical; if (score >= 60) return RISK_COLORS.high; if (score >= 40) return RISK_COLORS.medium; return RISK_COLORS.low; } function getRiskLabel(score) { if (score >= 80) return 'CRITICAL'; if (score >= 60) return 'HIGH'; if (score >= 40) return 'MEDIUM'; return 'LOW'; } const REGION_CENTERS = { all: { center: [28, 90], zoom: 4 }, china: { center: [35.86, 104.2], zoom: 4 }, india: { center: [22, 78.9], zoom: 5 }, }; // ─── Stat Card ─────────────────────────────────────────────────────────── function StatCard({ icon, value, label, color }) { return (
{icon}
{value}
{label}
); } // ─── Province Detail Sidebar ───────────────────────────────────────────── function ProvinceDetail({ detail, onClose }) { if (!detail) return null; const color = getRiskColor(detail.risk_score); const label = getRiskLabel(detail.risk_score); const isIndia = detail.region === 'india'; return (
{isIndia ? '🇮🇳' : '🇨🇳'}

{detail.name}

{label} — {detail.risk_score.toFixed(1)}
{/* Risk Gauge */}
{detail.description && (

{detail.description}

)} {/* Quick Stats */}
{detail.factories?.length > 0 && (
Factories
{detail.factories.length}
)}
Shocks
0 ? '#f43f5e' : 'var(--text)', marginTop: 2 }}> {detail.shocks?.length || 0}
{/* Active Shocks */} {detail.shocks?.length > 0 && (
⚠ Active Shocks
{detail.shocks.slice(0, 4).map(s => (
{(s.event_type || 'alert').replace(/_/g, ' ').toUpperCase()}
{s.title}
))}
)} {/* Top Entities */} {detail.top_entities?.length > 0 && (
{isIndia ? 'Top Consumed Drugs' : 'Dependent Products'}
{detail.top_entities.map(e => ( {e.name} ))}
)} {/* Factories List */} {detail.factories?.length > 0 && (
Key Facilities
{detail.factories.map((f, i) => (
{f}
))}
)}
); } // ─── Map Legend ─────────────────────────────────────────────────────────── function MapLegend() { const items = [ { color: RISK_COLORS.critical, label: 'Critical (80+)' }, { color: RISK_COLORS.high, label: 'High (60-79)' }, { color: RISK_COLORS.medium, label: 'Medium (40-59)' }, { color: RISK_COLORS.low, label: 'Low (<40)' }, ]; return (
Risk Level
{items.map(it => (
{it.label}
))}
Supply Corridor
); } // ─── Main Map Component ────────────────────────────────────────────────── export default function MapView() { const [heatmap, setHeatmap] = useState({ points: [] }); const [corridors, setCorridors] = useState({ corridors: [] }); const [stats, setStats] = useState(null); const [filters, setFilters] = useState({ sector: 'both', risk_min: 0, shock_type: '', region: 'all' }); const [selectedProvince, setSelectedProvince] = useState(null); const [provinceDetail, setProvinceDetail] = useState(null); const [showCorridors, setShowCorridors] = useState(false); const [loading, setLoading] = useState(true); const debounceRef = useRef(null); const regionConf = REGION_CENTERS[filters.region] || REGION_CENTERS.all; const fetchMapData = useCallback(async (f) => { setLoading(true); try { const [hm, cr, st] = await Promise.all([ api.getMapHeatmap(f), api.getMapCorridors(f.sector), api.getMapStats(), ]); setHeatmap(hm); setCorridors(cr); setStats(st); } catch (err) { console.error('Map data fetch failed', err); } finally { setLoading(false); } }, []); useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => fetchMapData(filters), 300); return () => clearTimeout(debounceRef.current); }, [filters, fetchMapData]); useEffect(() => { if (selectedProvince) { api.getMapProvince(selectedProvince, filters.sector) .then(setProvinceDetail) .catch(console.error); } else { setProvinceDetail(null); } }, [selectedProvince, filters.sector]); const chinaPoints = (heatmap.points || []).filter(p => p.region === 'china'); const indiaPoints = (heatmap.points || []).filter(p => p.region === 'india'); const totalPoints = heatmap.points?.length || 0; return (
{/* ── Left Sidebar ────────────────────────────────────────────── */}
{/* Title */}
⊕

Supply Map

Real-time geospatial intelligence across {totalPoints} monitored nodes.

{/* Quick Stats */} {stats && (
)} {/* Filter Panel */} setShowCorridors(v => !v)} /> {/* Province Detail */} setSelectedProvince(null)} />
{/* ── Map Container ───────────────────────────────────────────── */}
{/* Loading Indicator */} {loading && (
Updating map data…
)} {/* Supply Corridors */} {showCorridors && corridors.corridors?.map(c => { const riskColor = getRiskColor(c.risk_score || 30); return (
🇨🇳 {c.source.name} ➜ 🇮🇳 {c.target.name}
{c.weight} dependent product{c.weight > 1 ? 's' : ''} on this route
{c.affected_entities?.length > 0 && (
{c.affected_entities.map(e => ( {e} ))}
)}
); })} {/* China Province Markers */} {chinaPoints.map(p => { const color = getRiskColor(p.risk_score); const radius = Math.max(8, Math.min(28, p.risk_score / 3.5)); const isShocked = p.shock_count > 0; return ( setSelectedProvince(p.id) }} >
🇨🇳 {p.name}
Risk: {p.risk_score.toFixed(1)}
{isShocked && (
⚠ {p.shock_count} Active Shock{p.shock_count > 1 ? 's' : ''}
)}
); })} {/* India State Markers */} {indiaPoints.map(p => { const color = getRiskColor(p.risk_score); const radius = Math.max(7, Math.min(22, p.risk_score / 4)); return ( setSelectedProvince(p.id) }} >
🇮🇳 {p.name}
Dependency Risk: {p.risk_score.toFixed(1)}
{p.shock_count > 0 && (
📡 {p.shock_count} upstream shock{p.shock_count > 1 ? 's' : ''}
)}
); })}
{/* Legend */} {/* Map CSS Overrides */}