diff --git a/src/mocks/calls.ts b/src/mocks/calls.ts new file mode 100644 index 0000000..f4a451f --- /dev/null +++ b/src/mocks/calls.ts @@ -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, + }, +]; diff --git a/src/mocks/factories.ts b/src/mocks/factories.ts index e90a240..1e4bce1 100644 --- a/src/mocks/factories.ts +++ b/src/mocks/factories.ts @@ -94,6 +94,8 @@ export function createMockLead(overrides?: Partial): Lead { lastContactedAt: null, contactAttempts: 0, convertedAt: null, + aiSummary: null, + aiSuggestedAction: null, patientId: null, campaignId: null, adId: null, diff --git a/src/mocks/index.ts b/src/mocks/index.ts index d09bcac..cf81347 100644 --- a/src/mocks/index.ts +++ b/src/mocks/index.ts @@ -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'; diff --git a/src/mocks/ingestion-sources.ts b/src/mocks/ingestion-sources.ts new file mode 100644 index 0000000..cb55f36 --- /dev/null +++ b/src/mocks/ingestion-sources.ts @@ -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', + }, +]; diff --git a/src/mocks/leads.ts b/src/mocks/leads.ts index 685d988..5c0a2cf 100644 --- a/src/mocks/leads.ts +++ b/src/mocks/leads.ts @@ -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', }), ]; diff --git a/src/providers/data-provider.tsx b/src/providers/data-provider.tsx index dc98930..3c73218 100644 --- a/src/providers/data-provider.tsx +++ b/src/providers/data-provider.tsx @@ -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) => void; + addCall: (call: Call) => void; }; const DataContext = createContext(undefined); @@ -39,13 +42,19 @@ export const DataProvider = ({ children }: DataProviderProps) => { const [leadActivities] = useState(mockLeadActivities); const [templates] = useState(mockTemplates); const [agents] = useState(mockAgents); + const [calls, setCalls] = useState(mockCalls); + const [ingestionSources] = useState(mockIngestionSources); const updateLead = (id: string, updates: Partial) => { setLeads((prev) => prev.map((lead) => (lead.id === id ? { ...lead, ...updates } : lead))); }; + const addCall = (call: Call) => { + setCalls((prev) => [call, ...prev]); + }; + return ( - + {children} ); diff --git a/src/types/entities.ts b/src/types/entities.ts index 53e54e6..53fd125 100644 --- a/src/types/entities.ts +++ b/src/types/entities.ts @@ -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; +};