From 1d6d587ebf80af05a6fec8dd957d3e7ced4f2379 Mon Sep 17 00:00:00 2001
From: Satyam <95536056+Satyam-Rastogi@users.noreply.github.com>
Date: Sat, 21 Mar 2026 21:53:00 +0530
Subject: [PATCH] =?UTF-8?q?feat(dispatch):=20Phase=202=20=E2=80=94=20Dispa?=
=?UTF-8?q?tchLeadQueue=20with=20tabs,=20cards,=20and=20live=20drop-in?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- DispatchLeadQueue: 4-tab filter (All / Unassigned / At Risk / Confirmed)
with live counts; AnimatePresence popLayout for smooth card entrance/exit
- LeadCard: urgency badge (EMERGENCY pulse / HIGH / STANDARD), source icon,
customer name, lead type, address, status pill, assigned rep initials, time ago
— colored left border by urgency, blue ring when selected
- Storm mode: emergency leads float to top via sort; PRIORITY badge on
emergency_tarp cards; amber Unassigned pill in storm context
- 20s interval drops new leads from 3-lead pool; NEW badge fades after 5s
- LynkDispatchPage: left panel now renders DispatchLeadQueue; handleSelectLead
triggers 1.2s isProcessing state; right panel shows processing/selected/empty
states ready for Phase 3
---
src/components/dispatch/DispatchLeadQueue.jsx | 317 ++++++++++++++++++
src/pages/LynkDispatchPage.jsx | 122 ++++---
2 files changed, 392 insertions(+), 47 deletions(-)
create mode 100644 src/components/dispatch/DispatchLeadQueue.jsx
diff --git a/src/components/dispatch/DispatchLeadQueue.jsx b/src/components/dispatch/DispatchLeadQueue.jsx
new file mode 100644
index 0000000..59a4063
--- /dev/null
+++ b/src/components/dispatch/DispatchLeadQueue.jsx
@@ -0,0 +1,317 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { Globe, MapPin, Users, Phone, Clock, CloudLightning } from 'lucide-react';
+import { useMockStore, DISPATCH_REPS } from '../../data/mockStore';
+
+// ---------------------------------------------------------------------------
+// Config maps
+// ---------------------------------------------------------------------------
+const SOURCE_CONFIG = {
+ website_form: { icon: Globe, label: 'Web Form' },
+ canvassing: { icon: MapPin, label: 'Canvassing' },
+ referral: { icon: Users, label: 'Referral' },
+ call_center: { icon: Phone, label: 'Call Center' },
+};
+
+const LEAD_TYPE_LABELS = {
+ emergency_tarp: 'Emergency Tarp',
+ roof_inspection: 'Roof Inspection',
+ insurance_claim: 'Insurance Claim',
+ retail_estimate: 'Retail Estimate',
+};
+
+const URGENCY_CONFIG = {
+ emergency: { label: 'EMERGENCY', color: '#EF4444', bg: '#EF444415', pulse: true },
+ high: { label: 'HIGH', color: '#F59E0B', bg: '#F59E0B15', pulse: false },
+ standard: { label: 'STANDARD', color: '#6B7280', bg: '#6B728015', pulse: false },
+};
+
+const STATUS_CONFIG = {
+ unassigned: { label: 'Unassigned', color: '#EF4444', bg: '#EF444412' },
+ at_risk: { label: 'At Risk', color: '#F59E0B', bg: '#F59E0B12' },
+ assigned: { label: 'Assigned', color: '#3B82F6', bg: '#3B82F612' },
+ confirmed: { label: 'Confirmed', color: '#10B981', bg: '#10B98112' },
+};
+
+const TABS = [
+ { id: 'all', label: 'All' },
+ { id: 'unassigned', label: 'Unassigned' },
+ { id: 'at_risk', label: 'At Risk' },
+ { id: 'confirmed', label: 'Confirmed' },
+];
+
+const TAB_STATUS_FILTER = {
+ all: null,
+ unassigned: ['unassigned'],
+ at_risk: ['at_risk'],
+ confirmed: ['assigned', 'confirmed'],
+};
+
+// Fake leads that "drop in" every 20s during demo — cycled through pool
+const DROP_POOL = [
+ {
+ source: 'call_center', leadType: 'roof_inspection', urgency: 'high',
+ customer: { name: 'Marcus Webb' },
+ property: { address: '1620 Mapleshade Ln', city: 'Plano', state: 'TX', zip: '75075', lat: 33.0545, lng: -96.7634 },
+ estimatedDuration: 45, status: 'unassigned', arrivedMinsAgo: 0, assignedRepId: null,
+ },
+ {
+ source: 'website_form', leadType: 'emergency_tarp', urgency: 'emergency',
+ customer: { name: 'Sandra Kim' },
+ property: { address: '3488 Midway Rd', city: 'Plano', state: 'TX', zip: '75093', lat: 33.0712, lng: -96.8090 },
+ estimatedDuration: 90, status: 'unassigned', arrivedMinsAgo: 0, assignedRepId: null,
+ },
+ {
+ source: 'canvassing', leadType: 'insurance_claim', urgency: 'high',
+ customer: { name: 'David Okonkwo' },
+ property: { address: '892 McDermott Dr', city: 'Plano', state: 'TX', zip: '75024', lat: 33.0921, lng: -96.7812 },
+ estimatedDuration: 60, status: 'unassigned', arrivedMinsAgo: 0, assignedRepId: null,
+ },
+];
+
+// ---------------------------------------------------------------------------
+// LeadCard
+// ---------------------------------------------------------------------------
+const LeadCard = ({ lead, isSelected, isNew, onSelect, stormMode, accent, index }) => {
+ const urgCfg = URGENCY_CONFIG[lead.urgency] || URGENCY_CONFIG.standard;
+ const statusCfg = STATUS_CONFIG[lead.status] || STATUS_CONFIG.unassigned;
+ const srcCfg = SOURCE_CONFIG[lead.source] || SOURCE_CONFIG.call_center;
+ const SrcIcon = srcCfg.icon;
+ const assignedRep = lead.assignedRepId ? DISPATCH_REPS.find(r => r.id === lead.assignedRepId) : null;
+ const timeLabel = lead.arrivedMinsAgo === 0 ? 'Just now' : `${lead.arrivedMinsAgo} min ago`;
+ const borderColor = isSelected ? accent : urgCfg.color;
+
+ return (
+
+ {lead.customer.name}
+
+ {LEAD_TYPE_LABELS[lead.leadType] || lead.leadType}
+ ·
+ {lead.estimatedDuration} min est.
+
+ {lead.property.address}, {lead.property.city}
+ {lead.id}
No leads in this view
+New leads appear automatically
+{dispatchLeads.length} leads loaded
-Lead queue renders in Phase 2
-- {selectedLead ? 'Processing...' : 'No lead selected'} -
-- Click a lead in the queue to see AI-scored rep recommendations -
-- Recommendation engine — Phase 3 -
+Analyzing lead...
+AI scoring reps
+{selectedLead.id}
+{selectedLead.customer.name} · {selectedLead.property.address}
++ Recommendation engine — Phase 3 +
+No lead selected
+Click a lead in the queue to see AI-scored rep recommendations
+