feat(dispatch): additional features — quick assign, lead quick view, rep cycling, AI efficiency score, AI Says block
- Quick Assign: accent Zap pill on unassigned lead cards; dispatches top AI rec in one tap; 2.2s confirmation state - LeadQuickViewModal: right-drawer with client contact (copy buttons), property, source sub-details, editable notes with double-gate confirmation - Rep Status Live Cycling: repStatuses state map overrides DISPATCH_REPS.status; effectiveReps prop passed to map and AI drawer; assign sets rep to en_route, reverts to available after 60s - Dispatch Efficiency Score: aiCount/totalCount seeded 8/9 (89%); AI assign increments both, manual override increments total only; shown as badge in header - Efficiency badge label updated to '89% AI-Dispatched' with tooltip explaining the metric - AI Says block in RecommendationDrawer: rep identity row (avatar, name, status/distance, large score) + italic AI insight quote above recommendations - Lead aging badges: amber Aging (20+ min), red pulsing Overdue (40+ min) on unassigned leads - Search bar in Lead Queue: filters by customer name, address, city; combines with active tab filter - mockStore enrichment: email + notes + source sub-details on all 12 leads; rating/closeRate on all 5 reps; distanceMi/aiInsight/conflictFlag on all recommendations; updateLeadNotes() action; DISPATCH_WEEKLY_STATS export - LynkDispatchPage wired for Phase 5 KPIs, storm mode, chart modal, log drawer, and all new features
This commit is contained in:
+167
-18
@@ -2731,7 +2731,7 @@ export const DISPATCH_REPS = [
|
||||
currentLat: 33.0612, currentLng: -96.7891,
|
||||
todayAppointments: 3, maxDaily: 6,
|
||||
skills: ['insurance_claim', 'retail_estimate'],
|
||||
performanceScore: 94,
|
||||
performanceScore: 94, rating: 4.8, closeRate: 82,
|
||||
},
|
||||
{
|
||||
id: 'REP-02', name: 'Aisha Kumar', initials: 'AK',
|
||||
@@ -2739,7 +2739,7 @@ export const DISPATCH_REPS = [
|
||||
currentLat: 33.0812, currentLng: -96.7234,
|
||||
todayAppointments: 2, maxDaily: 6,
|
||||
skills: ['roof_inspection', 'emergency_tarp'],
|
||||
performanceScore: 88,
|
||||
performanceScore: 88, rating: 4.9, closeRate: 76,
|
||||
},
|
||||
{
|
||||
id: 'REP-03', name: 'Tom Bradley', initials: 'TB',
|
||||
@@ -2747,7 +2747,7 @@ export const DISPATCH_REPS = [
|
||||
currentLat: 33.0389, currentLng: -96.7401,
|
||||
todayAppointments: 5, maxDaily: 6,
|
||||
skills: ['retail_estimate', 'roof_inspection'],
|
||||
performanceScore: 79,
|
||||
performanceScore: 79, rating: 4.5, closeRate: 68,
|
||||
},
|
||||
{
|
||||
id: 'REP-04', name: 'Nina Patel', initials: 'NP',
|
||||
@@ -2755,7 +2755,7 @@ export const DISPATCH_REPS = [
|
||||
currentLat: 33.0516, currentLng: -96.7512,
|
||||
todayAppointments: 1, maxDaily: 6,
|
||||
skills: ['insurance_claim', 'roof_inspection', 'emergency_tarp'],
|
||||
performanceScore: 91,
|
||||
performanceScore: 91, rating: 4.7, closeRate: 79,
|
||||
},
|
||||
{
|
||||
id: 'REP-05', name: 'Derek Walsh', initials: 'DW',
|
||||
@@ -2763,81 +2763,105 @@ export const DISPATCH_REPS = [
|
||||
currentLat: 33.0198, currentLng: -96.7234,
|
||||
todayAppointments: 4, maxDaily: 6,
|
||||
skills: ['retail_estimate', 'roof_inspection'],
|
||||
performanceScore: 82,
|
||||
performanceScore: 82, rating: 4.6, closeRate: 74,
|
||||
},
|
||||
];
|
||||
|
||||
const DISPATCH_LEADS_INITIAL = [
|
||||
{
|
||||
id: 'LD-1001', source: 'call_center', leadType: 'emergency_tarp', urgency: 'emergency',
|
||||
customer: { name: 'John Smith', phone: '(972) 555-1001' },
|
||||
customer: { name: 'John Smith', phone: '(972) 555-1001', email: 'john.smith@gmail.com' },
|
||||
property: { address: '2612 Dunwick Dr', city: 'Plano', state: 'TX', zip: '75023', lat: 33.0708, lng: -96.7456 },
|
||||
callCenter: { id: 'CC-01', name: 'Plano Command Center' },
|
||||
notes: 'Customer reports major tarp blowoff after last night\'s storm — exposed decking on NE corner. Needs immediate coverage before rain resumes.',
|
||||
estimatedDuration: 60, status: 'unassigned', arrivedMinsAgo: 8, assignedRepId: null,
|
||||
},
|
||||
{
|
||||
id: 'LD-1002', source: 'website_form', leadType: 'roof_inspection', urgency: 'high',
|
||||
customer: { name: 'Maria Gonzalez', phone: '(972) 555-1002' },
|
||||
customer: { name: 'Maria Gonzalez', phone: '(972) 555-1002', email: 'maria.gonzalez@gmail.com' },
|
||||
property: { address: '4817 Shady Brook Ln', city: 'Plano', state: 'TX', zip: '75093', lat: 33.0652, lng: -96.8021 },
|
||||
webSource: { campaign: 'Google Ads — Storm Season 2026' },
|
||||
notes: 'Noticed curling shingles and granule loss in gutters. No active leak but concerned about upcoming storm season. Wants full inspection.',
|
||||
estimatedDuration: 45, status: 'unassigned', arrivedMinsAgo: 14, assignedRepId: null,
|
||||
},
|
||||
{
|
||||
id: 'LD-1003', source: 'canvassing', leadType: 'insurance_claim', urgency: 'high',
|
||||
customer: { name: 'Robert Chen', phone: '(972) 555-1003' },
|
||||
customer: { name: 'Robert Chen', phone: '(972) 555-1003', email: 'robert.chen@outlook.com' },
|
||||
property: { address: '1234 Oak Creek Blvd', city: 'Plano', state: 'TX', zip: '75075', lat: 33.0421, lng: -96.7138 },
|
||||
canvassedBy: { agentId: 'U-05', name: 'Marcus Webb' },
|
||||
notes: 'Filed insurance claim after hail event 3/18. Adjuster visit scheduled 3/28 — needs estimate in hand beforehand. Cooperative and responsive.',
|
||||
estimatedDuration: 45, status: 'assigned', arrivedMinsAgo: 22, assignedRepId: 'REP-01',
|
||||
},
|
||||
{
|
||||
id: 'LD-1004', source: 'referral', leadType: 'retail_estimate', urgency: 'standard',
|
||||
customer: { name: 'Sarah Mitchell', phone: '(972) 555-1004' },
|
||||
customer: { name: 'Sarah Mitchell', phone: '(972) 555-1004', email: 'sarah.mitchell@yahoo.com' },
|
||||
property: { address: '890 Custer Rd', city: 'Plano', state: 'TX', zip: '75075', lat: 33.0389, lng: -96.7334 },
|
||||
referredBy: { type: 'client', id: 'CL-2201', name: 'Tom Hargrove' },
|
||||
notes: 'Retail estimate for full reroof. No insurance involvement. Wants architectural shingles — GAF Timberline HDZ preferred. Budget-conscious.',
|
||||
estimatedDuration: 30, status: 'confirmed', arrivedMinsAgo: 35, assignedRepId: 'REP-03',
|
||||
},
|
||||
{
|
||||
id: 'LD-1005', source: 'call_center', leadType: 'roof_inspection', urgency: 'standard',
|
||||
customer: { name: 'James Wilson', phone: '(972) 555-1005' },
|
||||
customer: { name: 'James Wilson', phone: '(972) 555-1005', email: 'j.wilson85@gmail.com' },
|
||||
property: { address: '321 Independence Pkwy', city: 'Plano', state: 'TX', zip: '75023', lat: 33.0812, lng: -96.7521 },
|
||||
callCenter: { id: 'CC-02', name: 'DFW Ops Hub' },
|
||||
notes: 'At-risk — customer called twice to reschedule. Currently assigned but rep running behind. Possible missed appointment window.',
|
||||
estimatedDuration: 45, status: 'at_risk', arrivedMinsAgo: 47, assignedRepId: 'REP-02',
|
||||
},
|
||||
{
|
||||
id: 'LD-1006', source: 'website_form', leadType: 'insurance_claim', urgency: 'high',
|
||||
customer: { name: 'Lisa Thompson', phone: '(972) 555-1006' },
|
||||
customer: { name: 'Lisa Thompson', phone: '(972) 555-1006', email: 'lisathompson@icloud.com' },
|
||||
property: { address: '7654 Preston Rd', city: 'Plano', state: 'TX', zip: '75024', lat: 33.0934, lng: -96.8002 },
|
||||
webSource: { campaign: 'Organic Search' },
|
||||
notes: 'Large residential — ridge cap blown off, potential decking exposure. Adjuster already completed visit, needs supplement review before signing.',
|
||||
estimatedDuration: 60, status: 'unassigned', arrivedMinsAgo: 5, assignedRepId: null,
|
||||
},
|
||||
{
|
||||
id: 'LD-1007', source: 'canvassing', leadType: 'retail_estimate', urgency: 'standard',
|
||||
customer: { name: 'David Park', phone: '(972) 555-1007' },
|
||||
customer: { name: 'David Park', phone: '(972) 555-1007', email: 'dpark.plano@gmail.com' },
|
||||
property: { address: '2200 Alma Dr', city: 'Plano', state: 'TX', zip: '75075', lat: 33.0516, lng: -96.7289 },
|
||||
canvassedBy: { agentId: 'U-07', name: 'Priya Nair' },
|
||||
notes: 'Customer confirmed appointment. Wants two options: repair patch vs full replacement. Existing roof is 19 years old.',
|
||||
estimatedDuration: 30, status: 'confirmed', arrivedMinsAgo: 62, assignedRepId: 'REP-04',
|
||||
},
|
||||
{
|
||||
id: 'LD-1008', source: 'referral', leadType: 'roof_inspection', urgency: 'standard',
|
||||
customer: { name: 'Angela Moore', phone: '(972) 555-1008' },
|
||||
customer: { name: 'Angela Moore', phone: '(972) 555-1008', email: 'angela.moore72@gmail.com' },
|
||||
property: { address: '555 Legacy Dr', city: 'Plano', state: 'TX', zip: '75023', lat: 33.0744, lng: -96.7612 },
|
||||
referredBy: { type: 'customer', id: 'CU-0892', name: 'Daniel Reyes' },
|
||||
notes: 'Referral from existing customer. Friendly homeowner, flexible scheduling. Roof is 12 years old with no known damage — proactive inspection only.',
|
||||
estimatedDuration: 45, status: 'unassigned', arrivedMinsAgo: 28, assignedRepId: null,
|
||||
},
|
||||
{
|
||||
id: 'LD-1009', source: 'call_center', leadType: 'emergency_tarp', urgency: 'emergency',
|
||||
customer: { name: 'Carlos Rivera', phone: '(972) 555-1009' },
|
||||
customer: { name: 'Carlos Rivera', phone: '(972) 555-1009', email: 'c.rivera@hotmail.com' },
|
||||
property: { address: '3301 Avenue K', city: 'Plano', state: 'TX', zip: '75074', lat: 33.0198, lng: -96.6989 },
|
||||
callCenter: { id: 'CC-01', name: 'Plano Command Center' },
|
||||
notes: 'ACTIVE LEAK — water through master bedroom ceiling. Family with newborn on premises. Must dispatch immediately regardless of weather conditions.',
|
||||
estimatedDuration: 90, status: 'unassigned', arrivedMinsAgo: 3, assignedRepId: null,
|
||||
},
|
||||
{
|
||||
id: 'LD-1010', source: 'website_form', leadType: 'retail_estimate', urgency: 'standard',
|
||||
customer: { name: 'Patricia Davis', phone: '(972) 555-1010' },
|
||||
customer: { name: 'Patricia Davis', phone: '(972) 555-1010', email: 'pdavis.home@gmail.com' },
|
||||
property: { address: '6711 W Spring Creek Pkwy', city: 'Plano', state: 'TX', zip: '75024', lat: 33.0888, lng: -96.7756 },
|
||||
webSource: { campaign: 'Facebook — Spring Roofing Promo' },
|
||||
notes: 'At-risk of going cold — no response to last two callbacks. Rep assigned but contact not confirmed. Try alternate number if available.',
|
||||
estimatedDuration: 30, status: 'at_risk', arrivedMinsAgo: 55, assignedRepId: 'REP-05',
|
||||
},
|
||||
{
|
||||
id: 'LD-1011', source: 'canvassing', leadType: 'insurance_claim', urgency: 'high',
|
||||
customer: { name: 'Michael Anderson', phone: '(972) 555-1011' },
|
||||
customer: { name: 'Michael Anderson', phone: '(972) 555-1011', email: 'michael.a@outlook.com' },
|
||||
property: { address: '1800 Jupiter Rd', city: 'Plano', state: 'TX', zip: '75074', lat: 33.0267, lng: -96.7102 },
|
||||
canvassedBy: { agentId: 'U-05', name: 'Marcus Webb' },
|
||||
notes: 'Multiple hail impacts on canvass route. Customer receptive, confirmed hail event on property. Adjuster not yet contacted — walk them through the process.',
|
||||
estimatedDuration: 60, status: 'unassigned', arrivedMinsAgo: 11, assignedRepId: null,
|
||||
},
|
||||
{
|
||||
id: 'LD-1012', source: 'referral', leadType: 'roof_inspection', urgency: 'standard',
|
||||
customer: { name: 'Jennifer White', phone: '(972) 555-1012' },
|
||||
customer: { name: 'Jennifer White', phone: '(972) 555-1012', email: 'jwhite.plano@gmail.com' },
|
||||
property: { address: '4400 W Parker Rd', city: 'Plano', state: 'TX', zip: '75093', lat: 33.0612, lng: -96.8145 },
|
||||
referredBy: { type: 'client', id: 'CL-1178', name: 'Brenda Kowalski' },
|
||||
notes: 'Confirmed appointment. Customer already has a competing estimate — come in strong on materials quality. GAF or Owens Corning preferred. Price-sensitive but quality-motivated.',
|
||||
estimatedDuration: 45, status: 'confirmed', arrivedMinsAgo: 72, assignedRepId: 'REP-01',
|
||||
},
|
||||
];
|
||||
@@ -2848,211 +2872,331 @@ export const DISPATCH_RECOMMENDATIONS = {
|
||||
'LD-1001': [
|
||||
{
|
||||
repId: 'REP-04', score: 91, slotStart: '1:30 PM', slotEnd: '2:30 PM',
|
||||
distanceMi: '2.1',
|
||||
factors: { proximity: 88, driveTime: 90, calendarFit: 95, weather: 90, skillMatch: 100 },
|
||||
reasons: ['Closest eligible rep — 2.1 mi', '12 min drive time estimated', 'Insurance claim certified', 'Buffer preserved before next appointment'],
|
||||
stormReasons: ['Lightning risk before 2 PM — slot pushed to 1:30 PM', 'Rep carries emergency tarp kit'],
|
||||
aiInsight: 'Calendar and proximity align perfectly — earliest safe window before afternoon storm risk',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0516, -96.7512], [33.0612, -96.7484], [33.0708, -96.7456]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-02', score: 79, slotStart: '3:00 PM', slotEnd: '4:00 PM',
|
||||
distanceMi: '3.8',
|
||||
factors: { proximity: 72, driveTime: 75, calendarFit: 82, weather: 80, skillMatch: 85 },
|
||||
reasons: ['Available at 3 PM', '18 min drive — moderate traffic', 'Roof inspection certified'],
|
||||
stormReasons: ['Afternoon slot carries weather risk — monitor closely'],
|
||||
aiInsight: 'Solid backup with roof certification — afternoon slot viable but weather requires monitoring',
|
||||
conflictFlag: 'Post-3 PM weather risk — confirm conditions before dispatch',
|
||||
routePath: [[33.0812, -96.7234], [33.0760, -96.7345], [33.0708, -96.7456]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-01', score: 71, slotStart: '4:30 PM', slotEnd: '5:30 PM',
|
||||
distanceMi: '5.2',
|
||||
factors: { proximity: 62, driveTime: 60, calendarFit: 78, weather: 70, skillMatch: 100 },
|
||||
reasons: ['Available later in the day', '24 min drive from current location', 'High performer — score 94'],
|
||||
stormReasons: ['Late afternoon — weather conditions improving'],
|
||||
aiInsight: 'High performer but distance and late slot reduce overall optimality for this job type',
|
||||
conflictFlag: '20 min buffer only after completion — schedule at risk if job runs long',
|
||||
routePath: [[33.0612, -96.7891], [33.0660, -96.7674], [33.0708, -96.7456]],
|
||||
},
|
||||
],
|
||||
'LD-1002': [
|
||||
{
|
||||
repId: 'REP-01', score: 88, slotStart: '2:00 PM', slotEnd: '2:45 PM',
|
||||
distanceMi: '1.4',
|
||||
factors: { proximity: 92, driveTime: 88, calendarFit: 85, weather: 88, skillMatch: 100 },
|
||||
reasons: ['Closest rep — 1.4 mi from lead location', '8 min drive time', 'Buffer maintained after current job'],
|
||||
stormReasons: ['Weather clear until 3 PM — slot timing is safe', 'Rep familiar with West Plano territory'],
|
||||
aiInsight: 'Nearest eligible rep with strong calendar fit — weather window closes cleanly after completion',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0612, -96.7891], [33.0632, -96.7956], [33.0652, -96.8021]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-04', score: 74, slotStart: '3:30 PM', slotEnd: '4:15 PM',
|
||||
distanceMi: '4.1',
|
||||
factors: { proximity: 70, driveTime: 68, calendarFit: 85, weather: 72, skillMatch: 100 },
|
||||
reasons: ['Available at 3:30 PM', '22 min drive from current position', 'Insurance claim certified'],
|
||||
stormReasons: ['Afternoon slot — weather risk moderate after 3 PM'],
|
||||
aiInsight: 'Certified fallback with open afternoon — drive time and weather reduce confidence vs. top pick',
|
||||
conflictFlag: 'Afternoon weather window tight — start punctually or reschedule',
|
||||
routePath: [[33.0516, -96.7512], [33.0584, -96.7767], [33.0652, -96.8021]],
|
||||
},
|
||||
],
|
||||
'LD-1003': [
|
||||
{
|
||||
repId: 'REP-01', score: 87, slotStart: '11:00 AM', slotEnd: '11:45 AM',
|
||||
distanceMi: '2.8',
|
||||
factors: { proximity: 85, driveTime: 88, calendarFit: 90, weather: 85, skillMatch: 100 },
|
||||
reasons: ['Currently assigned — optimal for continuity', '11 min drive from current stop', 'Insurance claim specialist'],
|
||||
stormReasons: ['Morning slot — no weather concerns'],
|
||||
aiInsight: 'Continuity assignment maximizes close probability — same rep builds on prior customer rapport',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0612, -96.7891], [33.0517, -96.7515], [33.0421, -96.7138]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-05', score: 67, slotStart: '1:00 PM', slotEnd: '1:45 PM',
|
||||
distanceMi: '4.4',
|
||||
factors: { proximity: 65, driveTime: 62, calendarFit: 72, weather: 75, skillMatch: 60 },
|
||||
reasons: ['Available afternoon slot', '19 min drive', 'Less specialized — retail estimate primary skill'],
|
||||
stormReasons: ['Afternoon — monitor weather after 1 PM'],
|
||||
aiInsight: 'Capable fallback but skill mismatch lowers confidence for insurance claim type',
|
||||
conflictFlag: 'Skill mismatch — retail primary, insurance secondary skill only',
|
||||
routePath: [[33.0198, -96.7234], [33.0310, -96.7186], [33.0421, -96.7138]],
|
||||
},
|
||||
],
|
||||
'LD-1004': [
|
||||
{
|
||||
repId: 'REP-03', score: 95, slotStart: '10:30 AM', slotEnd: '11:00 AM',
|
||||
distanceMi: '0.6',
|
||||
factors: { proximity: 98, driveTime: 97, calendarFit: 88, weather: 95, skillMatch: 100 },
|
||||
reasons: ['0.6 mi away — optimal proximity', '4 min drive time', 'Retail estimate specialist'],
|
||||
stormReasons: ['Morning slot — conditions favorable'],
|
||||
aiInsight: 'Near-perfect assignment — virtually on-site with ideal skill set and favorable morning conditions',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0389, -96.7401], [33.0389, -96.7368], [33.0389, -96.7334]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-05', score: 73, slotStart: '2:00 PM', slotEnd: '2:30 PM',
|
||||
distanceMi: '3.3',
|
||||
factors: { proximity: 70, driveTime: 68, calendarFit: 80, weather: 72, skillMatch: 80 },
|
||||
reasons: ['Available afternoon', '16 min drive', 'Retail estimate capable'],
|
||||
stormReasons: ['Afternoon slot — watch post-2 PM weather'],
|
||||
aiInsight: 'Retail-capable with open afternoon — viable if top pick becomes unavailable',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0198, -96.7234], [33.0294, -96.7284], [33.0389, -96.7334]],
|
||||
},
|
||||
],
|
||||
'LD-1005': [
|
||||
{
|
||||
repId: 'REP-02', score: 94, slotStart: '12:00 PM', slotEnd: '12:45 PM',
|
||||
distanceMi: '0.3',
|
||||
factors: { proximity: 97, driveTime: 96, calendarFit: 90, weather: 92, skillMatch: 95 },
|
||||
reasons: ['0.3 mi from lead — walking distance', '3 min drive time', 'Roof inspection specialist', 'Low daily load — 2 of 6 slots used'],
|
||||
reasons: ['0.3 mi from lead — effectively on-site', '3 min drive time', 'Roof inspection specialist', 'Low daily load — 2 of 6 slots used'],
|
||||
stormReasons: ['Midday window — weather acceptable', 'Rep carries safety equipment'],
|
||||
aiInsight: 'Exceptional proximity with light workload — highest-confidence dispatch of the day',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0812, -96.7234], [33.0812, -96.7378], [33.0812, -96.7521]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-04', score: 77, slotStart: '2:30 PM', slotEnd: '3:15 PM',
|
||||
distanceMi: '3.6',
|
||||
factors: { proximity: 72, driveTime: 70, calendarFit: 88, weather: 78, skillMatch: 90 },
|
||||
reasons: ['Available at 2:30 PM', '17 min drive', 'Roof inspection certified'],
|
||||
stormReasons: ['Afternoon — post-3 PM weather worsening, start punctually'],
|
||||
aiInsight: 'Certified backup with clean afternoon window — must start on time before weather closes in',
|
||||
conflictFlag: 'Start before 3 PM — weather deteriorates sharply after',
|
||||
routePath: [[33.0516, -96.7512], [33.0664, -96.7517], [33.0812, -96.7521]],
|
||||
},
|
||||
],
|
||||
'LD-1006': [
|
||||
{
|
||||
repId: 'REP-01', score: 83, slotStart: '3:00 PM', slotEnd: '4:00 PM',
|
||||
distanceMi: '3.1',
|
||||
factors: { proximity: 80, driveTime: 78, calendarFit: 85, weather: 82, skillMatch: 100 },
|
||||
reasons: ['Best available slot — 3 PM', '14 min drive from current route', 'Insurance claim specialist'],
|
||||
stormReasons: ['Wind advisory active — exterior inspection may need delay', 'Insurance claim can proceed indoors'],
|
||||
aiInsight: 'Best certified specialist available — indoor claim process can proceed despite active wind advisory',
|
||||
conflictFlag: 'Wind advisory active — verify exterior conditions before starting roof portion',
|
||||
routePath: [[33.0612, -96.7891], [33.0773, -96.7947], [33.0934, -96.8002]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-02', score: 69, slotStart: '4:30 PM', slotEnd: '5:30 PM',
|
||||
distanceMi: '5.8',
|
||||
factors: { proximity: 65, driveTime: 62, calendarFit: 75, weather: 68, skillMatch: 85 },
|
||||
reasons: ['Late afternoon availability', '26 min drive', 'Roof inspection skilled'],
|
||||
stormReasons: ['Late afternoon — conditions deteriorating, not recommended'],
|
||||
aiInsight: 'Low-confidence option — weather and distance combine unfavorably for late afternoon exterior work',
|
||||
conflictFlag: 'Not recommended — conditions deteriorating by 4:30 PM, daylight limited',
|
||||
routePath: [[33.0812, -96.7234], [33.0873, -96.7618], [33.0934, -96.8002]],
|
||||
},
|
||||
],
|
||||
'LD-1007': [
|
||||
{
|
||||
repId: 'REP-04', score: 87, slotStart: '11:30 AM', slotEnd: '12:00 PM',
|
||||
distanceMi: '1.5',
|
||||
factors: { proximity: 90, driveTime: 88, calendarFit: 92, weather: 90, skillMatch: 80 },
|
||||
reasons: ['1.5 mi away — close proximity', '9 min drive time', 'Low daily load', 'Buffer before afternoon appointments'],
|
||||
stormReasons: ['Morning window — safe conditions'],
|
||||
aiInsight: 'Light daily load with close proximity — midday window maximizes buffer for afternoon pipeline',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0516, -96.7512], [33.0516, -96.7401], [33.0516, -96.7289]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-03', score: 78, slotStart: '1:30 PM', slotEnd: '2:00 PM',
|
||||
distanceMi: '2.9',
|
||||
factors: { proximity: 78, driveTime: 75, calendarFit: 70, weather: 82, skillMatch: 100 },
|
||||
reasons: ['Retail estimate specialist', '12 min drive', 'Near current work site'],
|
||||
stormReasons: ['Early afternoon — conditions acceptable'],
|
||||
aiInsight: 'Specialist-level skill match but heavier daily load reduces calendar fit score',
|
||||
conflictFlag: '5 appointments today — buffer is tight if any job runs over',
|
||||
routePath: [[33.0389, -96.7401], [33.0453, -96.7345], [33.0516, -96.7289]],
|
||||
},
|
||||
],
|
||||
'LD-1008': [
|
||||
{
|
||||
repId: 'REP-04', score: 86, slotStart: '2:00 PM', slotEnd: '2:45 PM',
|
||||
distanceMi: '2.7',
|
||||
factors: { proximity: 84, driveTime: 85, calendarFit: 90, weather: 85, skillMatch: 95 },
|
||||
reasons: ['2.7 mi from lead — manageable drive', '15 min estimated travel', 'Roof inspection certified', 'Open afternoon slot'],
|
||||
stormReasons: ['Weather acceptable until 3:30 PM', 'Book for 2 PM to maximize safe window'],
|
||||
aiInsight: 'Certified for roof inspection with open slot — 2 PM booking maximizes safe weather window',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0516, -96.7512], [33.0630, -96.7562], [33.0744, -96.7612]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-02', score: 75, slotStart: '3:30 PM', slotEnd: '4:15 PM',
|
||||
distanceMi: '4.1',
|
||||
factors: { proximity: 72, driveTime: 70, calendarFit: 82, weather: 74, skillMatch: 90 },
|
||||
reasons: ['Available at 3:30 PM', '19 min drive', 'Roof inspection specialist'],
|
||||
stormReasons: ['Late afternoon — weather risk elevated after 3 PM'],
|
||||
aiInsight: 'Roof specialist with afternoon availability — weather risk increases as slot approaches 4 PM',
|
||||
conflictFlag: '15 min buffer after completion — schedule may be tight if delayed',
|
||||
routePath: [[33.0812, -96.7234], [33.0778, -96.7423], [33.0744, -96.7612]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-01', score: 68, slotStart: '5:00 PM', slotEnd: '5:45 PM',
|
||||
distanceMi: '5.5',
|
||||
factors: { proximity: 62, driveTime: 58, calendarFit: 70, weather: 68, skillMatch: 80 },
|
||||
reasons: ['End of day slot only', '22 min drive', 'High performer — score 94'],
|
||||
stormReasons: ['Evening — conditions improving but daylight limited'],
|
||||
aiInsight: 'High performer but 5 PM slot limits daylight for roof work — use only as last resort',
|
||||
conflictFlag: 'Daylight limited at 5 PM — roof inspection may be incomplete',
|
||||
routePath: [[33.0612, -96.7891], [33.0678, -96.7752], [33.0744, -96.7612]],
|
||||
},
|
||||
],
|
||||
'LD-1009': [
|
||||
{
|
||||
repId: 'REP-05', score: 97, slotStart: '11:00 AM', slotEnd: '12:30 PM',
|
||||
distanceMi: '0.8',
|
||||
factors: { proximity: 99, driveTime: 98, calendarFit: 95, weather: 95, skillMatch: 90 },
|
||||
reasons: ['0.8 mi away — nearest rep on site', '5 min drive time', 'Emergency certified', 'PRIORITY: emergency tarp needed'],
|
||||
stormReasons: ['EMERGENCY — dispatch immediately', 'Rep stocked with emergency tarp materials'],
|
||||
aiInsight: 'PRIORITY dispatch — nearest certified rep with emergency materials on hand, 5 min ETA',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0198, -96.7234], [33.0198, -96.7112], [33.0198, -96.6989]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-03', score: 71, slotStart: '1:00 PM', slotEnd: '2:30 PM',
|
||||
distanceMi: '4.6',
|
||||
factors: { proximity: 65, driveTime: 62, calendarFit: 75, weather: 72, skillMatch: 75 },
|
||||
reasons: ['Backup option — 5 appointments today', '21 min drive', 'Roof inspection capable'],
|
||||
stormReasons: ['Secondary option — use only if REP-05 unavailable'],
|
||||
aiInsight: 'Emergency backup only — heavier daily load and longer drive make this a secondary option',
|
||||
conflictFlag: '5 appointments today — minimal buffer if any prior job overruns',
|
||||
routePath: [[33.0389, -96.7401], [33.0294, -96.7195], [33.0198, -96.6989]],
|
||||
},
|
||||
],
|
||||
'LD-1010': [
|
||||
{
|
||||
repId: 'REP-01', score: 84, slotStart: '2:30 PM', slotEnd: '3:00 PM',
|
||||
distanceMi: '2.9',
|
||||
factors: { proximity: 82, driveTime: 80, calendarFit: 88, weather: 84, skillMatch: 80 },
|
||||
reasons: ['Best proximity match — 2.9 mi', '16 min drive', 'High performer — score 94', 'Manageable daily load'],
|
||||
stormReasons: ['Afternoon slot — monitor weather', 'Retail estimate can proceed partially indoors'],
|
||||
aiInsight: 'Top performer with manageable load — retail estimate can proceed indoors if afternoon weather shifts',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0612, -96.7891], [33.0750, -96.7824], [33.0888, -96.7756]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-02', score: 72, slotStart: '4:00 PM', slotEnd: '4:30 PM',
|
||||
distanceMi: '4.8',
|
||||
factors: { proximity: 70, driveTime: 68, calendarFit: 78, weather: 70, skillMatch: 80 },
|
||||
reasons: ['Available late afternoon', '22 min drive', 'Roof inspection certified'],
|
||||
stormReasons: ['4 PM slot — weather conditions deteriorating, not ideal'],
|
||||
aiInsight: 'Viable backup but 4 PM timing and weather trend reduce confidence for exterior estimate',
|
||||
conflictFlag: '4 PM slot — low daylight and deteriorating conditions for exterior work',
|
||||
routePath: [[33.0812, -96.7234], [33.0850, -96.7495], [33.0888, -96.7756]],
|
||||
},
|
||||
],
|
||||
'LD-1011': [
|
||||
{
|
||||
repId: 'REP-05', score: 89, slotStart: '12:30 PM', slotEnd: '1:30 PM',
|
||||
distanceMi: '1.6',
|
||||
factors: { proximity: 90, driveTime: 88, calendarFit: 85, weather: 88, skillMatch: 90 },
|
||||
reasons: ['Closest rep — 1.6 mi', '9 min drive time', 'Insurance claim proficient', 'Midday slot timing is optimal'],
|
||||
stormReasons: ['Midday window — acceptable conditions', 'Insurance claim can be initiated outdoors then moved inside'],
|
||||
aiInsight: 'Close proximity with optimal midday timing — insurance initiation can flex indoors if needed',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0198, -96.7234], [33.0233, -96.7168], [33.0267, -96.7102]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-03', score: 76, slotStart: '2:00 PM', slotEnd: '3:00 PM',
|
||||
distanceMi: '3.5',
|
||||
factors: { proximity: 74, driveTime: 70, calendarFit: 78, weather: 76, skillMatch: 80 },
|
||||
reasons: ['Available at 2 PM', '18 min drive', 'Retail estimate skilled'],
|
||||
stormReasons: ['Afternoon slot — conditions marginal after 2:30 PM'],
|
||||
aiInsight: 'Partial skill match with afternoon availability — conditions deteriorate mid-appointment window',
|
||||
conflictFlag: 'Skill alignment moderate — retail primary, insurance secondary only',
|
||||
routePath: [[33.0389, -96.7401], [33.0328, -96.7252], [33.0267, -96.7102]],
|
||||
},
|
||||
],
|
||||
'LD-1012': [
|
||||
{
|
||||
repId: 'REP-01', score: 92, slotStart: '10:00 AM', slotEnd: '10:45 AM',
|
||||
distanceMi: '1.7',
|
||||
factors: { proximity: 94, driveTime: 92, calendarFit: 88, weather: 92, skillMatch: 95 },
|
||||
reasons: ['Currently assigned — optimal', '1.7 mi from current position', 'Morning slot — ideal weather window', 'Insurance + retail estimate certified'],
|
||||
stormReasons: ['Morning slot — safe window before any afternoon weather'],
|
||||
aiInsight: 'Morning assignment with ideal conditions — dual-certified rep maximizes flexibility for this job type',
|
||||
conflictFlag: null,
|
||||
routePath: [[33.0612, -96.7891], [33.0612, -96.8018], [33.0612, -96.8145]],
|
||||
},
|
||||
{
|
||||
repId: 'REP-04', score: 70, slotStart: '1:00 PM', slotEnd: '1:45 PM',
|
||||
distanceMi: '5.3',
|
||||
factors: { proximity: 65, driveTime: 62, calendarFit: 82, weather: 70, skillMatch: 85 },
|
||||
reasons: ['Afternoon availability', '24 min drive from current location', 'Roof inspection certified'],
|
||||
stormReasons: ['Afternoon — conditions acceptable but watch post-2 PM'],
|
||||
aiInsight: 'Longer drive reduces score significantly — afternoon window is viable if morning rep unavailable',
|
||||
conflictFlag: '24 min drive cuts into slot buffer — delays cascade to next appointment',
|
||||
routePath: [[33.0516, -96.7512], [33.0564, -96.7829], [33.0612, -96.8145]],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Weekly performance data — Mon Mar 20 → Sun Mar 26 (current week)
|
||||
// repBreakdown keys match DISPATCH_REPS ids
|
||||
// ---------------------------------------------------------------------------
|
||||
export const DISPATCH_WEEKLY_STATS = [
|
||||
{
|
||||
day: 'Mon', date: 'Mar 20', dispatched: 7, avgWait: 18, emergency: 0,
|
||||
stormDay: false, isToday: false,
|
||||
repBreakdown: { 'REP-01': 2, 'REP-02': 2, 'REP-03': 1, 'REP-04': 1, 'REP-05': 1 },
|
||||
},
|
||||
{
|
||||
day: 'Tue', date: 'Mar 21', dispatched: 9, avgWait: 14, emergency: 1,
|
||||
stormDay: false, isToday: false,
|
||||
repBreakdown: { 'REP-01': 2, 'REP-02': 2, 'REP-03': 2, 'REP-04': 2, 'REP-05': 1 },
|
||||
},
|
||||
{
|
||||
day: 'Wed', date: 'Mar 22', dispatched: 6, avgWait: 22, emergency: 0,
|
||||
stormDay: false, isToday: false,
|
||||
repBreakdown: { 'REP-01': 1, 'REP-02': 2, 'REP-03': 1, 'REP-04': 1, 'REP-05': 1 },
|
||||
},
|
||||
{
|
||||
day: 'Thu', date: 'Mar 23', dispatched: 14, avgWait: 9, emergency: 4,
|
||||
stormDay: true, isToday: false,
|
||||
repBreakdown: { 'REP-01': 3, 'REP-02': 3, 'REP-03': 3, 'REP-04': 3, 'REP-05': 2 },
|
||||
},
|
||||
{
|
||||
day: 'Fri', date: 'Mar 24', dispatched: 10, avgWait: 13, emergency: 2,
|
||||
stormDay: false, isToday: false,
|
||||
repBreakdown: { 'REP-01': 2, 'REP-02': 2, 'REP-03': 2, 'REP-04': 2, 'REP-05': 2 },
|
||||
},
|
||||
{
|
||||
day: 'Sat', date: 'Mar 25', dispatched: 5, avgWait: 20, emergency: 0,
|
||||
stormDay: false, isToday: false,
|
||||
repBreakdown: { 'REP-01': 1, 'REP-02': 1, 'REP-03': 1, 'REP-04': 1, 'REP-05': 1 },
|
||||
},
|
||||
{
|
||||
day: 'Sun', date: 'Mar 26', dispatched: 3, avgWait: 24, emergency: 0,
|
||||
stormDay: false, isToday: true,
|
||||
repBreakdown: { 'REP-01': 1, 'REP-02': 1, 'REP-03': 0, 'REP-04': 1, 'REP-05': 0 },
|
||||
},
|
||||
];
|
||||
|
||||
// --- CONTEXT SETUP ---
|
||||
|
||||
const MockStoreContext = createContext();
|
||||
@@ -3180,6 +3324,11 @@ export const MockStoreProvider = ({ children }) => {
|
||||
l.id === leadId ? { ...l, assignedRepId: repId, status: 'confirmed' } : l
|
||||
));
|
||||
},
|
||||
updateLeadNotes: (leadId, notes) => {
|
||||
setDispatchLeads(prev => prev.map(l =>
|
||||
l.id === leadId ? { ...l, notes } : l
|
||||
));
|
||||
},
|
||||
|
||||
updatePropertyStatus,
|
||||
addMeeting,
|
||||
|
||||
Reference in New Issue
Block a user