mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-05-18 20:08:19 +00:00
feat(data-provider): paginate entity queries + suppress polling-loading flash
Two related fixes: 1. KPIs were capped at 100. The data-provider's entity queries were hardcoded to first: 100; on Global the supervisor dashboard showed 'Total Calls: 100' this week while the AI assistant (which paginates) reported 182. Converted each query to a cursor-aware builder, added a generic fetchAll(rootField, builder) that loops until hasNextPage=false (capped at 25 pages × 200 as a runaway guard). Page size bumped 100→200 to cut round-trips on active tenants. 2. Every 30s background poll flipped loading=true, flashing a 'Loading...' overlay across supervisor surfaces. hasLoadedRef guards the flag so only the initial fetch triggers the loading state.
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { createContext, useCallback, useContext, useEffect, useRef, 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,
|
||||
leadsQuery,
|
||||
campaignsQuery,
|
||||
adsQuery,
|
||||
followUpsQuery,
|
||||
leadActivitiesQuery,
|
||||
callsQuery,
|
||||
appointmentsQuery,
|
||||
patientsQuery,
|
||||
} from '@/lib/queries';
|
||||
import {
|
||||
transformLeads,
|
||||
@@ -70,6 +70,7 @@ export const DataProvider = ({ children }: DataProviderProps) => {
|
||||
const [patients, setPatients] = useState<Patient[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const hasLoadedRef = useRef(false);
|
||||
|
||||
// These don't have platform entities yet — empty for now
|
||||
const [templates] = useState<WhatsAppTemplate[]>([]);
|
||||
@@ -82,21 +83,48 @@ export const DataProvider = ({ children }: DataProviderProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
// 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 = <T,>(query: string) => apiClient.graphql<T>(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
|
||||
// `{ <rootField>: { edges } }`.
|
||||
const MAX_PAGES = 25;
|
||||
const fetchAll = async (rootField: string, builder: (after?: string) => string): Promise<any | null> => {
|
||||
const allEdges: any[] = [];
|
||||
let after: string | undefined = undefined;
|
||||
for (let page = 0; page < MAX_PAGES; page++) {
|
||||
const data: any = await gql<any>(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([
|
||||
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),
|
||||
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));
|
||||
@@ -110,6 +138,7 @@ export const DataProvider = ({ children }: DataProviderProps) => {
|
||||
} catch (err: any) {
|
||||
setError(err.message ?? 'Failed to load data');
|
||||
} finally {
|
||||
hasLoadedRef.current = true;
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
Reference in New Issue
Block a user