Skip to content

Commit

Permalink
(fix) Display offline patient updates in offline tools (#516)
Browse files Browse the repository at this point in the history
  • Loading branch information
manuelroemer authored Sep 9, 2022
1 parent 309c69a commit d0e5988
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,99 @@ import {
getSynchronizationItems,
getDynamicOfflineDataEntries,
} from "@openmrs/esm-framework";
import useSWR from "swr";
import merge from "lodash-es/merge";
import { useMemo } from "react";
import useSWR, { SWRResponse } from "swr";

export function useOfflineRegisteredPatients() {
return useSWR("offlineTools/offlineRegisteredPatients", async () => {
const offlinePatients = await getDynamicOfflineDataEntries("patient");
const syncItems = await getSynchronizationItems<{
fhirPatient?: fhir.Patient;
}>("patient-registration");
return syncItems
.filter(
(item) =>
item.fhirPatient &&
!offlinePatients.find(
(entry) => entry.identifier === item.fhirPatient.id
)
function useDynamicOfflineDataEntries(type: string) {
return useSWR(`dynamicOfflineData/entries/${type}`, () =>
getDynamicOfflineDataEntries(type)
);
}

function useSynchronizationItems<T>(type: string) {
return useSWR(`syncQueue/items/${type}`, () =>
getSynchronizationItems<T>(type)
);
}

function useFhirPatients(ids: Array<string>) {
const stableIds = useMemo(() => [...ids].sort(), [ids]);
return useSWR(["fhirPatients", stableIds], () =>
Promise.all(
stableIds.map((patientId) =>
fetchCurrentPatient(patientId).then((res) => res.data)
)
)
);
}

export function useOfflineRegisteredPatients() {
const offlinePatientsSwr = useDynamicOfflineDataEntries("patient");
const patientSyncItemsSwr = useSynchronizationItems<{
fhirPatient?: fhir.Patient;
}>("patient-registration");

return useMergedSwr(() => {
return patientSyncItemsSwr.data
.filter((patientRegistrationItem) => {
const isNewlyRegistered =
patientRegistrationItem.fhirPatient &&
!offlinePatientsSwr.data.find(
(offlinePatientEntry) =>
offlinePatientEntry.identifier ===
patientRegistrationItem.fhirPatient.id
);
return isNewlyRegistered;
})
.map((item) => item.fhirPatient);
});
}, [offlinePatientsSwr, patientSyncItemsSwr]);
}

export function useOfflinePatientsWithEntries() {
return useSWR("offlineTools/offlinePatients", async () => {
const offlinePatientEntries = await getDynamicOfflineDataEntries("patient");
const offlinePatientsSwr = useDynamicOfflineDataEntries("patient");
const patientSyncItemsSwr = useSynchronizationItems<{
fhirPatient?: fhir.Patient;
}>("patient-registration");
const fhirPatientsSwr = useFhirPatients(
offlinePatientsSwr.data?.map((entry) => entry.identifier) ?? []
);

return useMergedSwr(() => {
return offlinePatientsSwr.data.map((offlinePatientEntry) => {
const matchingFhirPatient = fhirPatientsSwr.data.find(
(patient) => patient.id === offlinePatientEntry.identifier
);
const offlineUpdates = patientSyncItemsSwr.data
.filter(
(syncItem) =>
syncItem.fhirPatient.id === offlinePatientEntry.identifier
)
.map((item) => item.fhirPatient);
const finalPatient = merge(
matchingFhirPatient,
...offlineUpdates
) as fhir.Patient;

return {
patient: finalPatient,
entry: offlinePatientEntry,
};
});
}, [offlinePatientsSwr, patientSyncItemsSwr, fhirPatientsSwr]);
}

const result = await Promise.all(
offlinePatientEntries.map(async (entry) => ({
patient: (await fetchCurrentPatient(entry.identifier))?.data,
entry,
}))
);
export function useOfflinePatientStats() {
const offlinePatientsSwr = useDynamicOfflineDataEntries("patient");
const offlineRegisteredPatientsSwr = useOfflineRegisteredPatients();

return result.filter((x) => x.patient);
});
return useMergedSwr(
() => ({
downloadedCount: offlinePatientsSwr.data.length,
registeredCount: offlineRegisteredPatientsSwr.data.length,
}),
[offlinePatientsSwr, offlineRegisteredPatientsSwr]
);
}

export function useLastSyncStateOfPatient(patientUuid: string) {
Expand All @@ -52,3 +112,29 @@ export function useLastSyncStateOfPatient(patientUuid: string) {
}
);
}

function useMergedSwr<T>(
merge: () => T,
swrResponses: Array<SWRResponse>
): SWRResponse<T> {
return useMemo(() => {
const areAllLoaded = swrResponses.every((res) => !!res.data);
const data = areAllLoaded ? merge() : null;
const error = swrResponses.find((res) => res.error);
const mutate = () =>
Promise.all(swrResponses.map((res) => res.mutate())).then(merge);
const isValidating = swrResponses.some((res) => res.isValidating);

return {
data,
error,
mutate,
isValidating,
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
merge,
// eslint-disable-next-line react-hooks/exhaustive-deps
...swrResponses.flatMap((res) => [res.data, res.error, res.isValidating]),
]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ const PatientNameTableCell: React.FC<PatientNameTableCellProps> = ({
isNewlyRegistered = false,
}) => {
const { t } = useTranslation();
const name = `${patient.name[0].given.join(" ")} ${patient.name[0].family}`;
const name = `${[patient.name?.[0]?.given, patient.name?.[0]?.family]
.filter(Boolean)
.join(" ")}`;

return (
<div className={styles.cellContainer}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@ import { useTranslation } from "react-i18next";
import HeaderedQuickInfo from "../components/headered-quick-info.component";
import OverviewCard from "../components/overview-card.component";
import { routes } from "../constants";
import useSWR from "swr";
import {
getDynamicOfflineDataEntries,
getSynchronizationItems,
} from "@openmrs/esm-framework";
import { useOfflinePatientStats } from "../hooks/offline-patient-data-hooks";

const PatientsOverviewCard: React.FC = () => {
const { t } = useTranslation();
const { data } = useDownloadedOfflinePatients();
const { data } = useOfflinePatientStats();

return (
<OverviewCard
Expand All @@ -35,22 +31,4 @@ const PatientsOverviewCard: React.FC = () => {
);
};

function useDownloadedOfflinePatients() {
return useSWR(["offlineTools/offlinePatientsTotalCount"], async () => {
const patientDataEntries = await getDynamicOfflineDataEntries("patient");
const patientRegistrationSyncItems = await getSynchronizationItems(
"patient-registration"
);
const registeredCount = patientRegistrationSyncItems.length;
const downloadedCount = patientDataEntries.filter(
(entry) => entry.syncState && entry.syncState.erroredHandlers.length === 0
).length;

return {
downloadedCount,
registeredCount,
};
});
}

export default PatientsOverviewCard;

0 comments on commit d0e5988

Please sign in to comment.