import { isAsOfIncomplete } from '../utils/searchTruncation'; import { useCallback, useEffect, useState } from 'react'; import { useListServiceContext, usePatientServiceContext } from '../services'; import { describeFetchFailure } from '../utils/fetchFailureMessage'; import { patientCacheGuard } from '../utils/cacheOwner'; interface UseFhirClinicalListsOptions { autoLoad?: boolean; errorMessage?: string; forceReload?: boolean; } interface UseFhirClinicalListsReturn { /** Served as of a cutoff and not in full: what it left out is unknown, never none. */ incomplete: boolean; loading: boolean; error: string | undefined; loadClinicalLists: () => Promise; clearError: () => void; clinicalListsBundle: ReturnType['clinicalListsBundle']; clinicalListsList: ReturnType['clinicalListsList']; hasConfirmedNoCurrentMedications: boolean; hasConfirmedNoKnownAllergies: boolean; hasConfirmedNoKnownFamilyHistory: boolean; hasConfirmedNoRelatedPersons: boolean; } export const useFhirClinicalLists = ( options: UseFhirClinicalListsOptions = {} ): UseFhirClinicalListsReturn => { const { autoLoad = true, errorMessage = 'Failed to load clinical lists', forceReload = false } = options; const { patientState } = usePatientServiceContext(); const { cachedForPatientId, fetchClinicalListsWithCache, clinicalListsBundle, clinicalListsList, hasConfirmedNoCurrentMedications, hasConfirmedNoKnownAllergies, hasConfirmedNoKnownFamilyHistory, hasConfirmedNoRelatedPersons } = useListServiceContext(); const [loading, setLoading] = useState(false); const [error, setError] = useState(); const loadClinicalLists = useCallback(async () => { if (!patientState) { return; } setLoading(true); setError(undefined); try { await fetchClinicalListsWithCache(patientState, forceReload); } catch (err) { console.error('Failed to load clinical lists:', err); setError(describeFetchFailure(err, errorMessage)); } finally { setLoading(false); } }, [patientState, fetchClinicalListsWithCache, forceReload, errorMessage]); const clearError = useCallback(() => { setError(undefined); }, []); useEffect(() => { if (!autoLoad) return; if (!patientState) return; // A bundle belonging to SOMEBODY ELSE is not a reason not to ask. This used to fire only when // there was no bundle at all, so moving between two charts left the previous patient's list in // place, with no request and nothing on screen to say it was not theirs. // NOT a refetch on every navigation: same patient, bundle loaded, no request. if (!clinicalListsBundle || cachedForPatientId !== patientState.id || forceReload) { loadClinicalLists(); } }, [autoLoad, patientState, clinicalListsBundle, cachedForPatientId, forceReload, loadClinicalLists]); // The cache outlives a change of patient: hand out only the patient asked about. const guard = patientCacheGuard(cachedForPatientId, patientState?.id); return { loading: guard.loading(loading, Boolean(error)), error, loadClinicalLists, clearError, // Served as of a cutoff and not in full: what it left out is unknown, never none. incomplete: isAsOfIncomplete(guard.bundle(clinicalListsBundle, null)), clinicalListsBundle: guard.bundle(clinicalListsBundle, null), clinicalListsList: guard.list(clinicalListsList), // Answers derived from the same cache, so they are the last patient's until it is this one's. hasConfirmedNoCurrentMedications: guard.flag(hasConfirmedNoCurrentMedications), hasConfirmedNoKnownAllergies: guard.flag(hasConfirmedNoKnownAllergies), hasConfirmedNoKnownFamilyHistory: guard.flag(hasConfirmedNoKnownFamilyHistory), hasConfirmedNoRelatedPersons: guard.flag(hasConfirmedNoRelatedPersons) }; };