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

(feat) O3-3211: Ward App - display metrics for admission status and bed occupancy #1213

Merged
merged 13 commits into from
Aug 30, 2024
31 changes: 31 additions & 0 deletions __mocks__/wardBeds.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { mockBedType } from './wards.mock';

export const mockWardBeds = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this relate to the existing mockAdmissionLocation mock in the wards.mock file? Can we just use that other existing mock?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of properties such as id and uuid are named differently in mockAdmissionLocation for beds with some additional properties,so i just created a separate mock for beds.

{
id: 1,
uuid: '0000-bed1',
bedNumber: 'bed1',
bedType: mockBedType,
row: 1,
column: 2,
status: 'OCCUPIED' as const,
},
{
id: 2,
uuid: '0000-bed2',
bedNumber: 'bed2',
bedType: mockBedType,
row: 1,
column: 2,
status: 'AVAILABLE' as const,
},
{
id: 1,
uuid: '0000-bed3',
bedNumber: 'bed3',
bedType: mockBedType,
row: 1,
column: 3,
status: 'AVAILABLE' as const,
},
];
2 changes: 1 addition & 1 deletion __mocks__/wards.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type AdmissionLocationFetchResponse, type BedType } from '../packages/e
import { mockLocationInpatientWard } from './locations.mock';
import { mockPatientAlice, mockPatientBrian } from './patient.mock';

const mockBedType: BedType = {
export const mockBedType: BedType = {
uuid: '0000-bed-type',
name: 'mockBedType',
displayName: 'Mock Bed Type',
Expand Down
35 changes: 35 additions & 0 deletions packages/esm-ward-app/src/hooks/useWardPatientGrouping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useMemo } from 'react';
import { createAndGetWardPatientGrouping, getInpatientAdmissionsUuidMap } from '../ward-view/ward-view.resource';
import { useAdmissionLocation } from './useAdmissionLocation';
import { useInpatientAdmission } from './useInpatientAdmission';

export function useWardPatientGrouping() {
const admissionLocationResponse = useAdmissionLocation();
const inpatientAdmissionResponse = useInpatientAdmission();

const { inpatientAdmissions } = inpatientAdmissionResponse;
const { admissionLocation } = admissionLocationResponse;
const inpatientAdmissionsByPatientUuid = useMemo(() => {
return getInpatientAdmissionsUuidMap(inpatientAdmissions);
}, [inpatientAdmissions]);

const {
wardAdmittedPatientsWithBed,
wardUnadmittedPatientsWithBed,
wardPatientPendingCount,
bedLayouts,
wardUnassignedPatientsList,
} = useMemo(() => {
return createAndGetWardPatientGrouping(inpatientAdmissions, admissionLocation, inpatientAdmissionsByPatientUuid);
}, [inpatientAdmissionsByPatientUuid, admissionLocation, inpatientAdmissions]);

return {
wardAdmittedPatientsWithBed,
wardUnadmittedPatientsWithBed,
wardUnassignedPatientsList,
wardPatientPendingCount,
admissionLocationResponse,
inpatientAdmissionResponse,
bedLayouts,
};
}
9 changes: 9 additions & 0 deletions packages/esm-ward-app/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
Visit,
} from '@openmrs/esm-framework';
import type React from 'react';
import type { useWardPatientGrouping } from '../hooks/useWardPatientGrouping';

export type WardPatientCard = React.FC<WardPatient>;

Expand Down Expand Up @@ -189,6 +190,12 @@ export interface EncounterRole extends OpenmrsResourceStrict {
retired?: boolean;
}

export interface WardMetrics {
patients: string;
freeBeds: string;
capacity: string;
}

