Skip to content

Commit

Permalink
(fix) O3-3709 - ward app - handle pagination (#1292)
Browse files Browse the repository at this point in the history
  • Loading branch information
chibongho authored Sep 5, 2024
1 parent 2543e1b commit 915e023
Show file tree
Hide file tree
Showing 18 changed files with 254 additions and 277 deletions.
26 changes: 22 additions & 4 deletions packages/esm-ward-app/src/hooks/useAdmissionLocation.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import { type FetchResponse, openmrsFetch, restBaseUrl, useFeatureFlag } from '@openmrs/esm-framework';
import useSWR from 'swr';
import { type AdmissionLocationFetchResponse } from '../types/index';
import useSWRImmutable from 'swr/immutable';
import useWardLocation from './useWardLocation';

const requestRep =
'custom:(ward,totalBeds,occupiedBeds,bedLayouts:(rowNumber,columnNumber,bedNumber,bedId,bedUuid,status,location,patients:(person:full,identifiers,uuid)))';

// note "admissionLocation" sn't the clearest name, but it matches the endpoint; endpoint fetches bed information (including info about patients in those beds) for a location (as provided by the bed management module)
/**
*
* Fetches bed information (including info about patients in those beds) for a location,
* as provided by the bed management module. If the bed management module is not installed,
* then no request will be made, the return object's
* `isLoading` field will be false, and the `admissionLocation` field will be undefined.
*
* Note that "admissionLocation" isn't the clearest name, but it matches the endpoint name
*
* @param rep the "v=" representation parameter
* @returns
*/
export function useAdmissionLocation(rep: string = requestRep) {
const { location } = useWardLocation();

const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');

const apiUrl = location?.uuid ? `${restBaseUrl}/admissionLocation/${location?.uuid}?v=${rep}` : null;
const { data, ...rest } = useSWRImmutable<FetchResponse<AdmissionLocationFetchResponse>, Error>(apiUrl, openmrsFetch);
const { data, ...rest } = useSWR<FetchResponse<AdmissionLocationFetchResponse>, Error>(
isBedManagementModuleInstalled ? apiUrl : null,
openmrsFetch,
);

return {
admissionLocation: data?.data,
...rest,
Expand Down
6 changes: 3 additions & 3 deletions packages/esm-ward-app/src/hooks/useBeds.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import { openmrsFetch, restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework';
import useSWR from 'swr';
import { type Bed, type BedStatus } from '../types/index';

Expand All @@ -17,10 +17,10 @@ export function useBeds(searchCriteria?: BedSearchCriteria) {
}

const apiUrl = `${restBaseUrl}/bed?${searchParam}`;
const { data, ...rest } = useSWR<{ data: { results: Array<Bed> } }, Error>(apiUrl, openmrsFetch);
const { data, ...rest } = useOpenmrsFetchAll<Bed>(apiUrl);

return {
beds: data?.data?.results ?? [],
beds: data,
...rest,
};
}
7 changes: 3 additions & 4 deletions packages/esm-ward-app/src/hooks/useConcept.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { type Concept, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import useSWRImmutable from 'swr/immutable';
import { type Concept, restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework';

export function useConcepts(uuids: string[], rep = 'default') {
const apiUrl = `${restBaseUrl}/concept?references=${uuids.join()}&v=${rep}`;
const { data, ...rest } = useSWRImmutable<{ data: { results: Array<Concept> } }, Error>(apiUrl, openmrsFetch);
const { data, ...rest } = useOpenmrsFetchAll<Concept>(apiUrl, { immutable: true });
return {
concepts: data?.data?.results,
concepts: data,
...rest,
};
}
14 changes: 4 additions & 10 deletions packages/esm-ward-app/src/hooks/useInpatientAdmission.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import useSWR from 'swr';
import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import { type InpatientAdmissionFetchResponse } from '../types';
import { restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework';
import { type InpatientAdmission } from '../types';
import useWardLocation from './useWardLocation';

export function useInpatientAdmission() {
Expand All @@ -15,15 +14,10 @@ export function useInpatientAdmission() {
'currentInpatientRequest:(dispositionLocation,dispositionType,disposition:(uuid,display),dispositionEncounter:(uuid,display),dispositionObsGroup:(uuid,display),visit:(uuid),patient:(uuid)),' +
'firstAdmissionOrTransferEncounter:(encounterDatetime),' +
')';
const { data, ...rest } = useSWR<FetchResponse<InpatientAdmissionFetchResponse>, Error>(

return useOpenmrsFetchAll<InpatientAdmission>(
location
? `${restBaseUrl}/emrapi/inpatient/admission?currentInpatientLocation=${location.uuid}&v=${customRepresentation}`
: null,
openmrsFetch,
);

return {
inpatientAdmissions: data?.data?.results,
...rest,
};
}
19 changes: 4 additions & 15 deletions packages/esm-ward-app/src/hooks/useInpatientRequest.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import type { DispositionType, InpatientRequestFetchResponse } from '../types';
import useSWR from 'swr';
import { restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework';
import type { DispositionType, InpatientRequest } from '../types';
import useWardLocation from './useWardLocation';
import { useMemo } from 'react';

// prettier-ignore
const defaultRep =
Expand All @@ -26,18 +24,9 @@ export function useInpatientRequest(
searchParams.set('dispositionLocation', location?.uuid);
searchParams.set('v', rep);

const { data, ...rest } = useSWR<FetchResponse<InpatientRequestFetchResponse>, Error>(
const { data, ...rest } = useOpenmrsFetchAll<InpatientRequest>(
location?.uuid ? `${restBaseUrl}/emrapi/inpatient/request?${searchParams.toString()}` : null,
openmrsFetch,
);

const results = useMemo(
() => ({
inpatientRequests: data?.data?.results,
...rest,
}),
[data, rest],
);

return results;
return { inpatientRequests: data, ...rest };
}
59 changes: 8 additions & 51 deletions packages/esm-ward-app/src/hooks/useLocations.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,11 @@
import { type FetchResponse, fhirBaseUrl, openmrsFetch } from '@openmrs/esm-framework';
import { useEffect, useMemo, useState } from 'react';
import useSWRImmutable from 'swr/immutable';
import { fhirBaseUrl, useFhirPagination } from '@openmrs/esm-framework';

interface FhirLocation {
fullUrl: string;
resource: {
resourceType: 'Location';
id: string;
name: string;
description: string;
};
}

interface FhirResponse {
resourceType: 'Bundle';
id: '6a107c31-d760-4df0-bb70-89ad742225ca';
meta: {
lastUpdated: '2024-08-08T06:28:01.495+00:00';
};
type: 'searchset';
total: number;
link: Array<{
relation: 'self' | 'prev' | 'next';
url: string;
}>;
entry: Array<FhirLocation>;
}

export default function useLocations(filterCriteria: Array<Array<string>> = [], skip: boolean = false) {
const [totalLocations, setTotalLocations] = useState(0);
const [url, setUrl] = useState(`${fhirBaseUrl}/Location`);
export default function useLocations(
filterCriteria: Array<Array<string>> = [],
pageSize: number,
skip: boolean = false,
) {
const searchParams = new URLSearchParams(filterCriteria);
const urlWithSearchParams = `${url}?${searchParams.toString()}`;
const { data, ...rest } = useSWRImmutable<FetchResponse<FhirResponse>>(
!skip ? urlWithSearchParams : null,
openmrsFetch,
);

useEffect(() => {
if (data?.data) {
setTotalLocations(data.data.total);
}
}, [data]);

const results = useMemo(() => {
return {
locations: data?.data?.entry?.map((entry) => entry.resource),
totalLocations,
...rest,
};
}, [data, rest, totalLocations]);
return results;
const urlWithSearchParams = `${fhirBaseUrl}/Location?${searchParams.toString()}`;
return useFhirPagination<fhir.Location>(skip ? null : urlWithSearchParams, pageSize, { immutable: true });
}
8 changes: 2 additions & 6 deletions packages/esm-ward-app/src/hooks/useObs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import useSWR from 'swr';
import { restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework';
import { type Observation } from '../types';
import { type Link, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';

interface ObsSearchCriteria {
patient: string;
Expand All @@ -14,8 +13,5 @@ export function useObs(criteria?: ObsSearchCriteria, representation = 'default')
});

const apiUrl = `${restBaseUrl}/obs?${params}`;
return useSWR<{ data: { results: Array<Observation>; totalCount: number; links: Array<Link> } }, Error>(
apiUrl,
openmrsFetch,
);
return useOpenmrsFetchAll<Observation>(apiUrl);
}
2 changes: 1 addition & 1 deletion packages/esm-ward-app/src/hooks/useWardPatientGrouping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function useWardPatientGrouping() {
const admissionLocationResponse = useAdmissionLocation();
const inpatientAdmissionResponse = useInpatientAdmission();

const { inpatientAdmissions } = inpatientAdmissionResponse;
const { data: inpatientAdmissions } = inpatientAdmissionResponse;
const { admissionLocation } = admissionLocationResponse;
const inpatientAdmissionsByPatientUuid = useMemo(() => {
return getInpatientAdmissionsUuidMap(inpatientAdmissions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,26 @@ export default function LocationSelector(props: LocationSelectorProps) {
const isTablet = !isDesktop(useLayoutType());
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm);
const [page, setPage] = useState(1);
const filterCriteria: Array<Array<string>> = useMemo(() => {
const criteria = [];
if (debouncedSearchTerm) {
criteria.push(['name:contains', debouncedSearchTerm]);
}
criteria.push(['_count', size.toString()]);
if (emrConfiguration) {
criteria.push(['_tag', emrConfiguration.supportsTransferLocationTag.name]);
}
if (page > 1) {
criteria.push(['_getpagesoffset', ((page - 1) * size).toString()]);
}
return criteria;
}, [debouncedSearchTerm, page, emrConfiguration]);
const { locations, isLoading, totalLocations } = useLocations(filterCriteria, !emrConfiguration);
}, [debouncedSearchTerm, emrConfiguration]);
const {
data: locations,
isLoading,
totalCount,
currentPage,
totalPages,
goToNext,
goToPrevious,
} = useLocations(filterCriteria, size, !emrConfiguration);

const handlePageChange = useCallback(
({ page: newPage }) => {
setPage(newPage);
},
[setPage, page],
);
const handleSearch = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
Expand Down Expand Up @@ -84,30 +81,30 @@ export default function LocationSelector(props: LocationSelectorProps) {
</RadioButtonGroup>
</ResponsiveWrapper>
)}
{totalLocations > 5 && (
{totalCount > size && (
<div className={styles.pagination}>
<span className={styles.bodyShort01}>
{t('showingLocations', '{{start}}-{{end}} of {{count}} locations', {
start: (page - 1) * size + 1,
end: Math.min(page * size, totalLocations),
count: totalLocations,
start: (currentPage - 1) * size + 1,
end: Math.min(currentPage * size, totalCount),
count: totalCount,
})}
</span>
<div>
<IconButton
className={classNames(styles.button, styles.buttonLeft)}
disabled={page === 1}
disabled={currentPage === 1}
kind="ghost"
label={t('previousPage', 'Previous page')}
onClick={() => handlePageChange({ page: page - 1 })}>
onClick={() => goToPrevious()}>
<ChevronLeftIcon />
</IconButton>
<IconButton
className={styles.button}
disabled={page * size >= totalLocations}
disabled={currentPage >= totalPages}
kind="ghost"
label={t('nextPage', 'Next page')}
onClick={() => handlePageChange({ page: page + 1 })}>
onClick={() => goToNext()}>
<ChevronRightIcon />
</IconButton>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { SkeletonText, Tag } from '@carbon/react';
import { type Patient, translateFrom, type Visit, type OpenmrsResource } from '@openmrs/esm-framework';
import { type OpenmrsResource, type Patient, type Visit } from '@openmrs/esm-framework';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { moduleName } from '../../constant';
import { type ColoredObsTagsCardRowConfigObject } from '../../config-schema-extension-colored-obs-tags';
import { useObs } from '../../hooks/useObs';
import styles from '../ward-patient-card.scss';
import { obsCustomRepresentation, useConceptToTagColorMap } from './ward-patient-obs.resource';
import { type ColoredObsTagsCardRowConfigObject } from '../../config-schema-extension-colored-obs-tags';

interface WardPatientCodedObsTagsProps {
config: ColoredObsTagsCardRowConfigObject;
Expand All @@ -28,12 +27,12 @@ const WardPatientCodedObsTags: React.FC<WardPatientCodedObsTagsProps> = ({ confi
const { conceptUuid, summaryLabel, summaryLabelColor } = config;
const { data, isLoading } = useObs({ patient: patient.uuid, concept: conceptUuid }, obsCustomRepresentation);
const { t } = useTranslation();
const { data: conceptToTagColorMap } = useConceptToTagColorMap(config.tags);
const conceptToTagColorMap = useConceptToTagColorMap(config.tags);

if (isLoading) {
return <SkeletonText />;
} else {
const obsToDisplay = data?.data?.results?.filter((o) => {
const obsToDisplay = data?.filter((o) => {
const matchVisit = o.encounter.visit?.uuid == visit?.uuid;
return matchVisit || visit == null; // TODO: remove visit == null hack when server API supports returning visit
});
Expand Down
Loading

0 comments on commit 915e023

Please sign in to comment.