Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(fix) Fix infinite loop on loading cohort list columns #1884

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import { TableEmptyState } from '../empty-state/table-empty-state.component';
import { DataTableSkeleton } from '@carbon/react';
import { basePath } from '../../constants';
import { useTranslation } from 'react-i18next';
import { useFormsJson } from '../../hooks/useFormsJson';
import { columns, consolidatatePatientMeta, filterPatientsByName, type PatientListColumn } from './helpers';

import styles from './cohort-patient-list.scss';
import { useCohortData, usePatientsLastEncounters } from '../../hooks/useCohortList';
import { useFormsJson } from '../../hooks/useFormsJson';

interface CohortPatientListProps {
cohortId: string;
Expand Down Expand Up @@ -58,23 +59,19 @@ export const CohortPatientList: React.FC<CohortPatientListProps> = ({
viewPatientProgramSummary,
viewTptPatientProgramSummary,
}) => {
const [isLoading, setIsLoading] = useState(true);
const [hasLoadedPatients, setHasLoadedPatients] = useState(false);
const [loadedEncounters, setLoadedEncounters] = useState(false);
const [loadedHIVStatuses, setLoadedHIVStatuses] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [patientsCount, setPatientsCount] = useState(0);
const [searchTerm, setSearchTerm] = useState(null);
const [counter, setCounter] = useState(0);
const [searchTerm, setSearchTerm] = useState('');
const [filteredResults, setFilteredResults] = useState([]);
const [loadedExtraEncounters, setLoadedExtraEncounters] = useState(false);
const [extraEncounters, setExtraEncounters] = useState([]);
const [paginatedPatients, setPaginatedPatients] = useState([]);
const [allPatients, setAllPatients] = useState([]);
const [rawCohortData, setRawCohortData] = useState<{ location?: any; members: any[] }>({ members: [] });
const [isLoadingCohorts, setIsLoadingCohorts] = useState(true);
const columnAtLastIndex = 'actions';
const [patientsCount, setPatientsCount] = useState(0);
const [counter, setCounter] = useState(0);
const { t } = useTranslation();

// Fetch cohort data and forms JSON using your existing hooks
const { data: cohortData, error: cohortError } = useCohortData(cohortId, isReportingCohort, queryParams);

const forms = useMemo(() => [launchableForm.name], [launchableForm]);
const { formsJson, isLoading: isLoadingFormsJson } = useFormsJson(forms);

Expand All @@ -97,52 +94,29 @@ export const CohortPatientList: React.FC<CohortPatientListProps> = ({
? 'Male'
: 'Female'
: rawPatient.patient.person.gender == 'M'
? 'Male'
: 'Female',
? 'Male'
: 'Female',
birthday: isReportingCohort ? rawPatient.person.birthdate : rawPatient.patient.person.birthdate,
url: `${basePath}${patientUuid}/chart${dashboard}`,
};
},
[isReportingCohort, launchableForm?.targetDashboard],
[isReportingCohort, launchableForm?.targetDashboard]
);

const { t } = useTranslation();

const updatePatientTable = (fullDataset, start, itemCount) => {
let currentRows = [];

for (let i = start; i < start + itemCount; i++) {
if (i < fullDataset.length) {
currentRows.push(fullDataset[i]);
}
}
setPaginatedPatients(currentRows);
};
const updatePatientTable = useCallback((fullDataset, start, itemCount) => {
setPaginatedPatients(fullDataset.slice(start, start + itemCount));
}, []);

useEffect(() => {
if (!isReportingCohort) {
getCohort(cohortId, 'full').then((results) => {
setRawCohortData({ members: results.cohortMembers, location: results.location });
setIsLoadingCohorts(false);
});
} else {
getReportingCohortMembers(cohortId, queryParams).then((results) => {
setRawCohortData({ members: results.map((result) => result.data) });
setIsLoadingCohorts(false);
});
}
}, [cohortId, isReportingCohort, queryParams]);

useEffect(() => {
if (!isLoadingCohorts && !isLoadingFormsJson) {
const patients = rawCohortData.members.map((member) => {
if (cohortData && formsJson) {
const patients = cohortData.members.map((member) => {
const patient = constructPatient(member);
member['patientUrl'] = patient.url;
return {
...patient,
...consolidatatePatientMeta(member, formsJson[0], {
isDynamicCohort: isReportingCohort,
location: rawCohortData.location,
location: cohortData.location,
encounterType: associatedEncounterType,
moduleName,
launchableFormProps: launchableForm,
Expand All @@ -157,93 +131,31 @@ export const CohortPatientList: React.FC<CohortPatientListProps> = ({
});
setAllPatients(patients);
updatePatientTable(patients, 0, pageSize);
setHasLoadedPatients(true);
setIsLoading(false);
}
}, [
rawCohortData,
formsJson,
isLoadingCohorts,
isLoadingFormsJson,
pageSize,
constructPatient,
isReportingCohort,
associatedEncounterType,
moduleName,
launchableForm,
addPatientToListOptions,
t,
viewTptPatientProgramSummary,
viewPatientProgramSummary,
]);
}, [cohortData, formsJson, pageSize, constructPatient]);

useEffect(() => {
if (hasLoadedPatients && allPatients.length) {
Promise.all(allPatients.map((patient) => fetchPatientLastEncounter(patient.uuid, associatedEncounterType))).then(
(results) => {
results.forEach((encounter, index) => {
allPatients[index].latestEncounter = encounter;
if (index == allPatients.length - 1) {
setAllPatients([...allPatients]);
setLoadedEncounters(true);
setIsLoading(false);
}
});
},
);
}
setPatientsCount(allPatients.length);
}, [allPatients, associatedEncounterType, hasLoadedPatients]);
// Use the custom SWR hook to fetch patient last encounters
const { data: patientsWithEncounters, isLoading: isLoadingEncounters, isError: encountersError } = usePatientsLastEncounters(allPatients, associatedEncounterType, excludeColumns);

useEffect(() => {
const fetchHivResults = excludeColumns ? !excludeColumns.includes('hivResult') : true;
if ((loadedEncounters || !associatedEncounterType) && !loadedHIVStatuses && fetchHivResults) {
Promise.all(allPatients.map((patient) => fetchPatientsFinalHIVStatus(patient.uuid))).then((results) => {
results.forEach((hivResult, index) => {
allPatients[index].hivResult = hivResult;
if (index == allPatients.length - 1) {
setAllPatients([...allPatients]);
setLoadedHIVStatuses(true);
}
});
});
if (patientsWithEncounters && !isLoadingEncounters) {
setAllPatients(patientsWithEncounters);
}
}, [allPatients, associatedEncounterType, excludeColumns, loadedEncounters, loadedHIVStatuses]);

const pagination = useMemo(() => {
return {
usePagination: true,
currentPage: currentPage,
onChange: ({ pageSize, page }) => {
let startOffset = (page - 1) * pageSize;
updatePatientTable(allPatients, startOffset, pageSize);
}, [patientsWithEncounters, isLoadingEncounters]);

setCurrentPage(page);
setPageSize(pageSize);
return null;
},
pageSize: pageSize,
totalItems: patientsCount,
};
}, [currentPage, pageSize, patientsCount, allPatients]);
useEffect(() => {
setPatientsCount(allPatients.length);
}, [allPatients]);

const handleSearch = useCallback(
(searchTerm) => {
setSearchTerm(searchTerm);
const filtrate = filterPatientsByName(searchTerm, allPatients);
setFilteredResults(filtrate);
return true;
},
[allPatients],
[allPatients]
);

useEffect(() => {
attach(cohortSlotName, 'patient-table');
return () => {
detach(cohortSlotName, 'patient-table');
};
});

const state = useMemo(() => {
let filteredColumns = [...columns];
if (excludeColumns) {
Expand All @@ -258,8 +170,7 @@ export const CohortPatientList: React.FC<CohortPatientListProps> = ({
}
});
}
// position column designated to be at the last index
const index = filteredColumns.findIndex((column) => column.key == columnAtLastIndex);
const index = filteredColumns.findIndex((column) => column.key == 'actions');
if (index) {
const column = filteredColumns[index];
filteredColumns.splice(index, 1);
Expand All @@ -269,12 +180,11 @@ export const CohortPatientList: React.FC<CohortPatientListProps> = ({
return {
patients: searchTerm ? filteredResults : paginatedPatients,
columns: filteredColumns,
isLoading,
isLoading: !cohortData || !formsJson || isLoadingEncounters,
search: {
placeHolder: t('searchClientList', 'Search client list'),
onSearch: (searchTerm) => {
if (!searchTerm) {
// clear value
setSearchTerm('');
}
},
Expand All @@ -288,52 +198,41 @@ export const CohortPatientList: React.FC<CohortPatientListProps> = ({
autoFocus: true,
},
},
pagination: pagination,
pagination: {
usePagination: true,
currentPage: currentPage,
onChange: ({ pageSize, page }) => {
let startOffset = (page - 1) * pageSize;
updatePatientTable(allPatients, startOffset, pageSize);

setCurrentPage(page);
setPageSize(pageSize);
return null;
},
pageSize: pageSize,
totalItems: patientsCount,
},
autoFocus: true,
};
}, [
searchTerm,
filteredResults,
paginatedPatients,
handleSearch,
pagination,
isLoading,
currentPage,
pageSize,
patientsCount,
cohortData,
formsJson,
isLoadingEncounters,
t,
excludeColumns,
otherColumns,
]);

useEffect(() => {
setCounter(counter + 1);
}, [counter, state]);

useEffect(() => {
if (allPatients.length && extraAssociatedEncounterTypes && !loadedExtraEncounters) {
allPatients.forEach((patient) => {
extraAssociatedEncounterTypes.forEach((encType) => {
extraEncounters.push(fetchPatientLastEncounter(patient.uuid, encType));
});
});

Promise.all(extraEncounters).then((results) => {
results.forEach((encounter, index) => {
const idx = allPatients.findIndex((patient) => patient.uuid === encounter?.patient.uuid);
if (idx !== -1) {
allPatients[idx].latestExtraEncounters = allPatients[idx].latestExtraEncounters?.concat(encounter) ?? [
encounter,
];
}
});
setLoadedExtraEncounters(true);
});
}
}, [allPatients, extraAssociatedEncounterTypes, extraEncounters, loadedExtraEncounters]);

return (
<div className={styles.table1}>
{isLoading ? (
<DataTableSkeleton rowCount={5} />
) : !paginatedPatients.length ? (
{!paginatedPatients.length ? (
<TableEmptyState
tableHeaders={state.columns}
message={t('noPatientSidenav', 'There are no patients in this list.')}
Expand Down
80 changes: 80 additions & 0 deletions packages/esm-commons-lib/src/hooks/useCohortList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { openmrsFetch } from '@openmrs/esm-framework';
import useSWR from 'swr';
import { encounterRepresentation } from '../constants';
import { fetchPatientsFinalHIVStatus } from '../api.resource';

export function useCohortData(cohortId, isReportingCohort, queryParams) {
const params = queryParams ? queryParams.join('&') : null;
const url = isReportingCohort
? `/ws/rest/v1/reportingrest/cohort/${cohortId}?${params}`
: `/ws/rest/v1/cohortm/cohort/${cohortId}`;
const { data, error, isLoading } = useSWR<{ data: any }, Error>(url, openmrsFetch);

console.log("===isLoading", isLoading)
console.log("====cohort data", data)
console.log("====cohort data data", data?.data)

const cohortData = data?.data?.cohortMembers ? data?.data?.cohortMembers.filter((member) => !member.voided) : data?.data;

return {
data: cohortData ?? [],
isLoading,
error
}
}

const fetchPatientLastEncounter = async (patientUuid, encounterType) => {
const query = `encounterType=${encounterType}&patient=${patientUuid}`;
const { data } = await openmrsFetch(`/ws/rest/v1/encounter?${query}&v=default`);
if (data.results.length) {
const sortedEncounters = data.results.sort(
(firstEncounter, secondEncounter) =>
new Date(secondEncounter.encounterDatetime).getTime() - new Date(firstEncounter.encounterDatetime).getTime(),
);
return sortedEncounters[0];
}
return null;
};

export function usePatientLastEncounter(patientUuid, encounterType) {
const { data, error } = useSWR(
patientUuid ? [`/ws/rest/v1/encounter?encounterType=${encounterType}&patient=${patientUuid}`, encounterType] : null,
() => fetchPatientLastEncounter(patientUuid, encounterType),
);

return {
data: data?.data,
isLoading: !error && !data,
isError: error,
};
}

const fetchPatientsData = async (patients, encounterType, excludeColumns) => {
const encountersPromises = patients.map((patient) => fetchPatientLastEncounter(patient.uuid, encounterType));
const encountersResults = await Promise.all(encountersPromises);

const hivStatusesPromises = excludeColumns
? patients.map((patient) => fetchPatientsFinalHIVStatus(patient.uuid))
: [];
const hivStatusesResults = await Promise.all(hivStatusesPromises);

const patientsWithData = patients.map((patient, index) => ({
...patient,
latestEncounter: encountersResults[index],
hivResult: hivStatusesResults[index] || null,
}));

return patientsWithData;
};

export function usePatientsLastEncounters(patients, encounterType, excludeColumns) {
const { data, error } = useSWR(patients.length > 0 ? [patients, encounterType, excludeColumns] : null, () =>
fetchPatientsData(patients, encounterType, excludeColumns),
);

return {
data: data?.data,
isLoading: !error && !data,
isError: error,
};
}
Loading