mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: add Call, LeadIngestionSource types and mock data, AI fields on leads
- Add CallDirection, CallStatus, CallDisposition, Call types to entities.ts - Add IntegrationStatus, AuthStatus, IngestionPlatform, LeadIngestionSource types - Add aiSummary and aiSuggestedAction fields to Lead type - Create src/mocks/calls.ts with 15 INBOUND COMPLETED calls across 3 agents - Create src/mocks/ingestion-sources.ts with 6 integration sources (ACTIVE/WARNING/ERROR) - Update mockLeads with AI summaries on ~20 leads (new, contacted, spam cohorts) - Expose calls, ingestionSources, and addCall() in DataContext Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
372
src/mocks/calls.ts
Normal file
372
src/mocks/calls.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
import type { Call } from '@/types/entities';
|
||||
import { daysAgo } from './factories';
|
||||
import { mockLeads } from './leads';
|
||||
|
||||
// Helper: pick a lead by stable index (module-level, after mockLeads is defined)
|
||||
const getLeadAt = (index: number) => mockLeads[index % mockLeads.length];
|
||||
|
||||
// Helper: build an ISO end time given a start time ISO string and duration in seconds
|
||||
function addSeconds(isoStart: string, seconds: number): string {
|
||||
const d = new Date(isoStart);
|
||||
d.setSeconds(d.getSeconds() + seconds);
|
||||
return d.toISOString();
|
||||
}
|
||||
|
||||
// Spread leads across call records for denormalized display data
|
||||
const lead0 = getLeadAt(0);
|
||||
const lead1 = getLeadAt(1);
|
||||
const lead2 = getLeadAt(2);
|
||||
const lead3 = getLeadAt(3);
|
||||
const lead4 = getLeadAt(4);
|
||||
const lead5 = getLeadAt(5);
|
||||
const lead6 = getLeadAt(6);
|
||||
const lead7 = getLeadAt(7);
|
||||
const lead8 = getLeadAt(8);
|
||||
const lead9 = getLeadAt(9);
|
||||
const lead10 = getLeadAt(10);
|
||||
const lead11 = getLeadAt(11);
|
||||
const lead12 = getLeadAt(12);
|
||||
const lead13 = getLeadAt(13);
|
||||
const lead14 = getLeadAt(14);
|
||||
|
||||
function leadDisplayName(lead: (typeof mockLeads)[number]): string {
|
||||
const fn = lead.contactName?.firstName ?? '';
|
||||
const ln = lead.contactName?.lastName ?? '';
|
||||
return `${fn} ${ln}`.trim() || 'Unknown';
|
||||
}
|
||||
|
||||
function leadDisplayPhone(lead: (typeof mockLeads)[number]): string {
|
||||
return lead.contactPhone?.[0]?.number ?? '';
|
||||
}
|
||||
|
||||
// Today and yesterday timestamps (hours offset)
|
||||
function hoursAgo(h: number): string {
|
||||
const d = new Date();
|
||||
d.setHours(d.getHours() - h);
|
||||
return d.toISOString();
|
||||
}
|
||||
|
||||
const c1Start = hoursAgo(0.5);
|
||||
const c2Start = hoursAgo(1.2);
|
||||
const c3Start = hoursAgo(2.1);
|
||||
const c4Start = hoursAgo(3.0);
|
||||
const c5Start = hoursAgo(4.5);
|
||||
const c6Start = hoursAgo(5.3);
|
||||
const c7Start = hoursAgo(6.0);
|
||||
const c8Start = hoursAgo(7.2);
|
||||
const c9Start = hoursAgo(8.8);
|
||||
const c10Start = hoursAgo(10.0);
|
||||
// Yesterday calls
|
||||
const c11Start = daysAgo(1.05);
|
||||
const c12Start = daysAgo(1.1);
|
||||
const c13Start = daysAgo(1.2);
|
||||
const c14Start = daysAgo(1.35);
|
||||
const c15Start = daysAgo(1.5);
|
||||
|
||||
export const mockCalls: Call[] = [
|
||||
// --- Rekha S. calls (8 total) ---
|
||||
{
|
||||
id: 'call-1',
|
||||
createdAt: c1Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead0.contactPhone,
|
||||
agentName: 'Rekha S.',
|
||||
startedAt: c1Start,
|
||||
endedAt: addSeconds(c1Start, 320),
|
||||
durationSeconds: 320,
|
||||
recordingUrl: null,
|
||||
disposition: 'APPOINTMENT_BOOKED',
|
||||
callNotes: 'Patient confirmed appointment for IVF consultation on Friday.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead0.id,
|
||||
leadName: leadDisplayName(lead0),
|
||||
leadPhone: leadDisplayPhone(lead0),
|
||||
leadService: lead0.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-2',
|
||||
createdAt: c2Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead1.contactPhone,
|
||||
agentName: 'Rekha S.',
|
||||
startedAt: c2Start,
|
||||
endedAt: addSeconds(c2Start, 180),
|
||||
durationSeconds: 180,
|
||||
recordingUrl: null,
|
||||
disposition: 'INFO_PROVIDED',
|
||||
callNotes: 'Caller asked about cervical screening package pricing. Sent WhatsApp brochure.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead1.id,
|
||||
leadName: leadDisplayName(lead1),
|
||||
leadPhone: leadDisplayPhone(lead1),
|
||||
leadService: lead1.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-3',
|
||||
createdAt: c3Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead2.contactPhone,
|
||||
agentName: 'Rekha S.',
|
||||
startedAt: c3Start,
|
||||
endedAt: addSeconds(c3Start, 540),
|
||||
durationSeconds: 540,
|
||||
recordingUrl: null,
|
||||
disposition: 'FOLLOW_UP_SCHEDULED',
|
||||
callNotes: 'Lead expressed interest but needs to discuss with spouse. Follow-up scheduled for tomorrow.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead2.id,
|
||||
leadName: leadDisplayName(lead2),
|
||||
leadPhone: leadDisplayPhone(lead2),
|
||||
leadService: lead2.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-4',
|
||||
createdAt: c4Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead3.contactPhone,
|
||||
agentName: 'Rekha S.',
|
||||
startedAt: c4Start,
|
||||
endedAt: addSeconds(c4Start, 90),
|
||||
durationSeconds: 90,
|
||||
recordingUrl: null,
|
||||
disposition: 'NO_ANSWER',
|
||||
callNotes: 'Caller disconnected before agent could respond.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead3.id,
|
||||
leadName: leadDisplayName(lead3),
|
||||
leadPhone: leadDisplayPhone(lead3),
|
||||
leadService: lead3.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-5',
|
||||
createdAt: c7Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead4.contactPhone,
|
||||
agentName: 'Rekha S.',
|
||||
startedAt: c7Start,
|
||||
endedAt: addSeconds(c7Start, 420),
|
||||
durationSeconds: 420,
|
||||
recordingUrl: null,
|
||||
disposition: 'APPOINTMENT_BOOKED',
|
||||
callNotes: 'Booked fertility assessment appointment for next Monday at 10 AM.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead4.id,
|
||||
leadName: leadDisplayName(lead4),
|
||||
leadPhone: leadDisplayPhone(lead4),
|
||||
leadService: lead4.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-6',
|
||||
createdAt: c8Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead5.contactPhone,
|
||||
agentName: 'Rekha S.',
|
||||
startedAt: c8Start,
|
||||
endedAt: addSeconds(c8Start, 600),
|
||||
durationSeconds: 600,
|
||||
recordingUrl: null,
|
||||
disposition: 'APPOINTMENT_BOOKED',
|
||||
callNotes: 'Long call — patient had many questions about IVF process. Appointment booked.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead5.id,
|
||||
leadName: leadDisplayName(lead5),
|
||||
leadPhone: leadDisplayPhone(lead5),
|
||||
leadService: lead5.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-7',
|
||||
createdAt: c11Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead6.contactPhone,
|
||||
agentName: 'Rekha S.',
|
||||
startedAt: c11Start,
|
||||
endedAt: addSeconds(c11Start, 250),
|
||||
durationSeconds: 250,
|
||||
recordingUrl: null,
|
||||
disposition: 'CALLBACK_REQUESTED',
|
||||
callNotes: 'Caller requested callback after 6 PM — busy at work.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead6.id,
|
||||
leadName: leadDisplayName(lead6),
|
||||
leadPhone: leadDisplayPhone(lead6),
|
||||
leadService: lead6.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-8',
|
||||
createdAt: c12Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead7.contactPhone,
|
||||
agentName: 'Rekha S.',
|
||||
startedAt: c12Start,
|
||||
endedAt: addSeconds(c12Start, 390),
|
||||
durationSeconds: 390,
|
||||
recordingUrl: null,
|
||||
disposition: 'FOLLOW_UP_SCHEDULED',
|
||||
callNotes: 'Interested in mammography screening. Wants to check insurance coverage first.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead7.id,
|
||||
leadName: leadDisplayName(lead7),
|
||||
leadPhone: leadDisplayPhone(lead7),
|
||||
leadService: lead7.interestedService ?? undefined,
|
||||
},
|
||||
|
||||
// --- Suresh P. calls (5 total) ---
|
||||
{
|
||||
id: 'call-9',
|
||||
createdAt: c5Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead8.contactPhone,
|
||||
agentName: 'Suresh P.',
|
||||
startedAt: c5Start,
|
||||
endedAt: addSeconds(c5Start, 480),
|
||||
durationSeconds: 480,
|
||||
recordingUrl: null,
|
||||
disposition: 'APPOINTMENT_BOOKED',
|
||||
callNotes: 'Diabetic patient. Booked endocrinology consultation for Wednesday.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead8.id,
|
||||
leadName: leadDisplayName(lead8),
|
||||
leadPhone: leadDisplayPhone(lead8),
|
||||
leadService: lead8.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-10',
|
||||
createdAt: c6Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead9.contactPhone,
|
||||
agentName: 'Suresh P.',
|
||||
startedAt: c6Start,
|
||||
endedAt: addSeconds(c6Start, 120),
|
||||
durationSeconds: 120,
|
||||
recordingUrl: null,
|
||||
disposition: 'WRONG_NUMBER',
|
||||
callNotes: 'Caller was trying to reach a different clinic. Politely redirected.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead9.id,
|
||||
leadName: leadDisplayName(lead9),
|
||||
leadPhone: leadDisplayPhone(lead9),
|
||||
leadService: lead9.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-11',
|
||||
createdAt: c9Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead10.contactPhone,
|
||||
agentName: 'Suresh P.',
|
||||
startedAt: c9Start,
|
||||
endedAt: addSeconds(c9Start, 330),
|
||||
durationSeconds: 330,
|
||||
recordingUrl: null,
|
||||
disposition: 'INFO_PROVIDED',
|
||||
callNotes: 'Answered questions about orthopaedic consultation availability and waiting times.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead10.id,
|
||||
leadName: leadDisplayName(lead10),
|
||||
leadPhone: leadDisplayPhone(lead10),
|
||||
leadService: lead10.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-12',
|
||||
createdAt: c13Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead11.contactPhone,
|
||||
agentName: 'Suresh P.',
|
||||
startedAt: c13Start,
|
||||
endedAt: addSeconds(c13Start, 560),
|
||||
durationSeconds: 560,
|
||||
recordingUrl: null,
|
||||
disposition: 'FOLLOW_UP_SCHEDULED',
|
||||
callNotes: 'Interested in senior health package for elderly parent. Needs to confirm family schedule.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead11.id,
|
||||
leadName: leadDisplayName(lead11),
|
||||
leadPhone: leadDisplayPhone(lead11),
|
||||
leadService: lead11.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-13',
|
||||
createdAt: c14Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead12.contactPhone,
|
||||
agentName: 'Suresh P.',
|
||||
startedAt: c14Start,
|
||||
endedAt: addSeconds(c14Start, 200),
|
||||
durationSeconds: 200,
|
||||
recordingUrl: null,
|
||||
disposition: 'APPOINTMENT_BOOKED',
|
||||
callNotes: 'Quick call — patient knew exactly what they wanted. Cardiology checkup booked.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead12.id,
|
||||
leadName: leadDisplayName(lead12),
|
||||
leadPhone: leadDisplayPhone(lead12),
|
||||
leadService: lead12.interestedService ?? undefined,
|
||||
},
|
||||
|
||||
// --- Meena K. calls (2 total) ---
|
||||
{
|
||||
id: 'call-14',
|
||||
createdAt: c10Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead13.contactPhone,
|
||||
agentName: 'Meena K.',
|
||||
startedAt: c10Start,
|
||||
endedAt: addSeconds(c10Start, 440),
|
||||
durationSeconds: 440,
|
||||
recordingUrl: null,
|
||||
disposition: 'INFO_PROVIDED',
|
||||
callNotes: 'Patient inquired about egg freezing costs and eligibility criteria. Sent detailed email.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead13.id,
|
||||
leadName: leadDisplayName(lead13),
|
||||
leadPhone: leadDisplayPhone(lead13),
|
||||
leadService: lead13.interestedService ?? undefined,
|
||||
},
|
||||
{
|
||||
id: 'call-15',
|
||||
createdAt: c15Start,
|
||||
callDirection: 'INBOUND',
|
||||
callStatus: 'COMPLETED',
|
||||
callerNumber: lead14.contactPhone,
|
||||
agentName: 'Meena K.',
|
||||
startedAt: c15Start,
|
||||
endedAt: addSeconds(c15Start, 60),
|
||||
durationSeconds: 60,
|
||||
recordingUrl: null,
|
||||
disposition: 'NO_ANSWER',
|
||||
callNotes: 'Caller dropped after 60 seconds — possible network issue. Sent WhatsApp message.',
|
||||
patientId: null,
|
||||
appointmentId: null,
|
||||
leadId: lead14.id,
|
||||
leadName: leadDisplayName(lead14),
|
||||
leadPhone: leadDisplayPhone(lead14),
|
||||
leadService: lead14.interestedService ?? undefined,
|
||||
},
|
||||
];
|
||||
@@ -94,6 +94,8 @@ export function createMockLead(overrides?: Partial<Lead>): Lead {
|
||||
lastContactedAt: null,
|
||||
contactAttempts: 0,
|
||||
convertedAt: null,
|
||||
aiSummary: null,
|
||||
aiSuggestedAction: null,
|
||||
patientId: null,
|
||||
campaignId: null,
|
||||
adId: null,
|
||||
|
||||
@@ -6,3 +6,5 @@ export * from './lead-activities';
|
||||
export * from './follow-ups';
|
||||
export * from './templates';
|
||||
export * from './agents';
|
||||
export { mockCalls } from './calls';
|
||||
export { mockIngestionSources } from './ingestion-sources';
|
||||
|
||||
101
src/mocks/ingestion-sources.ts
Normal file
101
src/mocks/ingestion-sources.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { LeadIngestionSource } from '@/types/entities';
|
||||
import { daysAgo } from './factories';
|
||||
|
||||
function minutesAgo(m: number): string {
|
||||
const d = new Date();
|
||||
d.setMinutes(d.getMinutes() - m);
|
||||
return d.toISOString();
|
||||
}
|
||||
|
||||
function hoursAgo(h: number): string {
|
||||
const d = new Date();
|
||||
d.setHours(d.getHours() - h);
|
||||
return d.toISOString();
|
||||
}
|
||||
|
||||
export const mockIngestionSources: LeadIngestionSource[] = [
|
||||
{
|
||||
id: 'source-1',
|
||||
createdAt: daysAgo(90),
|
||||
sourceName: 'Facebook Lead Ads',
|
||||
platform: 'FACEBOOK',
|
||||
integrationStatus: 'ACTIVE',
|
||||
lastSuccessfulSyncAt: minutesAgo(10),
|
||||
lastFailureAt: null,
|
||||
leadsReceivedLast24h: 24,
|
||||
consecutiveFailures: 0,
|
||||
authStatus: 'VALID',
|
||||
webhookUrl: 'https://api.helixengage.in/webhooks/facebook-leads',
|
||||
lastErrorMessage: null,
|
||||
},
|
||||
{
|
||||
id: 'source-2',
|
||||
createdAt: daysAgo(75),
|
||||
sourceName: 'Instagram Lead Ads',
|
||||
platform: 'INSTAGRAM',
|
||||
integrationStatus: 'ACTIVE',
|
||||
lastSuccessfulSyncAt: minutesAgo(15),
|
||||
lastFailureAt: null,
|
||||
leadsReceivedLast24h: 9,
|
||||
consecutiveFailures: 0,
|
||||
authStatus: 'VALID',
|
||||
webhookUrl: 'https://api.helixengage.in/webhooks/instagram-leads',
|
||||
lastErrorMessage: null,
|
||||
},
|
||||
{
|
||||
id: 'source-3',
|
||||
createdAt: daysAgo(60),
|
||||
sourceName: 'Google Search Ads',
|
||||
platform: 'GOOGLE_ADS',
|
||||
integrationStatus: 'WARNING',
|
||||
lastSuccessfulSyncAt: hoursAgo(2),
|
||||
lastFailureAt: hoursAgo(1.5),
|
||||
leadsReceivedLast24h: 18,
|
||||
consecutiveFailures: 3,
|
||||
authStatus: 'VALID',
|
||||
webhookUrl: 'https://api.helixengage.in/webhooks/google-ads',
|
||||
lastErrorMessage: 'API rate limit exceeded — retry scheduled in 45 minutes',
|
||||
},
|
||||
{
|
||||
id: 'source-4',
|
||||
createdAt: daysAgo(45),
|
||||
sourceName: 'Google My Business',
|
||||
platform: 'GOOGLE_MY_BUSINESS',
|
||||
integrationStatus: 'ACTIVE',
|
||||
lastSuccessfulSyncAt: minutesAgo(30),
|
||||
lastFailureAt: null,
|
||||
leadsReceivedLast24h: 3,
|
||||
consecutiveFailures: 0,
|
||||
authStatus: 'EXPIRING_SOON',
|
||||
webhookUrl: null,
|
||||
lastErrorMessage: null,
|
||||
},
|
||||
{
|
||||
id: 'source-5',
|
||||
createdAt: daysAgo(120),
|
||||
sourceName: 'Website Forms',
|
||||
platform: 'WEBSITE',
|
||||
integrationStatus: 'ACTIVE',
|
||||
lastSuccessfulSyncAt: minutesAgo(5),
|
||||
lastFailureAt: null,
|
||||
leadsReceivedLast24h: 5,
|
||||
consecutiveFailures: 0,
|
||||
authStatus: 'VALID',
|
||||
webhookUrl: 'https://api.helixengage.in/webhooks/website-forms',
|
||||
lastErrorMessage: null,
|
||||
},
|
||||
{
|
||||
id: 'source-6',
|
||||
createdAt: daysAgo(30),
|
||||
sourceName: 'WhatsApp Business API',
|
||||
platform: 'WHATSAPP',
|
||||
integrationStatus: 'ERROR',
|
||||
lastSuccessfulSyncAt: hoursAgo(18),
|
||||
lastFailureAt: hoursAgo(0.5),
|
||||
leadsReceivedLast24h: 0,
|
||||
consecutiveFailures: 8,
|
||||
authStatus: 'EXPIRED',
|
||||
webhookUrl: 'https://api.helixengage.in/webhooks/whatsapp',
|
||||
lastErrorMessage: 'Authentication token expired — re-authorization required in Meta Business Manager',
|
||||
},
|
||||
];
|
||||
@@ -7,9 +7,73 @@ const campaignPool = [CAMPAIGN_ID_WD, CAMPAIGN_ID_GH, CAMPAIGN_ID_IVF, CAMPAIGN_
|
||||
const adPool = [AD_ID_WD_1, AD_ID_WD_2, AD_ID_WD_3, AD_ID_GH_1, AD_ID_GH_2, AD_ID_IVF_1, AD_ID_IVF_2, AD_ID_IVF_3, AD_ID_SN_1];
|
||||
const agents = ['Rekha S.', 'Suresh P.', 'Meena K.'];
|
||||
|
||||
// AI summaries and suggested actions for new leads
|
||||
const newLeadAiData: Array<{ aiSummary: string | null; aiSuggestedAction: string | null }> = [
|
||||
{
|
||||
aiSummary: "First-time inquiry from Women's Day campaign. Submitted via Facebook Lead Form with complete contact details. No previous interactions.",
|
||||
aiSuggestedAction: "Send WhatsApp template and assign to call center for follow-up",
|
||||
},
|
||||
{
|
||||
aiSummary: "High-intent lead from Google Search Ads — searched 'IVF cost Bangalore', spent 4 minutes on the IVF landing page before submitting form.",
|
||||
aiSuggestedAction: "Prioritize for immediate call — high purchase intent signal",
|
||||
},
|
||||
{
|
||||
aiSummary: "Returning visitor (3rd site visit) who finally submitted a contact form. Previously viewed IVF and egg freezing pages.",
|
||||
aiSuggestedAction: "Call within 1 hour — warm lead showing sustained interest",
|
||||
},
|
||||
{
|
||||
aiSummary: "Lead from Instagram Stories ad. Young profile, interested in fertility assessment. No prior contact history.",
|
||||
aiSuggestedAction: "Send WhatsApp intro message with fertility assessment info brochure",
|
||||
},
|
||||
{
|
||||
aiSummary: "Submitted via Google My Business 'Request Appointment' button. Short form — only name and phone provided.",
|
||||
aiSuggestedAction: "Call to gather more details and offer appointment slot",
|
||||
},
|
||||
{
|
||||
aiSummary: "Facebook lead form submission — selected 'Cervical Screening' as service of interest. Age range indicated 35-45.",
|
||||
aiSuggestedAction: "Assign to Rekha S. for cervical screening consultation booking",
|
||||
},
|
||||
{
|
||||
aiSummary: "Website contact form submitted at 11:30 PM. Mentioned 'urgent' in message field. Interested in second opinion for ongoing treatment.",
|
||||
aiSuggestedAction: "Escalate to senior clinician for priority callback",
|
||||
},
|
||||
{ aiSummary: null, aiSuggestedAction: null },
|
||||
{
|
||||
aiSummary: "Referral lead from existing patient. Filled form on mobile device, Instagram source. Quick 90-second form completion.",
|
||||
aiSuggestedAction: "Mention referral source on first call — warms up conversation",
|
||||
},
|
||||
{ aiSummary: null, aiSuggestedAction: null },
|
||||
{
|
||||
aiSummary: "WhatsApp opt-in lead from seasonal campaign. Responded to broadcast message with interest emoji. No further details yet.",
|
||||
aiSuggestedAction: "Continue WhatsApp conversation flow — send service menu",
|
||||
},
|
||||
{
|
||||
aiSummary: "First-time inquiry from Diwali health checkup campaign. Interested in General Health Package. Complete contact details provided.",
|
||||
aiSuggestedAction: "Send health package pricing and book slot via WhatsApp",
|
||||
},
|
||||
{ aiSummary: null, aiSuggestedAction: null },
|
||||
{
|
||||
aiSummary: "Lead from Google Ads 'near me' search. Visited 3 service pages (cardiology, diabetes, general checkup) before submitting.",
|
||||
aiSuggestedAction: "Call to understand primary concern — multi-service interest needs clarification",
|
||||
},
|
||||
{ aiSummary: null, aiSuggestedAction: null },
|
||||
{
|
||||
aiSummary: "Instagram DM converted to lead. Patient asked about embryo freezing costs. Engaged with 5 Instagram posts before contacting.",
|
||||
aiSuggestedAction: "Share embryology team profile and pricing guide on WhatsApp",
|
||||
},
|
||||
{ aiSummary: null, aiSuggestedAction: null },
|
||||
{
|
||||
aiSummary: "Website live chat session that converted to lead. Chat lasted 8 minutes — asked detailed questions about IVF success rates and doctor credentials.",
|
||||
aiSuggestedAction: "Send doctor profiles and success rate report — book free consultation",
|
||||
},
|
||||
{ aiSummary: null, aiSuggestedAction: null },
|
||||
{ aiSummary: null, aiSuggestedAction: null },
|
||||
];
|
||||
|
||||
// --- 20 NEW leads (0-2 days old) ---
|
||||
const newLeads: Lead[] = Array.from({ length: 20 }, (_, i) => {
|
||||
const campaignId = randomFrom(campaignPool);
|
||||
const aiData = newLeadAiData[i] ?? { aiSummary: null, aiSuggestedAction: null };
|
||||
return createMockLead({
|
||||
id: `lead-new-${i + 1}`,
|
||||
leadStatus: 'NEW',
|
||||
@@ -23,14 +87,52 @@ const newLeads: Lead[] = Array.from({ length: 20 }, (_, i) => {
|
||||
firstContactedAt: null,
|
||||
lastContactedAt: null,
|
||||
contactAttempts: 0,
|
||||
aiSummary: aiData.aiSummary,
|
||||
aiSuggestedAction: aiData.aiSuggestedAction,
|
||||
});
|
||||
});
|
||||
|
||||
// AI data for contacted leads
|
||||
const contactedLeadAiData: Array<{ aiSummary: string | null; aiSuggestedAction: string | null }> = [
|
||||
{
|
||||
aiSummary: "2nd interaction — first contacted 2 days ago, expressed interest in cervical screening. Requested callback for appointment booking.",
|
||||
aiSuggestedAction: "Offer appointment booking — patient has shown repeat interest",
|
||||
},
|
||||
{
|
||||
aiSummary: "Spoke with agent on first call (3 min). Interested in IVF but concerned about costs. Asked for payment plan options.",
|
||||
aiSuggestedAction: "Share EMI and insurance empanelment details before next call",
|
||||
},
|
||||
{
|
||||
aiSummary: "Three contact attempts made. Lead answered on 2nd attempt, listened briefly and said 'will call back'. No callback received yet.",
|
||||
aiSuggestedAction: "Try one more call today, then switch to WhatsApp outreach",
|
||||
},
|
||||
{
|
||||
aiSummary: "Active conversation on WhatsApp. Lead asked about senior health packages for parent. Shows moderate engagement.",
|
||||
aiSuggestedAction: "Send senior health package brochure and offer home visit option",
|
||||
},
|
||||
{ aiSummary: null, aiSuggestedAction: null },
|
||||
{
|
||||
aiSummary: "First call connected. Lead is a PCOS patient looking for fertility specialist referral. Discussed treatment options briefly.",
|
||||
aiSuggestedAction: "Book consultation with Dr. Anitha Rao (fertility specialist)",
|
||||
},
|
||||
{ aiSummary: null, aiSuggestedAction: null },
|
||||
{
|
||||
aiSummary: "Lead contacted twice via phone — busy both times. Replied to WhatsApp template saying 'interested, will confirm soon'.",
|
||||
aiSuggestedAction: "Wait 24 hours for confirmation, then send gentle follow-up",
|
||||
},
|
||||
{
|
||||
aiSummary: "Quality lead — verified contact details, confirmed interest in diabetes management program. Budget and timeline discussed.",
|
||||
aiSuggestedAction: "Book discovery call with endocrinologist this week",
|
||||
},
|
||||
{ aiSummary: null, aiSuggestedAction: null },
|
||||
];
|
||||
|
||||
// --- 10 CONTACTED leads (1-4 days old) ---
|
||||
const contactedLeads: Lead[] = Array.from({ length: 10 }, (_, i) => {
|
||||
const created = daysAgo(1 + Math.random() * 3);
|
||||
const firstContacted = daysAgo(0.5 + Math.random() * 1.5);
|
||||
const lastContacted = daysAgo(Math.random() * 0.5);
|
||||
const aiData = contactedLeadAiData[i] ?? { aiSummary: null, aiSuggestedAction: null };
|
||||
return createMockLead({
|
||||
id: `lead-contacted-${i + 1}`,
|
||||
leadStatus: 'CONTACTED',
|
||||
@@ -44,6 +146,8 @@ const contactedLeads: Lead[] = Array.from({ length: 10 }, (_, i) => {
|
||||
firstContactedAt: firstContacted,
|
||||
lastContactedAt: lastContacted,
|
||||
contactAttempts: 1 + Math.floor(Math.random() * 3),
|
||||
aiSummary: aiData.aiSummary,
|
||||
aiSuggestedAction: aiData.aiSuggestedAction,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -132,6 +236,8 @@ const spamLeads: Lead[] = [
|
||||
campaignId: CAMPAIGN_ID_WD,
|
||||
adId: AD_ID_WD_1,
|
||||
assignedAgent: null,
|
||||
aiSummary: 'Suspicious entry — name pattern matches known spam, no email provided, submitted at 2:45 AM, very fast form completion (42 seconds).',
|
||||
aiSuggestedAction: 'Review and likely mark as spam',
|
||||
}),
|
||||
createMockLead({
|
||||
id: 'lead-spam-2',
|
||||
@@ -146,6 +252,8 @@ const spamLeads: Lead[] = [
|
||||
campaignId: CAMPAIGN_ID_IVF,
|
||||
adId: AD_ID_IVF_1,
|
||||
assignedAgent: null,
|
||||
aiSummary: 'Bot submission detected — yopmail disposable address, sequential phone number (+919999999999), form completed in under 5 seconds.',
|
||||
aiSuggestedAction: 'Auto-mark as spam — confirmed bot pattern',
|
||||
}),
|
||||
createMockLead({
|
||||
id: 'lead-spam-3',
|
||||
@@ -160,6 +268,8 @@ const spamLeads: Lead[] = [
|
||||
campaignId: CAMPAIGN_ID_GH,
|
||||
adId: AD_ID_GH_1,
|
||||
assignedAgent: null,
|
||||
aiSummary: 'Flagged by spam classifier — zero phone number (+910000000000), placeholder email domain, no UTM data despite campaign origin.',
|
||||
aiSuggestedAction: 'Mark as spam and exclude from campaign analytics',
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { createContext, useContext, useState } from 'react';
|
||||
|
||||
import type { Lead, Campaign, Ad, LeadActivity, FollowUp, WhatsAppTemplate, Agent } from '@/types/entities';
|
||||
import { mockLeads, mockCampaigns, mockAds, mockFollowUps, mockLeadActivities, mockTemplates, mockAgents } from '@/mocks';
|
||||
import type { Lead, Campaign, Ad, LeadActivity, FollowUp, WhatsAppTemplate, Agent, Call, LeadIngestionSource } from '@/types/entities';
|
||||
import { mockLeads, mockCampaigns, mockAds, mockFollowUps, mockLeadActivities, mockTemplates, mockAgents, mockCalls, mockIngestionSources } from '@/mocks';
|
||||
|
||||
type DataContextType = {
|
||||
leads: Lead[];
|
||||
@@ -12,7 +12,10 @@ type DataContextType = {
|
||||
leadActivities: LeadActivity[];
|
||||
templates: WhatsAppTemplate[];
|
||||
agents: Agent[];
|
||||
calls: Call[];
|
||||
ingestionSources: LeadIngestionSource[];
|
||||
updateLead: (id: string, updates: Partial<Lead>) => void;
|
||||
addCall: (call: Call) => void;
|
||||
};
|
||||
|
||||
const DataContext = createContext<DataContextType | undefined>(undefined);
|
||||
@@ -39,13 +42,19 @@ export const DataProvider = ({ children }: DataProviderProps) => {
|
||||
const [leadActivities] = useState<LeadActivity[]>(mockLeadActivities);
|
||||
const [templates] = useState<WhatsAppTemplate[]>(mockTemplates);
|
||||
const [agents] = useState<Agent[]>(mockAgents);
|
||||
const [calls, setCalls] = useState<Call[]>(mockCalls);
|
||||
const [ingestionSources] = useState<LeadIngestionSource[]>(mockIngestionSources);
|
||||
|
||||
const updateLead = (id: string, updates: Partial<Lead>) => {
|
||||
setLeads((prev) => prev.map((lead) => (lead.id === id ? { ...lead, ...updates } : lead)));
|
||||
};
|
||||
|
||||
const addCall = (call: Call) => {
|
||||
setCalls((prev) => [call, ...prev]);
|
||||
};
|
||||
|
||||
return (
|
||||
<DataContext.Provider value={{ leads, campaigns, ads, followUps, leadActivities, templates, agents, updateLead }}>
|
||||
<DataContext.Provider value={{ leads, campaigns, ads, followUps, leadActivities, templates, agents, calls, ingestionSources, updateLead, addCall }}>
|
||||
{children}
|
||||
</DataContext.Provider>
|
||||
);
|
||||
|
||||
@@ -121,6 +121,8 @@ export type Lead = {
|
||||
lastContactedAt: string | null;
|
||||
contactAttempts: number | null;
|
||||
convertedAt: string | null;
|
||||
aiSummary: string | null;
|
||||
aiSuggestedAction: string | null;
|
||||
patientId: string | null;
|
||||
campaignId: string | null;
|
||||
adId: string | null;
|
||||
@@ -242,3 +244,62 @@ export type Agent = {
|
||||
avgResponseHours: number | null;
|
||||
activeLeadCount: number | null;
|
||||
};
|
||||
|
||||
// Call domain
|
||||
export type CallDirection = 'INBOUND' | 'OUTBOUND';
|
||||
export type CallStatus = 'RINGING' | 'IN_PROGRESS' | 'COMPLETED' | 'MISSED' | 'VOICEMAIL';
|
||||
export type CallDisposition =
|
||||
| 'APPOINTMENT_BOOKED'
|
||||
| 'FOLLOW_UP_SCHEDULED'
|
||||
| 'INFO_PROVIDED'
|
||||
| 'WRONG_NUMBER'
|
||||
| 'NO_ANSWER'
|
||||
| 'CALLBACK_REQUESTED';
|
||||
|
||||
export type Call = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
callDirection: CallDirection | null;
|
||||
callStatus: CallStatus | null;
|
||||
callerNumber: { number: string; callingCode: string }[] | null;
|
||||
agentName: string | null;
|
||||
startedAt: string | null;
|
||||
endedAt: string | null;
|
||||
durationSeconds: number | null;
|
||||
recordingUrl: string | null;
|
||||
disposition: CallDisposition | null;
|
||||
callNotes: string | null;
|
||||
patientId: string | null;
|
||||
appointmentId: string | null;
|
||||
leadId: string | null;
|
||||
// Denormalized for display
|
||||
leadName?: string;
|
||||
leadPhone?: string;
|
||||
leadService?: string;
|
||||
};
|
||||
|
||||
// Lead Ingestion Source domain
|
||||
export type IntegrationStatus = 'ACTIVE' | 'WARNING' | 'ERROR' | 'DISABLED';
|
||||
export type AuthStatus = 'VALID' | 'EXPIRING_SOON' | 'EXPIRED' | 'NOT_CONFIGURED';
|
||||
export type IngestionPlatform =
|
||||
| 'FACEBOOK'
|
||||
| 'INSTAGRAM'
|
||||
| 'GOOGLE_ADS'
|
||||
| 'GOOGLE_MY_BUSINESS'
|
||||
| 'WEBSITE'
|
||||
| 'WHATSAPP';
|
||||
|
||||
export type LeadIngestionSource = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
sourceName: string | null;
|
||||
platform: IngestionPlatform | null;
|
||||
integrationStatus: IntegrationStatus | null;
|
||||
lastSuccessfulSyncAt: string | null;
|
||||
lastFailureAt: string | null;
|
||||
leadsReceivedLast24h: number | null;
|
||||
consecutiveFailures: number | null;
|
||||
authStatus: AuthStatus | null;
|
||||
webhookUrl: string | null;
|
||||
lastErrorMessage: string | null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user