feat(storm-intel): Phase 4+5 — Tomorrow.io forecast overlay and canvasser zone assignment

- Tomorrow.io 7-day forecast strip with weather icons, storm score bar, and per-day risk color coding
- High-risk alert banner in forecast strip when any day scores >=65
- api/storm-forecast.js: Vercel proxy that reads TOMORROW_API_KEY server-side; graceful empty response when key unset
- useStormForecast hook: USE_MOCK=false with realistic MOCK_FORECAST fallback
- ZoneDrawingLayer: click-to-place polygon vertices using react-leaflet useMapEvents; disables double-click zoom while active; shows live vertex dots + dashed preview polygon
- ZoneLayer: renders stormZones as colored semi-transparent polygons with agent initials at centroid
- ZoneAssignmentModal: name + canvasser picker + color selector + optional storm link; portaled modal
- StormIntelPage: Draw Zone button (Admin/Owner only); drawing state/cancel flow; stormZones displayed on map; zone summary strip in sidebar; StormDetailDrawer has working "Draw Canvasser Zone" action
- FieldStormZonePage: canvasser mobile view at /field/storm-zones showing assigned zones with map, linked storm details, conversion window badge, and peak window alert
- Layout: Storm Zones entry in FIELD_AGENT nav
- App: /field/storm-zones route (FIELD_AGENT + Admin + Owner)
This commit is contained in:
Satyam-Rastogi
2026-05-18 18:06:17 +05:30
parent df50f27ba6
commit 12bd2fe0de
8 changed files with 1199 additions and 115 deletions
+61
View File
@@ -5080,6 +5080,7 @@ export const MockStoreProvider = ({ children }) => {
const [templateAccessExcluded, setTemplateAccessExcluded] = useState([]);
const [kanbanColumns, setKanbanColumns] = useState(KANBAN_COLUMNS_INITIAL);
const [kanbanLeads, setKanbanLeads] = useState(KANBAN_LEADS_INITIAL);
const [stormZones, setStormZones] = useState(INITIAL_STORM_ZONES);
// Commission Settings Org defaults + per-user overrides
const [orgCommissionDefaults, setOrgCommissionDefaults] = useState({
@@ -5396,6 +5397,20 @@ export const MockStoreProvider = ({ children }) => {
removeUserCommissionOverride: (userId) => {
setUserCommissionOverrides(prev => prev.filter(o => o.userId !== userId));
},
// Storm Zones
stormZones,
addStormZone: (zone) => {
const newZone = { ...zone, id: `sz-${Date.now()}`, createdAt: new Date().toISOString() };
setStormZones(prev => [...prev, newZone]);
return newZone;
},
updateStormZone: (id, data) => {
setStormZones(prev => prev.map(z => z.id === id ? { ...z, ...data } : z));
},
deleteStormZone: (id) => {
setStormZones(prev => prev.filter(z => z.id !== id));
},
updateProject: (projectId, data) => {
setProjects(prev => prev.map(p => p.id === projectId ? { ...p, ...data } : p));
},
@@ -5705,6 +5720,52 @@ export const MOCK_STORM_EVENTS = [
// Storm-sourced leads canvasser door-knock results linked to storm events
// Each lead has a stormSource { id, areaName, date, maxHailSize, severity } and
// a contractValue (number) for Closed leads. createdAt is shortly after storm date.
// Canvasser zones polygons drawn on Storm Intel map, assigned to agents
export const INITIAL_STORM_ZONES = [
{
id: 'sz-001',
name: 'Spring Creek Zone A',
stormId: 'SE-001',
polygon: [
[33.108, -96.720], [33.100, -96.702], [33.086, -96.702],
[33.080, -96.720], [33.086, -96.738], [33.100, -96.738],
],
assignedAgentId: 'REP-01',
assignedAgentName: 'Carlos Mendez',
color: '#F59E0B',
status: 'active',
createdAt: '2026-04-29T08:00:00Z',
},
{
id: 'sz-002',
name: 'Parker Rd Zone B',
stormId: 'SE-003',
polygon: [
[33.033, -96.712], [33.025, -96.697], [33.011, -96.697],
[33.003, -96.712], [33.011, -96.727], [33.025, -96.727],
],
assignedAgentId: 'REP-02',
assignedAgentName: 'Aisha Kumar',
color: '#8B5CF6',
status: 'active',
createdAt: '2026-02-04T09:00:00Z',
},
{
id: 'sz-003',
name: 'Haggard Park Zone C',
stormId: 'SE-006',
polygon: [
[33.047, -96.774], [33.039, -96.757], [33.025, -96.757],
[33.017, -96.774], [33.025, -96.791], [33.039, -96.791],
],
assignedAgentId: 'REP-04',
assignedAgentName: 'Nina Patel',
color: '#10B981',
status: 'completed',
createdAt: '2025-11-06T07:30:00Z',
},
];
export const MOCK_STORM_ATTRIBUTION_LEADS = [
// SE-001 · Apr 28 2026 · Severe · NE Plano
{ id: 'SAL-001', firstName: 'John', lastName: 'Martinez', address: '4821 Spring Creek Pkwy', city: 'Plano', state: 'TX', urgency: 'high', leadSource: 'Door Knock', status: 'Contacted', contractValue: null, createdAt: '2026-04-29T10:14:00Z', stormSource: { id: 'SE-001', areaName: 'NE Plano / Spring Creek Pkwy', date: '2026-04-28', maxHailSize: 2.5, severity: 'severe' } },