import type { ReactNode } from 'react'; import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'; import { apiClient } from '@/lib/api-client'; import { leadsQuery, campaignsQuery, adsQuery, followUpsQuery, leadActivitiesQuery, callsQuery, appointmentsQuery, patientsQuery, } from '@/lib/queries'; import { transformLeads, transformCampaigns, transformAds, transformFollowUps, transformLeadActivities, transformCalls, transformAppointments, transformPatients, } from '@/lib/transforms'; import type { Lead, Campaign, Ad, LeadActivity, FollowUp, WhatsAppTemplate, Agent, Call, LeadIngestionSource, Patient, Appointment } from '@/types/entities'; type DataContextType = { leads: Lead[]; campaigns: Campaign[]; ads: Ad[]; followUps: FollowUp[]; leadActivities: LeadActivity[]; templates: WhatsAppTemplate[]; agents: Agent[]; calls: Call[]; appointments: Appointment[]; patients: Patient[]; ingestionSources: LeadIngestionSource[]; loading: boolean; error: string | null; updateLead: (id: string, updates: Partial) => void; addCall: (call: Call) => void; refresh: () => void; }; const DataContext = createContext(undefined); export const useData = (): DataContextType => { const context = useContext(DataContext); if (context === undefined) { throw new Error('useData must be used within a DataProvider'); } return context; }; interface DataProviderProps { children: ReactNode; } export const DataProvider = ({ children }: DataProviderProps) => { const [leads, setLeads] = useState([]); const [campaigns, setCampaigns] = useState([]); const [ads, setAds] = useState([]); const [followUps, setFollowUps] = useState([]); const [leadActivities, setLeadActivities] = useState([]); const [calls, setCalls] = useState([]); const [appointments, setAppointments] = useState([]); const [patients, setPatients] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const hasLoadedRef = useRef(false); // These don't have platform entities yet — empty for now const [templates] = useState([]); const [agents] = useState([]); const [ingestionSources] = useState([]); const fetchData = useCallback(async () => { if (!apiClient.isAuthenticated()) { setLoading(false); return; } // Only flip the global loading flag on the very first fetch. Background // polls refresh data in place so the UI doesn't flash "Loading..." — // QA reported this as the supervisor surfaces randomly refreshing. if (!hasLoadedRef.current) { setLoading(true); } setError(null); try { const gql = (query: string) => apiClient.graphql(query, undefined, { silent: true }).catch(() => null); // Generic Relay pagination. Keeps paging until hasNextPage=false // or we hit MAX_PAGES (guard against runaway loops on bad data). // Returned shape mirrors the original single-page response so // transformX functions work unchanged — they already read // `{ : { edges } }`. const MAX_PAGES = 25; const fetchAll = async (rootField: string, builder: (after?: string) => string): Promise => { const allEdges: any[] = []; let after: string | undefined = undefined; for (let page = 0; page < MAX_PAGES; page++) { const data: any = await gql(builder(after)); if (!data) return null; const root: any = data[rootField]; if (!root) break; if (Array.isArray(root.edges)) allEdges.push(...root.edges); if (!root.pageInfo?.hasNextPage) break; after = root.pageInfo.endCursor; if (!after) break; } return { [rootField]: { edges: allEdges } }; }; const [leadsData, campaignsData, adsData, followUpsData, activitiesData, callsData, appointmentsData, patientsData] = await Promise.all([ fetchAll('leads', leadsQuery), fetchAll('campaigns', campaignsQuery), fetchAll('ads', adsQuery), fetchAll('followUps', followUpsQuery), fetchAll('leadActivities', leadActivitiesQuery), fetchAll('calls', callsQuery), fetchAll('appointments', appointmentsQuery), fetchAll('patients', patientsQuery), ]); if (leadsData) setLeads(transformLeads(leadsData)); if (campaignsData) setCampaigns(transformCampaigns(campaignsData)); if (adsData) setAds(transformAds(adsData)); if (followUpsData) setFollowUps(transformFollowUps(followUpsData)); if (activitiesData) setLeadActivities(transformLeadActivities(activitiesData)); if (callsData) setCalls(transformCalls(callsData)); if (appointmentsData) setAppointments(transformAppointments(appointmentsData)); if (patientsData) setPatients(transformPatients(patientsData)); } catch (err: any) { setError(err.message ?? 'Failed to load data'); } finally { hasLoadedRef.current = true; setLoading(false); } }, []); useEffect(() => { fetchData(); // Poll every 30 seconds for fresh data (calls, leads, appointments) const interval = setInterval(() => { console.log('[DATA-PROVIDER] Polling for fresh data'); fetchData(); }, 30_000); return () => clearInterval(interval); }, [fetchData]); 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} ); };