fix(storm-intel): modal z-index, zone click-to-zoom, drawer close on fly-to, left padding
- ZoneAssignmentModal: portal to document.body with z-9999 so it renders above Leaflet map stacking context - ZoneLayer: clicking any zone polygon or centroid circle fits map bounds to that zone - handleFlyTo: closes detail drawer after zooming so the map is unobstructed - StormDetailDrawer: pl-5 on header, body, and footer for consistent left padding
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { X, MapPin, Check } from 'lucide-react';
|
import { X, MapPin, Check } from 'lucide-react';
|
||||||
import { DISPATCH_REPS } from '../../data/mockStore';
|
import { DISPATCH_REPS } from '../../data/mockStore';
|
||||||
@@ -38,14 +39,14 @@ const ZoneAssignmentModal = ({ open, onClose, onSave, polygon = [], linkedStormI
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return ReactDOM.createPortal(
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{open && (
|
{open && (
|
||||||
<>
|
<>
|
||||||
<motion.div
|
<motion.div
|
||||||
key="zam-backdrop"
|
key="zam-backdrop"
|
||||||
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
|
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
|
||||||
className="fixed inset-0 bg-black/40 backdrop-blur-[2px] z-[60]"
|
className="fixed inset-0 bg-black/40 backdrop-blur-[2px] z-[9998]"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -54,7 +55,7 @@ const ZoneAssignmentModal = ({ open, onClose, onSave, polygon = [], linkedStormI
|
|||||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||||
exit={{ opacity: 0, scale: 0.95, y: 10 }}
|
exit={{ opacity: 0, scale: 0.95, y: 10 }}
|
||||||
transition={{ type: 'spring', stiffness: 380, damping: 32 }}
|
transition={{ type: 'spring', stiffness: 380, damping: 32 }}
|
||||||
className="fixed inset-x-4 top-[8%] sm:inset-auto sm:left-1/2 sm:-translate-x-1/2 sm:top-[12%] sm:w-[420px] z-[61] bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-200 dark:border-white/[0.08] shadow-2xl overflow-hidden"
|
className="fixed inset-x-4 top-[8%] sm:inset-auto sm:left-1/2 sm:-translate-x-1/2 sm:top-[12%] sm:w-[420px] z-[9999] bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-200 dark:border-white/[0.08] shadow-2xl overflow-hidden"
|
||||||
>
|
>
|
||||||
{/* Color accent bar */}
|
{/* Color accent bar */}
|
||||||
<div className="h-1 w-full" style={{ backgroundColor: color }} />
|
<div className="h-1 w-full" style={{ backgroundColor: color }} />
|
||||||
@@ -188,7 +189,8 @@ const ZoneAssignmentModal = ({ open, onClose, onSave, polygon = [], linkedStormI
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>,
|
||||||
|
document.body
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,16 @@ const MapFlyTo = ({ storm, flyCount }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ZoneLayer = ({ zones }) => {
|
const ZoneLayer = ({ zones }) => {
|
||||||
|
const map = useMap();
|
||||||
if (!zones?.length) return null;
|
if (!zones?.length) return null;
|
||||||
|
|
||||||
|
const flyToZone = (polygon) => {
|
||||||
|
try {
|
||||||
|
const bounds = L.latLngBounds(polygon);
|
||||||
|
map.fitBounds(bounds, { padding: [50, 50], maxZoom: 15, animate: true, duration: 0.7 });
|
||||||
|
} catch { /* malformed polygon */ }
|
||||||
|
};
|
||||||
|
|
||||||
return zones.map(zone => {
|
return zones.map(zone => {
|
||||||
if (!zone.polygon?.length) return null;
|
if (!zone.polygon?.length) return null;
|
||||||
const centroid = zone.polygon.reduce(
|
const centroid = zone.polygon.reduce(
|
||||||
@@ -116,6 +125,7 @@ const ZoneLayer = ({ zones }) => {
|
|||||||
opacity: zone.status === 'completed' ? 0.45 : 0.85,
|
opacity: zone.status === 'completed' ? 0.45 : 0.85,
|
||||||
dashArray: zone.status === 'completed' ? '5 4' : undefined,
|
dashArray: zone.status === 'completed' ? '5 4' : undefined,
|
||||||
}}
|
}}
|
||||||
|
eventHandlers={{ click: () => flyToZone(zone.polygon) }}
|
||||||
>
|
>
|
||||||
<Tooltip direction="top" opacity={1} className="storm-tooltip" sticky={false}>
|
<Tooltip direction="top" opacity={1} className="storm-tooltip" sticky={false}>
|
||||||
<div style={{ fontFamily: 'system-ui,sans-serif', minWidth: 140 }}>
|
<div style={{ fontFamily: 'system-ui,sans-serif', minWidth: 140 }}>
|
||||||
@@ -126,11 +136,12 @@ const ZoneLayer = ({ zones }) => {
|
|||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Polygon>
|
</Polygon>
|
||||||
{/* Agent initials circle at centroid */}
|
{/* Agent initials circle at centroid — also clickable */}
|
||||||
<CircleMarker
|
<CircleMarker
|
||||||
center={centroid}
|
center={centroid}
|
||||||
radius={11}
|
radius={11}
|
||||||
pathOptions={{ color: zone.color, fillColor: zone.color, fillOpacity: 1, weight: 1.5 }}
|
pathOptions={{ color: zone.color, fillColor: zone.color, fillOpacity: 1, weight: 1.5 }}
|
||||||
|
eventHandlers={{ click: () => flyToZone(zone.polygon) }}
|
||||||
>
|
>
|
||||||
<Tooltip direction="top" opacity={1} className="storm-tooltip">
|
<Tooltip direction="top" opacity={1} className="storm-tooltip">
|
||||||
<p style={{ fontSize: 11, fontWeight: 700, color: '#18181b' }}>{zone.name}</p>
|
<p style={{ fontSize: 11, fontWeight: 700, color: '#18181b' }}>{zone.name}</p>
|
||||||
@@ -527,7 +538,7 @@ const StormDetailDrawer = ({ storm, open, onClose, onFlyTo, onAssignZone, canMan
|
|||||||
>
|
>
|
||||||
<div className="h-1 w-full shrink-0" style={{ backgroundColor: sty.dot }} />
|
<div className="h-1 w-full shrink-0" style={{ backgroundColor: sty.dot }} />
|
||||||
|
|
||||||
<div className="flex items-center justify-between px-4 py-3 border-b border-zinc-100 dark:border-white/[0.06] shrink-0">
|
<div className="flex items-center justify-between pl-5 pr-4 py-3 border-b border-zinc-100 dark:border-white/[0.06] shrink-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-8 h-8 rounded-lg flex items-center justify-center" style={{ backgroundColor: sty.dot + '18' }}>
|
<div className="w-8 h-8 rounded-lg flex items-center justify-center" style={{ backgroundColor: sty.dot + '18' }}>
|
||||||
<TypeIcon size={16} style={{ color: sty.dot }} />
|
<TypeIcon size={16} style={{ color: sty.dot }} />
|
||||||
@@ -547,7 +558,7 @@ const StormDetailDrawer = ({ storm, open, onClose, onFlyTo, onAssignZone, canMan
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-4">
|
<div className="flex-1 overflow-y-auto pl-5 pr-4 py-4 space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-black text-zinc-900 dark:text-white leading-tight">
|
<h2 className="text-lg font-black text-zinc-900 dark:text-white leading-tight">
|
||||||
{storm.areaName}
|
{storm.areaName}
|
||||||
@@ -617,7 +628,7 @@ const StormDetailDrawer = ({ storm, open, onClose, onFlyTo, onAssignZone, canMan
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-4 py-3 border-t border-zinc-100 dark:border-white/[0.06] space-y-2 shrink-0">
|
<div className="pl-5 pr-4 py-3 border-t border-zinc-100 dark:border-white/[0.06] space-y-2 shrink-0">
|
||||||
<button
|
<button
|
||||||
onClick={handleCreateLead}
|
onClick={handleCreateLead}
|
||||||
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl font-bold text-[13px] text-white transition-all hover:opacity-90 active:scale-[0.98]"
|
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl font-bold text-[13px] text-white transition-all hover:opacity-90 active:scale-[0.98]"
|
||||||
@@ -880,6 +891,7 @@ const StormIntelPage = () => {
|
|||||||
|
|
||||||
const handleFlyTo = useCallback(() => {
|
const handleFlyTo = useCallback(() => {
|
||||||
setFlyCount(c => c + 1);
|
setFlyCount(c => c + 1);
|
||||||
|
setDrawerOpen(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user