Map now shows only the currently selected event + any pinned events.
If nothing is selected or pinned, all events are shown (default state).
- Pin button (MapPin icon) on each card — click to persist that event
on the map independently of selection changes
- Card click = select (amber border, opens drawer, flies to zone)
- Pinned cards get a subtle amber tint + filled pin icon
- Pinned events on map: tighter dash + slightly elevated opacity vs unselected
- List header shows "X pinned" count + "Clear pins" button
- Selection clear button still works independently of pins
IEM geojson/sbw.geojson has CORS open to all origins so the browser can
fetch it directly. Moves all transformation logic into useStormEvents.js,
eliminating the need for vercel dev or any server-side proxy. Works with
plain pnpm run dev on localhost. Falls back to MOCK_STORM_EVENTS if IEM
is unreachable. api/storm-polygons.js kept as optional edge cache layer.
Use /geojson/sbw.geojson?sts=...&ets=... instead of year param (which was
being ignored). Handle MultiPolygon geometry type from IEM response. Use
correct 'issue' date field. Filter phenomena server-side (SV/TO/FF only).
Use hailtag field for accurate severity on SVR warnings.
api/storm-polygons.js — Vercel proxy that fetches historical NWS storm-based
warnings (severe thunderstorm + tornado) from IEM for WFO FWD (Fort Worth),
covering Collin County / Plano TX. Filters to local bounding box, derives
severity from polygon area (area2163 km²), estimates impacted homes from
density, derives area name from centroid. Cached 12h on Vercel edge.
useStormEvents.js — switched to real IEM data (USE_MOCK=false). Fetches full
36-month dataset once on mount; all range/type/severity filtering stays
client-side in useMemo for instant UI response. Falls back to
MOCK_STORM_EVENTS when /api routes are unavailable (local vite dev).
Updated all 7 MOCK_STORM_EVENTS polygons to use irregular, amoeba-like
swath outlines matching real hail storm paths from NWS reports. Also
renamed project to LynkedUpPro-CRM in package.json.
- Alert/warn forecast cards: day label, temp, and rain% now use high-contrast red/orange text instead of muted gray — readable in both light and dark mode
- Alert card background slightly more opaque (bg-red-100/dark:bg-red-500/15) for better definition
- Range chips: 12 Mo / 18 Mo / 24 Mo / 36 Mo replacing old 1m/3m/6m/1y options
- Custom range: CalendarRange chip reveals two date inputs (from/to) with amber focus ring
- useStormEvents: DATE_RANGE_DAYS updated for new keys; custom range filters by customStart/customEnd; allEvents now uses 36m baseline
- 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
- 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)
Adds an Attribution tab to Storm Intel showing full ROI breakdown of storm-sourced canvassing.
- MOCK_STORM_ATTRIBUTION_LEADS: 35 realistic leads across 7 storms, with addresses, statuses
(New / Contacted / Appointed / Closed) and contractValues for closed deals — $218,900 total revenue
- useStormAttribution hook: merges historical mock leads with any session-created storm leads,
aggregates per-storm stats (leads, closed, conversion rate, revenue, pipeline estimate, avg deal size)
- Attribution tab in StormIntelPage:
- 6 summary stat cards: Total Leads, Closed, Conversion Rate, Revenue, Pipeline, Avg Deal
- Grouped bar chart (Recharts) — leads generated vs closed per storm, bars colored by severity
- Per-storm revenue cards ranked by revenue: severity badge, stats row, animated revenue
progress bar relative to top performer
- "Top Performing Zone" insight card at bottom
- Tab switcher (Map / Attribution) added to header; filter bar only shows on Map tab
When a canvasser taps "Create Lead from Zone" in the Storm Intel detail drawer,
router state carries the storm context into CreateLeadPage:
- CreateLeadPage reads stormSource from location.state; initialises the form with
city=Plano, state=TX, leadSource=Door Knock, urgency=high for significant/severe
storms, and a pre-filled notes field with zone name, hail size, and date
- Amber storm banner renders below the page header showing zone name, hail size,
and a note that urgency was auto-set; dismissible with X
- stormSource is saved on the lead object via the existing addLead spread
- LeadsListPage LeadCard shows an amber "Storm Zone" badge when lead.stormSource exists
- KanbanCard footer shows a compact amber "Storm" label for storm-sourced leads
Adds a new Storm Intel section to the sidebar and routes for ADMIN and OWNER roles.
- StormIntelPage: full-height layout with Leaflet map + storm polygon overlays,
severity-coded color system, horizontal filter bar (date range / severity / sort),
and a scrollable storm event list
- Storm detail drawer slides in from right with hail size, est. homes, days ago,
conversion window indicator (Hot Zone / Warm / Cold / Too Fresh), intel text,
and quick-action buttons (Create Lead, Open in Dispatch, Assign Canvassers stub)
- Clicking a storm card or map polygon zooms the map to that polygon and opens the drawer
- useStormEvents hook with USE_MOCK flag, date-range cutoff filter, type/severity filter,
sort by date or severity, and conversionWindow() helper
- api/storm-events.js: NWS api.weather.gov proxy (free, no auth needed), parses
GeoJSON polygons and hail size from alert descriptions
- MOCK_STORM_EVENTS: 7 realistic Plano TX storm events with accurate polygon coords
- Sidebar: CloudLightning "Storm Intel" entry added for OWNER and ADMIN nav
Kanban pipeline board:
- Fix h-full flex-col layout so board fills viewport height on all screen sizes
- KanbanColumn drop zone: flex-1 min-h-0 so it expands to fill full column height
- KanbanCard: extract KanbanCardDisplay as named export for DragOverlay rendering above overflow containers
- KanbanPage: lift DndContext to page root; add "Add Stage" shortcut to page header
- PageTransition: remove slide-in-from-bottom-4 transform — the CSS transform ancestor was creating a containing block for position:fixed DragOverlay, causing drag ghost to render at a viewport offset
- Migrate fireCampaignToast to toast.custom() Sonner v2 API with explicit card styling
Project & lead views:
- LeadProjectPage: enrich project detail with richer info sections aligned with project stage
- OwnerProjectDetail: expand construction project detail with additional data panels
- OwnerProjectList: project list view improvements
- mockStore: extend lead/project data to support richer project detail rendering
- Add TemplateAccessModal: role-level toggles (All Admins / All Field Agents) + per-person overrides
- Three-state access logic: role grant, individual direct grant, individual exclusion override
- Excluded people (role ON but toggled off) pinned to top with amber 'Excluded' chip
- Direct grants (role OFF but toggled on) show green 'Direct' chip
- canEditTemplates replaces isOwner for all template CRUD guards in EstimatesPage
- Owner retains exclusive access to Manage Access button
- State persisted in mockStore: templateAccessRoles, templateAccessUsers, templateAccessExcluded
- Add EstimatesPage with Estimates + Templates tabs, KPI strip, search, and filter chips
- Estimate detail drawer with pipeline status tracker (Draft→Sent→Waiting Approval→Approved) and edge statuses (Follow Up Required, Revision Required, Rejected, Expired); clickable nodes update estimate status live
- Share bar: WhatsApp, iMessage, Email, PDF download (jsPDF + autotable, light-mode A4)
- ALL_STATUS_CONFIG as shared status registry across cards, badge, filters, and tracker
- Sort controls: Newest First / Oldest First / By Status
- Date range filter with From/To date inputs and clear button
- Fix unit price discrepancy: Unit Price = clientPrice/qty so Qty × Unit Price = Total
- Fix chatbot overlap: pr-20 on drawer footer keeps grand total visible
- Fix card vs drawer total mismatch: computeGrandTotal extracted to estimateExport.js, used as single source of truth in cards, drawer, PDF, and WhatsApp message
- TemplateEditorModal: Owner CRUD for master templates, duplicate with Save as Copy / Replace Original toggle
- EstimatesPage: tabbed hub with Estimates and Templates tabs, KPI strip, search + filter
- EstimateDetailModal: right-side drawer showing scope of work, computed line items, and financial summary from template + roof area
- TemplateEditorModal: create/edit/duplicate templates with segmented mode toggle
- Owner-only template management (create, edit, duplicate, delete); Admin/FA read-only view
- Duplicate mode: toggle between 'Save as new copy' (default, editable name) and 'Replace original'
- MASTER_TEMPLATES and MOCK_ESTIMATES moved into mockStore with full CRUD helpers
- TemplateSelectionModal now reads from mockStore instead of local stub
- Sidebar nav updated to point to /estimates hub for all roles
- Routes added: /emp/fa/estimates, /admin/estimates, /owner/estimates
- DispatchMapPanel: replace h-full/height:100% with explicit inline px height
on both wrapper div and MapContainer — CollapsePanel uses height:auto which
makes percentage heights resolve to 0 giving Leaflet a zero-size canvas
- DispatchMapPanel: accept mapHeight prop from parent for responsive sizing
- LynkDispatchPage: compute responsive mapHeight state (300px mobile →
380px sm → 460px md → 520px lg tablet) updated on window resize; passed
as prop to DispatchMapPanel on all non-desktop renders
- LynkDispatchPage: add missing reps={effectiveReps} prop on mobile map render
so live rep status cycling is reflected on all breakpoints
- LynkDispatchPage: add missing onQuickAssign={handleAssign} on desktop lead
queue render so Quick Assign button works on desktop
- LynkDispatchPage: add whitespace-nowrap to efficiency badge to prevent
'89% AI-Dispatched' wrapping to two lines in header
- LynkDispatchPage: dispatch synthetic resize event 260ms after map panel
expands on mobile/tablet so Leaflet invalidateSize fires post-animation
- DispatchMapPanel: add second invalidateSize call at 320ms (belt-and-suspenders
alongside 50ms) to cover 220ms CollapsePanel animation on slower devices
- DispatchLeadQueue: enrich DROP_POOL leads with phone, email, source
sub-details and notes so Lead Quick View shows complete data on drop-in leads