Skip to content

Commit

Permalink
(fix) O3-4016 Ward App - add way to unassign bed from patient without…
Browse files Browse the repository at this point in the history
… transferring / discharging (#1350)
  • Loading branch information
chibongho authored Oct 24, 2024
1 parent c79b31f commit 8761fb7
Show file tree
Hide file tree
Showing 18 changed files with 548 additions and 413 deletions.
4 changes: 2 additions & 2 deletions __mocks__/wards.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const mockAdmissionLocation: AdmissionLocationFetchResponse = {
rowNumber: 1,
columnNumber: 2,
bedNumber: 'bed2',
bedId: 1,
bedId: 2,
bedUuid: '0000-bed2',
status: 'AVAILABLE',
bedType: mockBedType,
Expand Down Expand Up @@ -79,4 +79,4 @@ export const mockInpatientAdmissions: InpatientAdmission[] = [
currentInpatientRequest: null,
firstAdmissionOrTransferEncounter: null,
},
];
];
21 changes: 16 additions & 5 deletions packages/esm-ward-app/src/beds/ward-bed.test.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
import { configSchema, type WardConfigObject } from '../config-schema';
import { render, screen } from '@testing-library/react';
import React from 'react';
import {
mockAdmissionLocation,
mockLocationInpatientWard,
mockPatientAlice,
mockPatientBrian,
} from '../../../../__mocks__';
import { bedLayoutToBed, filterBeds } from '../ward-view/ward-view.resource';
import { configSchema, type WardConfigObject } from '../config-schema';
import { useObs } from '../hooks/useObs';
import useWardLocation from '../hooks/useWardLocation';
import WardBed from './ward-bed.component';
import { type WardPatient } from '../types';
import DefaultWardPatientCard from '../ward-view/default-ward/default-ward-patient-card.component';
import { bedLayoutToBed, filterBeds } from '../ward-view/ward-view.resource';
import WardBed from './ward-bed.component';

const defaultConfig: WardConfigObject = getDefaultsFromConfigSchema(configSchema);

jest.mocked(useConfig).mockReturnValue(defaultConfig);
jest.mock('../hooks/useObs', () => ({
useObs: jest.fn(),
}));
jest.mock('../ward-patient-card/row-elements/ward-patient-obs.resource', () => ({
useConceptToTagColorMap: jest.fn(),
}));

const mockBedLayouts = filterBeds(mockAdmissionLocation);

jest.mock('../hooks/useWardLocation', () => jest.fn());
//@ts-ignore
jest.mocked(useObs).mockReturnValue({
data: [],
});