export interface EncounterPayload {
encounterDatetime?: string;
encounterType: string;
Expand All @@ -206,3 +213,5 @@ export interface ObsPayload {
value?: string;
groupMembers?: Array<ObsPayload>;
}

export type WardPatientGroupDetails = ReturnType<typeof useWardPatientGrouping>;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
align-items: center;
padding: layout.$spacing-02 0 layout.$spacing-02 layout.$spacing-04;
background-color: #393939;

margin-left: layout.$spacing-03;
& > button {
color: #78a9ff;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import styles from './ward-metric.scss';
import { SkeletonPlaceholder } from '@carbon/react';

interface WardMetricProps {
metricName: string;
metricValue: string;
isLoading: boolean;
}
const WardMetric: React.FC<WardMetricProps> = ({ metricName, metricValue, isLoading }) => {
return (
<div className={styles.metric}>
<span className={styles.metricName}>{metricName}</span>
{isLoading ? (
<SkeletonPlaceholder className={styles.skeleton} />
) : (
<span className={styles.metricValue}>{metricValue}</span>
)}
</div>
);
};

export default WardMetric;
25 changes: 25 additions & 0 deletions packages/esm-ward-app/src/ward-view-header/ward-metric.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@use '@carbon/styles/scss/spacing';
@use '@carbon/type';
@import '~@openmrs/esm-styleguide/src/vars';

.metric {
margin-left: spacing.$spacing-05;
display: flex;
align-items: end;
gap: 5px;
}

.metricName {
@include type.type-style('helper-text-01');
color: $color-gray-70;
}

.metricValue {
@include type.type-style('heading-03');
line-height: revert;
}

.skeleton {
height: 15px;
width: 15px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import styles from './ward-metrics.scss';
import { useBeds } from '../hooks/useBeds';
import { showNotification, useAppContext, useFeatureFlag } from '@openmrs/esm-framework';
import { useTranslation } from 'react-i18next';
import { getWardMetrics } from '../ward-view/ward-view.resource';
import WardMetric from './ward-metric.component';
import type { WardPatientGroupDetails } from '../types';
import useWardLocation from '../hooks/useWardLocation';

const wardMetrics = [
{ name: 'Patients', key: 'patients' },
{ name: 'Free beds', key: 'freeBeds' },
{ name: 'Capacity', key: 'capacity' },
];

const WardMetrics = () => {
const { location } = useWardLocation();
const { beds, isLoading, error } = useBeds({ locationUuid: location.uuid });
const { t } = useTranslation();
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
const wardPatientGroup = useAppContext<WardPatientGroupDetails>('ward-patients-group');

if (error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will depend on how the useBeds() hook works, but per the ticket description, we do not want to fail if the bedmanagment module is not installed, so this needs to be aware of the bed management feature flag. @chibongho should be able to advise here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mseaton , if bedmanagement module,is not installed,this component is not rendered and there will be a empty page.Is this the desired behaviour or should the metrics be displayed having "--" as value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mseaton . Will look into it.

showNotification({
kind: 'error',
title: t('errorLoadingBedDetails', 'Error Loading Bed Details'),
description: error.message,
});
}
const wardMetricValues = getWardMetrics(beds);
return (
<div className={styles.metricsContainer}>
{isBedManagementModuleInstalled ? (
wardMetrics.map((wardMetric) => {
return (
<WardMetric
metricName={wardMetric.name}
metricValue={wardMetricValues[wardMetric.key]}
isLoading={!!isLoading}
key={wardMetric.key}
/>
);
})
) : (
<WardMetric metricName={'Patients'} metricValue={'--'} isLoading={false} key={'patients'} />
)}
{isBedManagementModuleInstalled && (
<WardMetric
metricName="Pending out"
metricValue={error ? '--' : wardPatientGroup?.wardPatientPendingCount.toString() ?? '--'}
isLoading={!wardPatientGroup}
key="pending"
/>
)}
</div>
);
};

export default WardMetrics;
8 changes: 8 additions & 0 deletions packages/esm-ward-app/src/ward-view-header/ward-metrics.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@use '@carbon/styles/scss/spacing';
@import '~@openmrs/esm-styleguide/src/vars';

.metricsContainer {
display: flex;
align-items: end;
margin-left: auto;
}
77 changes: 77 additions & 0 deletions packages/esm-ward-app/src/ward-view-header/ward-metrics.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react';
import WardMetrics from './ward-metrics.component';
import { renderWithSwr } from '../../../../tools/test-utils';
import { useBeds } from '../hooks/useBeds';
import { mockWardBeds } from '../../../../__mocks__/wardBeds.mock';
import { getWardMetrics } from '../ward-view/ward-view.resource';
import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
import { mockAdmissionLocation, mockInpatientAdmissions } from '__mocks__';
import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
import useWardLocation from '../hooks/useWardLocation';
import { screen } from '@testing-library/react';

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useParams: jest.fn().mockReturnValue({}),
}));

jest.mock('../hooks/useWardLocation', () =>
jest.fn().mockReturnValue({
location: { uuid: 'abcd', display: 'mock location' },
isLoadingLocation: false,
errorFetchingLocation: null,
invalidLocation: false,
}),
);

const mockUseWardLocation = jest.mocked(useWardLocation);

jest.mock('../hooks/useBeds', () => ({
useBeds: jest.fn(),
}));

jest.mock('../hooks/useAdmissionLocation', () => ({
useAdmissionLocation: jest.fn(),
}));
jest.mock('../hooks/useInpatientAdmission', () => ({
useInpatientAdmission: jest.fn(),
}));

jest.mocked(useBeds).mockReturnValue({
error: undefined,
mutate: jest.fn(),
isValidating: false,
isLoading: false,
beds: mockWardBeds,
});

jest.mocked(useAdmissionLocation).mockReturnValue({
error: undefined,
mutate: jest.fn(),
isValidating: false,
isLoading: false,
admissionLocation: mockAdmissionLocation,
});
jest.mocked(useInpatientAdmission).mockReturnValue({
error: undefined,
mutate: jest.fn(),
isValidating: false,
isLoading: false,
inpatientAdmissions: mockInpatientAdmissions,
});

describe('Ward Metrics', () => {
it('Should display metrics of in the ward ', () => {
mockUseWardLocation.mockReturnValueOnce({
location: null,
isLoadingLocation: false,
errorFetchingLocation: null,
invalidLocation: true,
});
const bedMetrics = getWardMetrics(mockWardBeds);
renderWithSwr(<WardMetrics />);
for (let [key, value] of Object.entries(bedMetrics)) {
expect(screen.getByText(value)).toBeInTheDocument();
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import React from 'react';
import styles from './ward-view-header.scss';
import AdmissionRequestsBar from './admission-requests-bar.component';
import useWardLocation from '../hooks/useWardLocation';
import WardMetrics from './ward-metrics.component';

interface WardViewHeaderProps {}

const WardViewHeader: React.FC<WardViewHeaderProps> = () => {
const { location } = useWardLocation();

return (
<div className={styles.wardViewHeader}>
<h4>{location?.display}</h4>
<WardMetrics />
<AdmissionRequestsBar />
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@
margin: layout.$spacing-05 0;
display: flex;
align-items: center;
justify-content: space-between;
}
Loading
Loading