diff --git a/packages/esm-commons-lib/src/api.resource.ts b/packages/esm-commons-lib/src/api.resource.ts index 0cacbd838..ff6b0c045 100644 --- a/packages/esm-commons-lib/src/api.resource.ts +++ b/packages/esm-commons-lib/src/api.resource.ts @@ -1,4 +1,4 @@ -import { openmrsFetch } from '@openmrs/esm-framework'; +import { fhirBaseUrl, openmrsFetch } from '@openmrs/esm-framework'; import dayjs from 'dayjs'; import { finalHIVCodeConcept, @@ -358,12 +358,11 @@ export async function getCohortList( `/ws/rest/v1/encounter?encounterType=${encounterType}&patient=${member.patient.uuid}&v=${encounterRepresentation}`, ).then(({ data }) => { if (data.results.length) { - const sortedEncounters = data.results - .sort( - (firstEncounter, secondEncounter) => - new Date(secondEncounter.encounterDatetime).getTime() - - new Date(firstEncounter.encounterDatetime).getTime(), - ); + const sortedEncounters = data.results.sort( + (firstEncounter, secondEncounter) => + new Date(secondEncounter.encounterDatetime).getTime() - + new Date(firstEncounter.encounterDatetime).getTime(), + ); return sortedEncounters[0]; } return null; diff --git a/packages/esm-commons-lib/src/components/banner-tags/.patient-status-tag.test.tsx b/packages/esm-commons-lib/src/components/banner-tags/.patient-status-tag.test.tsx deleted file mode 100644 index d1f7dc74a..000000000 --- a/packages/esm-commons-lib/src/components/banner-tags/.patient-status-tag.test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { render, act, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import { PatientStatusBannerTag } from './patient-status-tag.component'; -import { isPatientHivPositive } from './patientHivStatus'; - -const mockIsPatientHivPositive = isPatientHivPositive as jest.Mock; -jest.mock('./patientHivStatus'); - -describe('PatientStatusBannerTag', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - const hivPositiveSampleUuid = '703AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; - - describe('PatientStatusBannerTag', () => { - it('renders red tag when patient is HIV positive', async () => { - mockIsPatientHivPositive.mockResolvedValue(true); - await act(async () => { - render(); - }); - - expect(screen.getByText(/HIV Positive/i)).toBeInTheDocument(); - }); - }); - - it('does not render red tag when patient is not HIV positive', async () => { - await act(async () => { - (isPatientHivPositive as jest.Mock).mockResolvedValue(false); - render(); - }); - - expect(screen.queryByText('HIV Positive')).not.toBeInTheDocument(); - }); -}); diff --git a/packages/esm-commons-lib/src/components/banner-tags/patient-status-tag.component.tsx b/packages/esm-commons-lib/src/components/banner-tags/patient-status-tag.component.tsx index 8c6a6368b..c19f8bbc7 100644 --- a/packages/esm-commons-lib/src/components/banner-tags/patient-status-tag.component.tsx +++ b/packages/esm-commons-lib/src/components/banner-tags/patient-status-tag.component.tsx @@ -1,18 +1,24 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Tag } from '@carbon/react'; import { useTranslation } from 'react-i18next'; -import { isPatientHivPositive } from './patientHivStatus'; +import { finalHIVCodeConcept, finalPositiveHIVValueConcept } from '../../constants'; +import { usePatientsFinalHIVStatus } from './usePatientHivStatus'; export function PatientStatusBannerTag({ patientUuid }) { const { t } = useTranslation(); - const [hivPositive, setHivPositive] = useState(false); + const { isLoading, hivStatus, error } = usePatientsFinalHIVStatus( + patientUuid, + finalHIVCodeConcept, + finalPositiveHIVValueConcept, + ); - useEffect(() => { - isPatientHivPositive(patientUuid).then((result) => setHivPositive(result)); - }, [hivPositive, patientUuid]); + if (isLoading) { + return

{t('loading', 'Loading...')}

; + } - //TODO: Improve refresh time - // forceRerender(); + if (error) { + return

{t('error', 'Error...')}

; + } - return <>{hivPositive && {t('hivPositive', 'HIV Positive')}}; + return <>{hivStatus && {t('hivPositive', 'HIV Positive')}}; } diff --git a/packages/esm-commons-lib/src/components/banner-tags/patient-status-tag.test.tsx b/packages/esm-commons-lib/src/components/banner-tags/patient-status-tag.test.tsx new file mode 100644 index 000000000..03eba9aef --- /dev/null +++ b/packages/esm-commons-lib/src/components/banner-tags/patient-status-tag.test.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { PatientStatusBannerTag } from './patient-status-tag.component'; +import { usePatientsFinalHIVStatus } from './usePatientHivStatus'; + +const mockedUsePatientsFinalHIVStatus = jest.mocked(usePatientsFinalHIVStatus); + +jest.mock('./usePatientHivStatus', () => { + const originalModule = jest.requireActual('./usePatientHivStatus'); + + return { + ...originalModule, + usePatientsFinalHIVStatus: jest.fn().mockImplementation(() => ({ + hivStatus: true, + isLoading: false, + })), + }; +}); + +describe('PatientStatusBannerTag', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const samplePatientUuid = '703AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; + + it('renders red tag when patient is HIV positive', async () => { + render(); + expect(screen.getByText(/HIV Positive/i)).toBeInTheDocument(); + }); + + it('does not render red tag when patient is not HIV positive', async () => { + mockedUsePatientsFinalHIVStatus.mockReturnValue({ hivStatus: false, isLoading: false, error: null }); + render(); + expect(screen.queryByText('HIV Positive')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/esm-commons-lib/src/components/banner-tags/patientHivStatus.ts b/packages/esm-commons-lib/src/components/banner-tags/patientHivStatus.ts deleted file mode 100644 index c3df0b78e..000000000 --- a/packages/esm-commons-lib/src/components/banner-tags/patientHivStatus.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { openmrsFetch } from '@openmrs/esm-framework'; -import { fetchPatientsFinalHIVStatus, fetchPatientComputedConcept_HIV_Status } from '../../api.resource'; - -const fetchPatientHtsEncounters = (patientUuid: string) => { - const htsEncounterRepresentation = - 'custom:(uuid,encounterDatetime,location:(uuid,name),' + - 'encounterProviders:(uuid,provider:(uuid,name)),' + - 'obs:(uuid,obsDatetime,concept:(uuid,name:(uuid,name)),value:(uuid,name:(uuid,name))))'; - const htsRetrospectiveTypeUUID = '79c1f50f-f77d-42e2-ad2a-d29304dde2fe'; - const query = `encounterType=${htsRetrospectiveTypeUUID}&patient=${patientUuid}`; - - return openmrsFetch(`/ws/rest/v1/encounter?${query}&v=${htsEncounterRepresentation}`); -}; - -const isPatientHivPositive = async (patientUuid: string) => { - const hivTestResultConceptUUID = 'de18a5c1-c187-4698-9d75-258605ea07e8'; // Concept: Result of HIV test - - let isHivPositive = false; - let htsTestResult; - - await fetchPatientHtsEncounters(patientUuid).then((encounters) => { - encounters.data.results.forEach((encounter) => { - htsTestResult = encounter.obs.find((observation) => observation.concept.name.uuid === hivTestResultConceptUUID); - - if (htsTestResult && htsTestResult.value.name.uuid === 'ade5ba3f-3c7f-42b1-96d1-cfeb9b446980') { - isHivPositive = true; - } - }); - }); - - const hivFinalStatus = await fetchPatientsFinalHIVStatus(patientUuid); - - const computedConcept = await fetchPatientComputedConcept_HIV_Status(patientUuid); - - if (hivFinalStatus.toLowerCase().includes('positive') || computedConcept.toLowerCase().includes('positive')) { - isHivPositive = true; - } else { - isHivPositive = false; - } - - return isHivPositive; -}; - -export { isPatientHivPositive }; diff --git a/packages/esm-commons-lib/src/components/banner-tags/patientStatus.test.ts b/packages/esm-commons-lib/src/components/banner-tags/patientStatus.test.ts deleted file mode 100644 index 686e2f835..000000000 --- a/packages/esm-commons-lib/src/components/banner-tags/patientStatus.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @jest-environment jsdom - */ - -import { isPatientHivPositive } from './patientHivStatus'; - -describe('Patient HIV Status', () => { - it('Should return positive', () => { - let isHivPositive; - isPatientHivPositive('b280078a-c0ce-443b-9997-3c66c63ec2f8').then((result) => { - isHivPositive = result; - - expect(isHivPositive).toBe(true); - }); - }); -}); diff --git a/packages/esm-commons-lib/src/components/banner-tags/usePatientHivStatus.ts b/packages/esm-commons-lib/src/components/banner-tags/usePatientHivStatus.ts new file mode 100644 index 000000000..5cae13a30 --- /dev/null +++ b/packages/esm-commons-lib/src/components/banner-tags/usePatientHivStatus.ts @@ -0,0 +1,16 @@ +import { fhirBaseUrl, openmrsFetch } from '@openmrs/esm-framework'; +import useSWR from 'swr'; + +export function usePatientsFinalHIVStatus( + patientUuid: string, + finalHIVCodeConcept: string, + finalPositiveHIVValueConcept: string, +) { + const url = `${fhirBaseUrl}/Observation?code=${finalHIVCodeConcept}&value-concept=${finalPositiveHIVValueConcept}&patient=${patientUuid}&_sort=-date&_count=1`; + const { data, error, isLoading, mutate } = useSWR<{ data: any }, Error>(url, openmrsFetch); + + const hivStatusResult = data?.data?.entry[0].resource.valueCodeableConcept.coding[0].display; + const hivStatus = hivStatusResult.toLowerCase().includes('positive') ? true : false; + + return { hivStatus: hivStatus, isLoading: isLoading, error: error }; +} diff --git a/packages/esm-commons-lib/src/components/cohort-patient-list/helpers.tsx b/packages/esm-commons-lib/src/components/cohort-patient-list/helpers.tsx index 76394e274..4f0e6dbbd 100644 --- a/packages/esm-commons-lib/src/components/cohort-patient-list/helpers.tsx +++ b/packages/esm-commons-lib/src/components/cohort-patient-list/helpers.tsx @@ -5,9 +5,9 @@ import dayjs from 'dayjs'; import localizedFormat from 'dayjs/plugin/localizedFormat'; import relativeTime from 'dayjs/plugin/relativeTime'; import { AddPatientToListOverflowMenuItem } from '../modals/add-patient-to-list-modal.component'; -import { fetchPatientLastEncounter } from '../../api.resource'; import { launchForm } from '../../utils/ohri-forms-commons'; import { navigate, WorkspaceContainer } from '@openmrs/esm-framework'; +import { useLastEncounter } from '../../hooks/useLastEncounter'; interface PatientMetaConfig { location: { name: string }; @@ -38,23 +38,25 @@ export const LaunchableFormMenuItem = ({ }) => { const [actionText, setActionText] = useState(launchableForm.actionText); const [encounterUuid, setEncounterUuid] = useState(null); - const [isLoading, setIsLoading] = useState(false); const continueEncounterActionText = launchableForm.actionText || 'Continue encounter '; + const { lastEncounter, isLoading } = useLastEncounter(patientUuid, encounterType); useEffect(() => { if (launchableForm.editLatestEncounter && encounterType && !encounterUuid) { - setIsLoading(true); - fetchPatientLastEncounter(patientUuid, encounterType).then((latestEncounter) => { - if (latestEncounter) { - setActionText(continueEncounterActionText); - setEncounterUuid(latestEncounter.uuid); - } - setIsLoading(false); - }); - } else { - setIsLoading(false); + if (!isLoading && lastEncounter) { + setActionText(continueEncounterActionText); + setEncounterUuid(lastEncounter.uuid); + } } - }, [continueEncounterActionText, encounterType, encounterUuid, launchableForm.editLatestEncounter, patientUuid]); + }, [ + continueEncounterActionText, + encounterType, + encounterUuid, + launchableForm.editLatestEncounter, + patientUuid, + lastEncounter, + isLoading, + ]); return ( <> @@ -79,21 +81,15 @@ export const LaunchableFormMenuItem = ({ export const ViewSummaryMenuItem = ({ patientUuid, ViewSummary, encounterType }) => { const [actionText, setActionText] = useState(ViewSummary.actionText); const [encounterUuid, setEncounterUuid] = useState(null); - const [isLoading, setIsLoading] = useState(false); const viewSummaryActionText = ViewSummary.actionText || 'View Summary '; + const { lastEncounter, isLoading } = useLastEncounter(patientUuid, encounterType); useEffect(() => { if (ViewSummary.editLatestEncounter && encounterType && !encounterUuid) { - setIsLoading(true); - fetchPatientLastEncounter(patientUuid, encounterType).then((latestEncounter) => { - if (latestEncounter) { - setActionText(viewSummaryActionText); - setEncounterUuid(latestEncounter.uuid); - } - setIsLoading(false); - }); - } else { - setIsLoading(false); + if (!isLoading && lastEncounter) { + setActionText(viewSummaryActionText); + setEncounterUuid(lastEncounter.uuid); + } } }, [ViewSummary.editLatestEncounter, encounterType, encounterUuid, patientUuid, viewSummaryActionText]); @@ -114,24 +110,19 @@ export const ViewSummaryMenuItem = ({ patientUuid, ViewSummary, encounterType }) ); }; + export const ViewTptSummaryMenuItem = ({ patientUuid, ViewTptSummary, encounterType }) => { const [actionText, setActionText] = useState(ViewTptSummary.actionText); const [encounterUuid, setEncounterUuid] = useState(null); - const [isLoading, setIsLoading] = useState(false); const viewTptSummaryActionText = ViewTptSummary.actionText || 'View Summary '; + const { lastEncounter, isLoading } = useLastEncounter(patientUuid, encounterType); useEffect(() => { if (ViewTptSummary.editLatestEncounter && encounterType && !encounterUuid) { - setIsLoading(true); - fetchPatientLastEncounter(patientUuid, encounterType).then((latestEncounter) => { - if (latestEncounter) { - setActionText(viewTptSummaryActionText); - setEncounterUuid(latestEncounter.uuid); - } - setIsLoading(false); - }); - } else { - setIsLoading(false); + if (!isLoading && lastEncounter) { + setActionText(viewTptSummaryActionText); + setEncounterUuid(lastEncounter.uuid); + } } }, [ViewTptSummary.editLatestEncounter, encounterType, patientUuid, encounterUuid, viewTptSummaryActionText]); @@ -152,6 +143,7 @@ export const ViewTptSummaryMenuItem = ({ patientUuid, ViewTptSummary, encounterT ); }; + export function consolidatatePatientMeta(rawPatientMeta, form, config: PatientMetaConfig) { const { isDynamicCohort, diff --git a/packages/esm-commons-lib/src/index.ts b/packages/esm-commons-lib/src/index.ts index f4982ca46..b788a8bba 100644 --- a/packages/esm-commons-lib/src/index.ts +++ b/packages/esm-commons-lib/src/index.ts @@ -5,7 +5,7 @@ export * from './constants'; export * from './api.resource'; export * from './types'; export * from './components/banner-tags/patient-status-tag.component'; -export * from './components/banner-tags/patientHivStatus'; +export * from './components/banner-tags/usePatientHivStatus'; export * from './components/data-table/o-table.component'; export * from './components/empty-state/empty-data-illustration.component'; export * from './components/empty-state/empty-state-comingsoon.component';