const mockedUseWardLocation = useWardLocation as jest.Mock;
mockedUseWardLocation.mockReturnValue({
Expand Down
5 changes: 5 additions & 0 deletions packages/esm-ward-app/src/hooks/useWardPatientGrouping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,10 @@ export function useWardPatientGrouping() {
inpatientRequestResponse,
isLoading:
admissionLocationResponse.isLoading || inpatientAdmissionResponse.isLoading || inpatientRequestResponse.isLoading,
mutate() {
admissionLocationResponse?.mutate();
inpatientAdmissionResponse?.mutate();
inpatientRequestResponse?.mutate();
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,25 @@ import { mockInpatientAdmissionAlice } from '../../../../../__mocks__/inpatient-
import { mockWardBeds } from '../../../../../__mocks__/wardBeds.mock';
import { mockWardViewContext } from '../../../mock';
import { configSchema, type WardConfigObject } from '../../config-schema';
import { useObs } from '../../hooks/useObs';
import { type WardPatient, type WardViewContext } from '../../types';
import MaternalWardPatientCard from './maternal-ward-patient-card.component';

jest.mocked(useAppContext<WardViewContext>).mockReturnValue(mockWardViewContext);
jest.mock('../../hooks/useObs', () => ({
useObs: jest.fn(),
}));
jest.mock('../../ward-patient-card/row-elements/ward-patient-obs.resource', () => ({
useConceptToTagColorMap: jest.fn(),
}));

const defaultConfig: WardConfigObject = getDefaultsFromConfigSchema(configSchema);

jest.mocked(useConfig).mockReturnValue(defaultConfig);
//@ts-ignore
jest.mocked(useObs).mockReturnValue({
data: [],
});

describe('MaternalWardPatientCard', () => {
it('renders a patient with no child', () => {
Expand Down
8 changes: 8 additions & 0 deletions packages/esm-ward-app/src/ward-view/ward-view.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useParams } from 'react-router-dom';
import { renderWithSwr } from 'tools';
import { mockWardPatientGroupDetails, mockWardViewContext } from '../../mock';
import { configSchema } from '../config-schema';
import { useObs } from '../hooks/useObs';
import useWardLocation from '../hooks/useWardLocation';
import { type WardViewContext } from '../types';
import DefaultWardView from './default-ward/default-ward-view.component';
Expand All @@ -30,6 +31,9 @@ jest.mock('../hooks/useWardLocation', () =>
invalidLocation: false,
}),
);
jest.mock('../hooks/useObs', () => ({
useObs: jest.fn(),
}));

const mockUseWardLocation = jest.mocked(useWardLocation);

Expand All @@ -40,6 +44,10 @@ jest.mock('react-router-dom', () => ({
const mockUseParams = useParams as jest.Mock;

jest.mocked(useAppContext<WardViewContext>).mockReturnValue(mockWardViewContext);
//@ts-ignore
jest.mocked(useObs).mockReturnValue({
data: [],
});

const intersectionObserverMock = () => ({
observe: () => null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import { Button } from '@carbon/react';
import { ArrowRightIcon, launchWorkspace, useAppContext, useLayoutType } from '@openmrs/esm-framework';
import React, { useCallback } from 'react';
import {
ArrowRightIcon,
launchWorkspace,
showSnackbar,
useAppContext,
useFeatureFlag,
useLayoutType,
} from '@openmrs/esm-framework';
import React, { useCallback, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import type { WardPatientWorkspaceProps, WardPatientCardType, WardViewContext } from '../../types';
import useWardLocation from '../../hooks/useWardLocation';
import type { WardPatientCardType, WardPatientWorkspaceProps, WardViewContext } from '../../types';
import { useAdmitPatient } from '../../ward.resource';
import { AdmissionRequestsWorkspaceContext } from '../admission-request-workspace/admission-requests.workspace';
import type { AdmitPatientFormWorkspaceProps } from '../admit-patient-form-workspace/types';
import styles from './admission-request-card.scss';

const AdmissionRequestCardActions: WardPatientCardType = (wardPatient) => {
const { patient, inpatientRequest } = wardPatient;
const { dispositionType } = inpatientRequest;
const { t } = useTranslation();
const { location } = useWardLocation();
const responsiveSize = useLayoutType() === 'tablet' ? 'lg' : 'md';
const {WardPatientHeader} = useAppContext<WardViewContext>('ward-view-context') ?? {};
const { WardPatientHeader, wardPatientGroupDetails } = useAppContext<WardViewContext>('ward-view-context') ?? {};
const { admitPatient, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useAdmitPatient();

const launchPatientAdmissionForm = useCallback(
() => launchWorkspace<AdmitPatientFormWorkspaceProps>('admit-patient-form-workspace', { patient, dispositionType }),
Expand All @@ -21,16 +33,57 @@ const AdmissionRequestCardActions: WardPatientCardType = (wardPatient) => {
const launchPatientTransferForm = useCallback(() => {
launchWorkspace<WardPatientWorkspaceProps>('patient-transfer-request-workspace', {
wardPatient,
WardPatientHeader
WardPatientHeader,
});
}, [wardPatient, WardPatientHeader]);
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
const { closeWorkspaceWithSavedChanges } = useContext(AdmissionRequestsWorkspaceContext);

// If bed management module is installed, open the next form
// for bed selection. If not, admit patient directly
const onAdmit = () => {
if (isBedManagementModuleInstalled) {
launchPatientAdmissionForm();
} else {
admitPatient(patient, dispositionType)
.then(
(response) => {
if (response && response?.ok) {
showSnackbar({
kind: 'success',
title: t('patientAdmittedSuccessfully', 'Patient admitted successfully'),
subtitle: t('patientAdmittedWoBed', 'Patient admitted successfully to {{location}}', {
location: location?.display,
}),
});
}
},
(err: Error) => {
showSnackbar({
kind: 'error',
title: t('errorCreatingEncounter', 'Failed to admit patient'),
subtitle: err.message,
});
},
)
.finally(() => {
wardPatientGroupDetails?.mutate?.();
closeWorkspaceWithSavedChanges();
});
}
};

return (
<div className={styles.admissionRequestActionBar}>
<Button kind="ghost" size={responsiveSize} onClick={launchPatientTransferForm}>
{t('transferElsewhere', 'Transfer elsewhere')}
</Button>
<Button kind="ghost" renderIcon={ArrowRightIcon} size={responsiveSize} onClick={launchPatientAdmissionForm}>
<Button
kind="ghost"
renderIcon={ArrowRightIcon}
size={responsiveSize}
disabled={isLoadingEmrConfiguration || errorFetchingEmrConfiguration}
onClick={onAdmit}>
{t('admitPatient', 'Admit patient')}
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ import { screen } from '@testing-library/react';
import React from 'react';
import { renderWithSwr } from '../../../../../tools';
import { mockWardViewContext } from '../../../mock';
import useEmrConfiguration from '../../hooks/useEmrConfiguration';
import { type WardViewContext } from '../../types';
import DefaultWardPendingPatients from '../../ward-view/default-ward/default-ward-pending-patients.component';
import AdmissionRequestsWorkspace, { type AdmissionRequestsWorkspaceProps } from './admission-requests.workspace';

jest.mocked(useAppContext<WardViewContext>).mockReturnValue(mockWardViewContext);

jest.mock('../../hooks/useEmrConfiguration', () => jest.fn());
const mockedUseEmrConfiguration = jest.mocked(useEmrConfiguration);

const workspaceProps: AdmissionRequestsWorkspaceProps = {
closeWorkspace: jest.fn(),
promptBeforeClosing: jest.fn(),
Expand All @@ -18,9 +22,49 @@ const workspaceProps: AdmissionRequestsWorkspaceProps = {
};

describe('Admission Requests Workspace', () => {
it('should render a admission request card', () => {
beforeEach(() => {
mockedUseEmrConfiguration.mockReturnValue({
isLoadingEmrConfiguration: false,
errorFetchingEmrConfiguration: null,
// @ts-ignore - we only need these keys for now
emrConfiguration: {
admissionEncounterType: {
uuid: 'admission-encounter-type-uuid',
display: 'Admission Encounter',
},
transferWithinHospitalEncounterType: {
uuid: 'transfer-within-hospital-encounter-type-uuid',
display: 'Transfer Within Hospital Encounter Type',
},
clinicianEncounterRole: {
uuid: 'clinician-encounter-role-uuid',
},
},
mutateEmrConfiguration: jest.fn(),
});
});

it('should render an admission request card', () => {
renderWithSwr(<AdmissionRequestsWorkspace {...workspaceProps} />);
const alice = mockWardViewContext.wardPatientGroupDetails.inpatientRequestResponse.inpatientRequests[0].patient;
expect(screen.getByText(alice.person?.preferredName?.display as string)).toBeInTheDocument();
});

it('should render an admission request card with disabled "admit patient" button when emr config fails to load', () => {
mockedUseEmrConfiguration.mockReturnValue({
isLoadingEmrConfiguration: false,
errorFetchingEmrConfiguration: true,
emrConfiguration: null,
mutateEmrConfiguration: jest.fn(),
});

renderWithSwr(<AdmissionRequestsWorkspace {...workspaceProps} />);
expect(screen.getByText("Some parts of the form didn't load")).toBeInTheDocument();
expect(
screen.getByText(
'Fetching EMR configuration failed. Try refreshing the page or contact your system administrator.',
),
).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Admit patient/i })).toBeDisabled();
});
});
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { Search } from '@carbon/react';
import { InlineNotification, Search } from '@carbon/react';
import { type DefaultWorkspaceProps } from '@openmrs/esm-framework';
import React, { type ReactNode } from 'react';
import React, { createContext, type ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import useEmrConfiguration from '../../hooks/useEmrConfiguration';
import styles from './admission-requests-workspace.scss';

export interface AdmissionRequestsWorkspaceProps extends DefaultWorkspaceProps {
wardPendingPatients: ReactNode;
}
const AdmissionRequestsWorkspace: React.FC<AdmissionRequestsWorkspaceProps> = ({ wardPendingPatients }) => {

export const AdmissionRequestsWorkspaceContext = createContext<AdmissionRequestsWorkspaceProps>(null);

const AdmissionRequestsWorkspace: React.FC<AdmissionRequestsWorkspaceProps> = (props) => {
const { wardPendingPatients } = props;
const { t } = useTranslation();
const [searchTerm, setSearchTerm] = React.useState('');
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
};
const { errorFetchingEmrConfiguration } = useEmrConfiguration();

return (
<div className={styles.admissionRequestsWorkspaceContainer}>
Expand All @@ -24,8 +30,23 @@ const AdmissionRequestsWorkspace: React.FC<AdmissionRequestsWorkspaceProps> = ({
placeholder={t('searchForPatient', 'Search for a patient')}
disabled
/>

<div className={styles.content}>{wardPendingPatients}</div>
{errorFetchingEmrConfiguration && (
<div className={styles.formError}>
<InlineNotification
kind="error"
title={t('somePartsOfTheFormDidntLoad', "Some parts of the form didn't load")}
subtitle={t(
'fetchingEmrConfigurationFailed',
'Fetching EMR configuration failed. Try refreshing the page or contact your system administrator.',
)}
lowContrast
hideCloseButton
/>
</div>
)}
<AdmissionRequestsWorkspaceContext.Provider value={props}>
<div className={styles.content}>{wardPendingPatients}</div>
</AdmissionRequestsWorkspaceContext.Provider>
</div>
);
};
Expand Down
Loading

0 comments on commit 8761fb7

Please sign in to comment.