Skip to content

Commit

Permalink
Refactor ward patient card (#1273)
Browse files Browse the repository at this point in the history
* change WardPatient type

* refactor ward patient workspace

* comments and tests

* make bed field non-optional
  • Loading branch information
chibongho authored Aug 9, 2024
1 parent ba561a5 commit 2ff5ef0
Show file tree
Hide file tree
Showing 18 changed files with 134 additions and 209 deletions.
34 changes: 28 additions & 6 deletions packages/esm-ward-app/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
Concept,
DefaultWorkspaceProps,
Location,
OpenmrsResource,
OpenmrsResourceStrict,
Expand All @@ -9,18 +10,39 @@ import type {
} from '@openmrs/esm-framework';
import type React from 'react';

export type WardPatientCardRow = React.FC<WardPatient>;
export type WardPatientCardElement = React.FC<WardPatient>;
export type WardPatientCard = React.FC<WardPatient>;

// WardPatient is a patient admitted to a ward, and/or in a bed on a ward
export type WardPatient = {
/**
* The patient and their current visit. These values are taken either
* from either the inpatientAdmission object, the inpatientRequest object
* or the admissionLocation object (which contains the bed)
*/
patient: Patient;
visit: Visit;
bed?: Bed;
admitted: boolean;
encounterAssigningToCurrentInpatientLocation: Encounter;
firstAdmissionOrTransferEncounter: Encounter;

/**
* the bed assigned to the patient. This object is only set if the patient
* has a bed assigned
*/
bed: Bed;

/**
* The admission detail. This object is only set if the patient has been
* admitted to the ward
*/
inpatientAdmission: InpatientAdmission;

/**
* The admission request. The object is only set if the patient has a
* pending admission / transfer request.
*/
inpatientRequest: InpatientRequest;
};
export interface WardPatientWorkspaceProps extends DefaultWorkspaceProps {
wardPatient: WardPatient;
}

// server-side types defined in openmrs-module-bedmanagement:

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import { type WardPatientCardExtensionProps } from '../ward-patient-card.component';
import WardPatientCodedObsTags from '../row-elements/ward-patient-coded-obs-tags';
import { useConfig } from '@openmrs/esm-framework';
import React from 'react';
import { type ColoredObsTagsCardRowConfigObject } from '../../config-schema-extension-colored-obs-tags';
import { type WardPatientCard } from '../../types';
import WardPatientCodedObsTags from '../row-elements/ward-patient-coded-obs-tags';

const ColoredObsTagsCardRowExtension: React.FC<WardPatientCardExtensionProps> = ({ patient, visit }) => {
const ColoredObsTagsCardRowExtension: WardPatientCard = ({ patient, visit }) => {
const config = useConfig<ColoredObsTagsCardRowConfigObject>();

return <WardPatientCodedObsTags config={config} patient={patient} visit={visit} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import React from 'react';
import { type Bed } from '../../types';
import styles from '../ward-patient-card.scss';
import { type WardPatientCardElement } from '../../types';

export interface WardPatientBedNumberProps {
bed: {
bedNumber: string;
};
}

const WardPatientBedNumber: React.FC<WardPatientBedNumberProps> = ({ bed }) => {
const WardPatientBedNumber: React.FC<{ bed: Bed }> = ({ bed }) => {
if (!bed) {
return <></>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { type Patient } from '@openmrs/esm-framework';
import { type TFunction } from 'i18next';
import React from 'react';
import { useTranslation } from 'react-i18next';

const WardPatientGender: React.FC<{ patient: Patient }> = ({ patient }) => {
const { t } = useTranslation();
const getGender = (gender: string): string => {
switch (gender) {
case 'M':
return t('male', 'Male');
case 'F':
return t('female', 'Female');
case 'O':
return t('other', 'Other');
case 'unknown':
return t('unknown', 'Unknown');
default:
return gender;
}
};
return <div>{getGender(patient?.person?.gender)}</div>;

return <div>{getGender(t, patient?.person?.gender)}</div>;
};

export const getGender = (t: TFunction, gender: string): string => {
switch (gender) {
case 'M':
return t('male', 'Male');
case 'F':
return t('female', 'Female');
case 'O':
return t('other', 'Other');
case 'unknown':
return t('unknown', 'Unknown');
default:
return gender;
}
};

export default WardPatientGender;
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
import { InlineNotification } from '@carbon/react';
import { useConfig } from '@openmrs/esm-framework';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { InlineNotification } from '@carbon/react';
import { type Patient, useConfig, type Visit } from '@openmrs/esm-framework';
import { type WardConfigObject } from '../config-schema';
import { type WardPatient } from '../types';
import WardPatientAge from './row-elements/ward-patient-age';
import WardPatientAddress from './row-elements/ward-patient-header-address';
import WardPatientIdentifier from './row-elements/ward-patient-identifier';
import WardPatientObs from './row-elements/ward-patient-obs';
import WardPatientTimeOnWard from './row-elements/ward-patient-time-on-ward';
import WardPatientTimeSinceAdmission from './row-elements/ward-patient-time-since-admission';
import WardPatientObs from './row-elements/ward-patient-obs';
import WardPatientIdentifier from './row-elements/ward-patient-identifier';
import WardPatientAddress from './row-elements/ward-patient-header-address';
import { type Encounter } from '../types';

export interface WardPatientCardElementProps {
export interface WardPatientCardElementProps extends WardPatient {
elementId: string;
patient: Patient;
visit: Visit;
encounterAssigningToCurrentInpatientLocation: Encounter;
firstAdmissionOrTransferEncounter: Encounter;
}

export const WardPatientCardElement: React.FC<WardPatientCardElementProps> = ({
elementId,
patient,
visit,
encounterAssigningToCurrentInpatientLocation,
firstAdmissionOrTransferEncounter,
inpatientAdmission,
}) => {
const { obsElementDefinitions, identifierElementDefinitions, addressElementDefinitions } =
useConfig<WardConfigObject>().wardPatientCards;
const { t } = useTranslation();
const { encounterAssigningToCurrentInpatientLocation, firstAdmissionOrTransferEncounter } = inpatientAdmission ?? {};

switch (elementId) {
case 'patient-age':
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,15 @@
import React, { useMemo } from 'react';
import { type Bed, type Encounter } from '../types';
import { WardPatientCardElement } from './ward-patient-card-element.component';
import { ExtensionSlot, getPatientName, launchWorkspace } from '@openmrs/esm-framework';
import classNames from 'classnames';
import React from 'react';
import { useCurrentWardCardConfig } from '../hooks/useCurrentWardCardConfig';
import styles from './ward-patient-card.scss';
import { ExtensionSlot, getPatientName, launchWorkspace, type Patient, type Visit } from '@openmrs/esm-framework';
import WardPatientName from './row-elements/ward-patient-name';
import { type WardPatientCard, type WardPatientWorkspaceProps } from '../types';
import WardPatientBedNumber from './row-elements/ward-patient-bed-number';
import classNames from 'classnames';
import { type WardPatientWorkspaceProps } from '../ward-patient-workspace/types';

export interface WardPatientCardProps {
patient: Patient;
visit: Visit;
bed?: Bed;
admitted: boolean;
encounterAssigningToCurrentInpatientLocation: Encounter;
firstAdmissionOrTransferEncounter: Encounter;
}

export interface WardPatientCardExtensionProps extends WardPatientCardProps {
patientUuid: string;
}
import WardPatientName from './row-elements/ward-patient-name';
import { WardPatientCardElement } from './ward-patient-card-element.component';
import styles from './ward-patient-card.scss';

const WardPatientCard: React.FC<WardPatientCardProps> = ({
patient,
visit,
bed,
admitted,
firstAdmissionOrTransferEncounter,
encounterAssigningToCurrentInpatientLocation,
}) => {
const WardPatientCard: WardPatientCard = (wardPatient) => {
const { patient, bed } = wardPatient;
const { id, headerRowElements, footerRowElements } = useCurrentWardCardConfig();

const headerExtensionSlotName =
Expand All @@ -38,14 +18,6 @@ const WardPatientCard: React.FC<WardPatientCardProps> = ({
const footerExtensionSlotName =
id == 'default' ? 'ward-patient-card-footer-slot' : `ward-patient-card-footer-${id}-slot`;

const extensionSlotState = useMemo(() => {
return {
patient,
visit,
bed,
};
}, [patient, visit, bed]);

return (
<div className={styles.wardPatientCard}>
<div className={classNames(styles.wardPatientCardRow, styles.wardPatientCardHeader)}>
Expand All @@ -55,43 +27,31 @@ const WardPatientCard: React.FC<WardPatientCardProps> = ({
<WardPatientCardElement
key={`ward-card-${patient.uuid}-header-${i}`}
elementId={elementId}
patient={patient}
visit={visit}
firstAdmissionOrTransferEncounter={firstAdmissionOrTransferEncounter}
encounterAssigningToCurrentInpatientLocation={encounterAssigningToCurrentInpatientLocation}
{...wardPatient}
/>
))}
<ExtensionSlot name={headerExtensionSlotName} state={extensionSlotState} />
<ExtensionSlot name={headerExtensionSlotName} state={wardPatient} />
</div>
<ExtensionSlot
name={rowsExtensionSlotName}
state={extensionSlotState}
state={wardPatient}
className={classNames(styles.wardPatientCardRow, styles.wardPatientCardExtensionSlot)}
/>
<div className={styles.wardPatientCardRow}>
{footerRowElements.map((elementId, i) => (
<WardPatientCardElement
key={`ward-card-${patient.uuid}-footer-${i}`}
elementId={elementId}
patient={patient}
visit={visit}
firstAdmissionOrTransferEncounter={firstAdmissionOrTransferEncounter}
encounterAssigningToCurrentInpatientLocation={encounterAssigningToCurrentInpatientLocation}
{...wardPatient}
/>
))}
<ExtensionSlot name={footerExtensionSlotName} state={extensionSlotState} />
<ExtensionSlot name={footerExtensionSlotName} state={wardPatient} />
</div>
<button
className={styles.wardPatientCardButton}
onClick={() => {
launchWorkspace<WardPatientWorkspaceProps>('ward-patient-workspace', {
patientUuid: patient.uuid,
patient,
visit,
bed,
admitted,
firstAdmissionOrTransferEncounter,
encounterAssigningToCurrentInpatientLocation,
wardPatient,
});
}}>
{/* Name will not be displayed; just there for a11y */}
Expand Down
6 changes: 0 additions & 6 deletions packages/esm-ward-app/src/ward-patient-workspace/types.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,49 +1,30 @@
import React, { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { InlineLoading, InlineNotification } from '@carbon/react';
import { age, attach, ExtensionSlot, getPatientName, usePatient } from '@openmrs/esm-framework';
import { age, attach, ExtensionSlot, type Patient } from '@openmrs/esm-framework';
import React, { useEffect } from 'react';
import { type WardPatientWorkspaceProps } from '../types';
import styles from './ward-patient.style.scss';
import { type WardPatientWorkspaceProps } from './types';
import { useTranslation } from 'react-i18next';
import { getGender } from '../ward-patient-card/row-elements/ward-patient-gender.component';

attach('ward-patient-workspace-header-slot', 'patient-vitals-info');

export default function WardPatientWorkspace({ setTitle, patientUuid }: WardPatientWorkspaceProps) {
const { t } = useTranslation();
const { patient, isLoading, error } = usePatient(patientUuid);

export default function WardPatientWorkspace({ setTitle, wardPatient: { patient } }: WardPatientWorkspaceProps) {
useEffect(() => {
if (isLoading) {
setTitle(t('wardPatientWorkspaceTitle', 'Ward Patient'), <InlineLoading />);
} else if (patient) {
setTitle(getPatientName(patient), <PatientWorkspaceTitle patient={patient} />);
} else if (error) {
setTitle(t('wardPatientWorkspaceTitle', 'Ward Patient'));
}
}, [patient]);
setTitle(patient.person.display, <PatientWorkspaceTitle patient={patient} />);
}, []);

return (
<div className={styles.workspaceContainer}>
{isLoading ? (
<InlineLoading />
) : patient ? (
<WardPatientWorkspaceView patient={patient} />
) : error ? (
<InlineNotification>{error.message}</InlineNotification>
) : (
<InlineNotification>
{t('failedToLoadPatientWorkspace', 'Ward patient workspace has failed to load.')}
</InlineNotification>
)}
<WardPatientWorkspaceView patient={patient} />
</div>
);
}

interface WardPatientWorkspaceViewProps {
patient: fhir.Patient;
patient: Patient;
}

function WardPatientWorkspaceView({ patient }: WardPatientWorkspaceViewProps) {
const extensionSlotState = useMemo(() => ({ patient, patientUuid: patient.id }), [patient]);
const WardPatientWorkspaceView: React.FC<WardPatientWorkspaceViewProps> = ({ patient }) => {
const extensionSlotState = { patient, patientUuid: patient.uuid };

return (
<>
Expand All @@ -55,14 +36,16 @@ function WardPatientWorkspaceView({ patient }: WardPatientWorkspaceViewProps) {
</div>
</>
);
}
};

const PatientWorkspaceTitle: React.FC<WardPatientWorkspaceViewProps> = ({ patient }) => {
const { t } = useTranslation();

function PatientWorkspaceTitle({ patient }: { patient: fhir.Patient }) {
return (
<>
<div>{getPatientName(patient)} &nbsp;</div>
<div className={styles.headerPatientDetail}>&middot; &nbsp; {patient.gender}</div>
<div className={styles.headerPatientDetail}>&middot; &nbsp; {age(patient.birthDate)}</div>
<div>{patient.person.display} &nbsp;</div>
<div className={styles.headerPatientDetail}>&middot; &nbsp; {getGender(t, patient.person?.gender)}</div>
<div className={styles.headerPatientDetail}>&middot; &nbsp; {age(patient.person?.birthdate)}</div>
</>
);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const AdmissionRequestsBar = () => {
<div className={styles.admissionRequestsContainer}>
<Movement className={styles.movementIcon} size="24" />
<span className={styles.content}>
{t('admissionRequestsCount', '{{count}} admission requests', {
{t('admissionRequestsCount', '{{count}} admission request(s)', {
count: inpatientRequests.length,
})}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ describe('Admission Requests Button', () => {
it('there should be one admission request', () => {
renderWithSwr(<AdmissionRequestsBar />);

expect(screen.getByText('1 admission requests')).toBeInTheDocument();
expect(screen.getByText('1 admission request(s)')).toBeInTheDocument();
});
});
Loading

0 comments on commit 2ff5ef0

Please sign in to comment.