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:
2026-03-16 18:20:41 +05:30
parent 50388dcad5
commit 530dfa1aa4
7 changed files with 660 additions and 3 deletions

372
src/mocks/calls.ts Normal file
View 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,
},
];

View File

@@ -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,

View File

@@ -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';

View 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',
},
];

View File

@@ -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',
}),
];

View File

@@ -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>
);

View File

@@ -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;
};