mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 10:23:27 +00:00
- Worklist default sort descending (newest first), sortable column headers (PRIORITY, PATIENT, SLA) via React Aria - Contextual disposition: auto-selects based on in-call actions (appointment → APPOINTMENT_BOOKED, enquiry → INFO_PROVIDED, transfer → FOLLOW_UP_SCHEDULED) - Context panel redesign: collapsible AI Insight, Upcoming (appointments + follow-ups + linked patient), Recent (calls + activities) sections; auto-collapse on AI chat start - Appointments added to DataProvider with APPOINTMENTS_QUERY, Appointment type, transform - Notification bell for admin/supervisor: performance alerts (idle time, NPS, conversion thresholds) with toast on load + bell dropdown with dismiss; demo alerts as fallback - Slideout z-index fix: added z-50 to slideout ModalOverlay matching modal component Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
139 lines
4.7 KiB
TypeScript
139 lines
4.7 KiB
TypeScript
import type { ReactNode } from 'react';
|
|
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
|
import { apiClient } from '@/lib/api-client';
|
|
import {
|
|
LEADS_QUERY,
|
|
CAMPAIGNS_QUERY,
|
|
ADS_QUERY,
|
|
FOLLOW_UPS_QUERY,
|
|
LEAD_ACTIVITIES_QUERY,
|
|
CALLS_QUERY,
|
|
APPOINTMENTS_QUERY,
|
|
PATIENTS_QUERY,
|
|
} 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<Lead>) => void;
|
|
addCall: (call: Call) => void;
|
|
refresh: () => void;
|
|
};
|
|
|
|
const DataContext = createContext<DataContextType | undefined>(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<Lead[]>([]);
|
|
const [campaigns, setCampaigns] = useState<Campaign[]>([]);
|
|
const [ads, setAds] = useState<Ad[]>([]);
|
|
const [followUps, setFollowUps] = useState<FollowUp[]>([]);
|
|
const [leadActivities, setLeadActivities] = useState<LeadActivity[]>([]);
|
|
const [calls, setCalls] = useState<Call[]>([]);
|
|
const [appointments, setAppointments] = useState<Appointment[]>([]);
|
|
const [patients, setPatients] = useState<Patient[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// These don't have platform entities yet — empty for now
|
|
const [templates] = useState<WhatsAppTemplate[]>([]);
|
|
const [agents] = useState<Agent[]>([]);
|
|
const [ingestionSources] = useState<LeadIngestionSource[]>([]);
|
|
|
|
const fetchData = useCallback(async () => {
|
|
if (!apiClient.isAuthenticated()) {
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const gql = <T,>(query: string) => apiClient.graphql<T>(query, undefined, { silent: true }).catch(() => null);
|
|
|
|
const [leadsData, campaignsData, adsData, followUpsData, activitiesData, callsData, appointmentsData, patientsData] = await Promise.all([
|
|
gql<any>(LEADS_QUERY),
|
|
gql<any>(CAMPAIGNS_QUERY),
|
|
gql<any>(ADS_QUERY),
|
|
gql<any>(FOLLOW_UPS_QUERY),
|
|
gql<any>(LEAD_ACTIVITIES_QUERY),
|
|
gql<any>(CALLS_QUERY),
|
|
gql<any>(APPOINTMENTS_QUERY),
|
|
gql<any>(PATIENTS_QUERY),
|
|
]);
|
|
|
|
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 {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
}, [fetchData]);
|
|
|
|
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, calls, appointments, patients, ingestionSources,
|
|
loading, error,
|
|
updateLead, addCall, refresh: fetchData,
|
|
}}>
|
|
{children}
|
|
</DataContext.Provider>
|
|
);
|
|
};
|