@kenyaemr/esm-ward-app 7.0.3-pre.89 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +24 -16
- package/dist/130.js +1 -1
- package/dist/130.js.map +1 -1
- package/dist/169.js +1 -0
- package/dist/169.js.map +1 -0
- package/dist/269.js +1 -0
- package/dist/269.js.map +1 -0
- package/dist/346.js +1 -0
- package/dist/346.js.map +1 -0
- package/dist/348.js +1 -0
- package/dist/348.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/501.js +1 -0
- package/dist/501.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/577.js +1 -0
- package/dist/577.js.map +1 -0
- package/dist/659.js +1 -0
- package/dist/659.js.map +1 -0
- package/dist/749.js +1 -0
- package/dist/749.js.map +1 -0
- package/dist/76.js +1 -0
- package/dist/76.js.map +1 -0
- package/dist/767.js +1 -0
- package/dist/767.js.map +1 -0
- package/dist/793.js +2 -0
- package/dist/793.js.map +1 -0
- package/dist/803.js +1 -0
- package/dist/803.js.map +1 -0
- package/dist/940.js +1 -0
- package/dist/940.js.map +1 -0
- package/dist/960.js +1 -0
- package/dist/960.js.map +1 -0
- package/dist/kenyaemr-esm-ward-app.js +1 -1
- package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +330 -42
- package/dist/kenyaemr-esm-ward-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +3 -4
- package/src/action-menu-buttons/transfer-workspace-siderail.component.tsx +27 -0
- package/src/beds/empty-bed.component.tsx +1 -1
- package/src/beds/empty-bed.scss +6 -6
- package/src/beds/occupied-bed.component.tsx +5 -5
- package/src/beds/occupied-bed.scss +2 -3
- package/src/beds/occupied-bed.test.tsx +37 -21
- package/src/beds/unassigned-patient.component.tsx +20 -0
- package/src/beds/unassigned-patient.scss +6 -0
- package/src/config-schema-admission-request-note.ts +9 -0
- package/src/config-schema-extension-colored-obs-tags.ts +91 -0
- package/src/config-schema.ts +165 -231
- package/src/createDashboardLink.component.tsx +42 -0
- package/src/hooks/useAdmissionLocation.ts +12 -7
- package/src/hooks/useCurrentWardCardConfig.ts +32 -0
- package/src/hooks/useEmrConfiguration.ts +112 -0
- package/src/hooks/useInpatientAdmission.ts +28 -0
- package/src/hooks/useInpatientRequest.ts +39 -9
- package/src/hooks/useLocation.test.ts +38 -0
- package/src/hooks/useLocation.ts +9 -0
- package/src/hooks/useLocations.ts +54 -0
- package/src/hooks/useMostRecentObs.ts +27 -0
- package/src/hooks/useRestPatient.ts +18 -0
- package/src/hooks/useWardLocation.test.ts +108 -0
- package/src/hooks/useWardLocation.ts +26 -0
- package/src/index.ts +71 -4
- package/src/location-selector/location-selector.component.tsx +118 -0
- package/src/location-selector/location-selector.scss +48 -0
- package/src/root.component.tsx +2 -1
- package/src/routes.json +79 -12
- package/src/types/index.ts +87 -46
- package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +27 -0
- package/src/ward-patient-card/card-rows/colored-obs-tags-card-row.extension.tsx +13 -0
- package/src/ward-patient-card/row-elements/ward-patient-age.tsx +7 -13
- package/src/ward-patient-card/row-elements/ward-patient-bed-number.tsx +2 -2
- package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +51 -50
- package/src/ward-patient-card/row-elements/ward-patient-gender.component.tsx +27 -0
- package/src/ward-patient-card/row-elements/ward-patient-header-address.tsx +16 -15
- package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +53 -0
- package/src/ward-patient-card/row-elements/ward-patient-name.tsx +7 -7
- package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +4 -4
- package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +45 -44
- package/src/ward-patient-card/row-elements/ward-patient-time-on-ward.tsx +22 -0
- package/src/ward-patient-card/row-elements/ward-patient-time-since-admission.tsx +22 -0
- package/src/ward-patient-card/ward-patient-card-element.component.tsx +65 -0
- package/src/ward-patient-card/ward-patient-card.component.tsx +64 -0
- package/src/ward-patient-card/ward-patient-card.scss +61 -12
- package/src/ward-patient-workspace/ward-patient-action-button.extension.tsx +18 -0
- package/src/ward-patient-workspace/ward-patient.style.scss +11 -0
- package/src/ward-patient-workspace/ward-patient.workspace.tsx +51 -0
- package/src/ward-view/ward-bed.component.tsx +0 -1
- package/src/ward-view/ward-view.component.tsx +114 -76
- package/src/ward-view/ward-view.resource.ts +2 -2
- package/src/ward-view/ward-view.scss +4 -4
- package/src/ward-view/ward-view.test.tsx +76 -49
- package/src/ward-view-header/admission-requests-bar.component.tsx +29 -28
- package/src/ward-view-header/admission-requests-bar.test.tsx +11 -15
- package/src/ward-view-header/admission-requests.scss +20 -25
- package/src/ward-view-header/ward-view-header.component.tsx +7 -7
- package/src/ward-view-header/ward-view-header.scss +2 -2
- package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +29 -0
- package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +51 -0
- package/src/ward-workspace/admission-request-card/admission-request-card.component.tsx +16 -0
- package/src/ward-workspace/admission-request-card/admission-request-card.scss +49 -0
- package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.scss +12 -0
- package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +48 -0
- package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +61 -0
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.scss +35 -0
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +341 -0
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +267 -0
- package/src/ward-workspace/admit-patient-form-workspace/types.ts +7 -0
- package/src/ward-workspace/patient-banner/patient-banner.component.tsx +29 -0
- package/src/ward-workspace/patient-banner/style.scss +23 -0
- package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +210 -0
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +238 -0
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.scss +73 -0
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +44 -0
- package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +180 -0
- package/src/ward-workspace/ward-patient-notes/form/notes-form.scss +30 -0
- package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +116 -0
- package/src/ward-workspace/ward-patient-notes/history/note.component.tsx +53 -0
- package/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx +55 -0
- package/src/ward-workspace/ward-patient-notes/history/notes-container.test.tsx +84 -0
- package/src/ward-workspace/ward-patient-notes/history/styles.scss +61 -0
- package/src/ward-workspace/ward-patient-notes/notes-action-button.extension.tsx +18 -0
- package/src/ward-workspace/ward-patient-notes/notes.resource.ts +71 -0
- package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +25 -0
- package/src/ward-workspace/ward-patient-notes/types.ts +44 -0
- package/src/ward.resource.ts +25 -0
- package/translations/en.json +63 -2
- package/dist/443.js +0 -1
- package/dist/443.js.map +0 -1
- package/dist/589.js +0 -1
- package/dist/589.js.map +0 -1
- package/dist/695.js +0 -2
- package/dist/695.js.map +0 -1
- package/src/hooks/useAdmittedPatients.ts +0 -13
- package/src/ward-patient-card/row-elements/row-elements.scss +0 -16
- package/src/ward-patient-card/ward-patient-card-row.resources.tsx +0 -92
- package/src/ward-patient-card/ward-patient-card.tsx +0 -20
- package/src/ward-workspace/admission-request-card.component.tsx +0 -23
- package/src/ward-workspace/admission-request-card.scss +0 -34
- package/src/ward-workspace/admission-request-workspace.test.tsx +0 -38
- package/src/ward-workspace/admission-requests-workspace.component.tsx +0 -21
- package/src/ward-workspace/admission-requests-workspace.scss +0 -13
- /package/dist/{695.js.LICENSE.txt → 793.js.LICENSE.txt} +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import styles from './ward-view-header.scss';
|
|
3
3
|
import AdmissionRequestsBar from './admission-requests-bar.component';
|
|
4
|
-
import
|
|
4
|
+
import useWardLocation from '../hooks/useWardLocation';
|
|
5
5
|
|
|
6
|
-
interface WardViewHeaderProps {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
6
|
+
interface WardViewHeaderProps {}
|
|
7
|
+
|
|
8
|
+
const WardViewHeader: React.FC<WardViewHeaderProps> = () => {
|
|
9
|
+
const { location } = useWardLocation();
|
|
10
10
|
return (
|
|
11
11
|
<div className={styles.wardViewHeader}>
|
|
12
|
-
<h4>{location
|
|
13
|
-
<AdmissionRequestsBar
|
|
12
|
+
<h4>{location?.display}</h4>
|
|
13
|
+
<AdmissionRequestsBar />
|
|
14
14
|
</div>
|
|
15
15
|
);
|
|
16
16
|
};
|
package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Button } from '@carbon/react';
|
|
2
|
+
import { ArrowRightIcon, launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
|
|
3
|
+
import React, { useCallback } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import type { WardPatientCard } from '../../types';
|
|
6
|
+
import type { AdmitPatientFormWorkspaceProps } from '../admit-patient-form-workspace/types';
|
|
7
|
+
import styles from './admission-request-card.scss';
|
|
8
|
+
|
|
9
|
+
const AdmissionRequestCardActions: WardPatientCard = ({ patient, inpatientRequest }) => {
|
|
10
|
+
const { dispositionType } = inpatientRequest;
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
const responsiveSize = useLayoutType() === 'tablet' ? 'lg' : 'md';
|
|
13
|
+
const launchPatientAdmissionForm = useCallback(
|
|
14
|
+
() => launchWorkspace<AdmitPatientFormWorkspaceProps>('admit-patient-form-workspace', { patient, dispositionType }),
|
|
15
|
+
[],
|
|
16
|
+
);
|
|
17
|
+
return (
|
|
18
|
+
<div className={styles.admissionRequestActionBar}>
|
|
19
|
+
<Button kind="ghost" size={responsiveSize}>
|
|
20
|
+
{t('transferElsewhere', 'Transfer elsewhere')}
|
|
21
|
+
</Button>
|
|
22
|
+
<Button kind="ghost" renderIcon={ArrowRightIcon} size={responsiveSize} onClick={launchPatientAdmissionForm}>
|
|
23
|
+
{t('admitPatient', 'Admit patient')}
|
|
24
|
+
</Button>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default AdmissionRequestCardActions;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ExtensionSlot, formatDatetime, getLocale } from '@openmrs/esm-framework';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component';
|
|
5
|
+
import styles from './admission-request-card.scss';
|
|
6
|
+
import type WardPatientCard from '../../ward-patient-card/ward-patient-card.component';
|
|
7
|
+
import { useCurrentWardCardConfig } from '../../hooks/useCurrentWardCardConfig';
|
|
8
|
+
import { WardPatientCardElement } from '../../ward-patient-card/ward-patient-card-element.component';
|
|
9
|
+
import WardPatientName from '../../ward-patient-card/row-elements/ward-patient-name';
|
|
10
|
+
|
|
11
|
+
const AdmissionRequestCardHeader: WardPatientCard = (wardPatient) => {
|
|
12
|
+
const { inpatientRequest } = wardPatient;
|
|
13
|
+
const { dispositionEncounter } = inpatientRequest;
|
|
14
|
+
const { id, headerRowElements } = useCurrentWardCardConfig();
|
|
15
|
+
const { patient } = wardPatient;
|
|
16
|
+
const extensionSlotState = wardPatient;
|
|
17
|
+
|
|
18
|
+
const rowsExtensionSlotName = id == 'default' ? 'ward-patient-card-slot' : `ward-patient-card-${id}-slot`;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className={styles.admissionRequestCardHeaderContainer}>
|
|
22
|
+
<div className={styles.admissionRequestCardHeader}>
|
|
23
|
+
<WardPatientName patient={patient} />
|
|
24
|
+
{headerRowElements.map((elementId, i) => (
|
|
25
|
+
<WardPatientCardElement
|
|
26
|
+
key={`ward-card-${patient.uuid}-header-${i}`}
|
|
27
|
+
elementId={elementId}
|
|
28
|
+
{...wardPatient}
|
|
29
|
+
/>
|
|
30
|
+
))}
|
|
31
|
+
</div>
|
|
32
|
+
<div className={classNames(styles.admissionRequestCardHeader, styles.admissionEncounterDetails)}>
|
|
33
|
+
<div>
|
|
34
|
+
{formatDatetime(new Date(dispositionEncounter?.encounterDatetime), {
|
|
35
|
+
locale: getLocale(),
|
|
36
|
+
mode: 'standard',
|
|
37
|
+
})}
|
|
38
|
+
</div>
|
|
39
|
+
<div>{dispositionEncounter?.encounterProviders?.map((provider) => provider?.provider?.display).join(',')}</div>
|
|
40
|
+
<div>{dispositionEncounter?.location?.display}</div>
|
|
41
|
+
</div>
|
|
42
|
+
<ExtensionSlot
|
|
43
|
+
name={rowsExtensionSlotName}
|
|
44
|
+
state={extensionSlotState}
|
|
45
|
+
className={styles.admissionRequestCardRow}
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default AdmissionRequestCardHeader;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { WardPatientCard } from '../../types';
|
|
3
|
+
import AdmissionRequestCardActions from './admission-request-card-actions.component';
|
|
4
|
+
import AdmissionRequestCardHeader from './admission-request-card-header.component';
|
|
5
|
+
import styles from './admission-request-card.scss';
|
|
6
|
+
|
|
7
|
+
const AdmissionRequestCard: WardPatientCard = (wardPatient) => {
|
|
8
|
+
return (
|
|
9
|
+
<div className={styles.admissionRequestCard}>
|
|
10
|
+
<AdmissionRequestCardHeader {...wardPatient} />
|
|
11
|
+
<AdmissionRequestCardActions {...wardPatient} />
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default AdmissionRequestCard;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@use '@carbon/type';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
4
|
+
|
|
5
|
+
.admissionRequestCard {
|
|
6
|
+
background-color: $ui-03;
|
|
7
|
+
border: 1px solid $color-gray-30;
|
|
8
|
+
height: fit-content;
|
|
9
|
+
color: $color-gray-70;
|
|
10
|
+
margin-bottom: layout.$spacing-05;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.admissionRequestCardHeaderContainer {
|
|
14
|
+
padding: layout.$spacing-03;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.admissionRequestCardHeader {
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
flex-wrap: wrap;
|
|
21
|
+
|
|
22
|
+
> div:not(:first-of-type):not(:empty)::before {
|
|
23
|
+
content: '·';
|
|
24
|
+
padding: layout.$spacing-02;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.admissionRequestCardRow {
|
|
29
|
+
padding: layout.$spacing-05;
|
|
30
|
+
margin: layout.$spacing-03;
|
|
31
|
+
background-color: white;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.admissionEncounterDetails {
|
|
35
|
+
@include type.type-style('helper-text-01');
|
|
36
|
+
color: $text-02;
|
|
37
|
+
margin-top: layout.$spacing-03;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.admissionRequestActionBar {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: space-between;
|
|
44
|
+
border-top: 1px solid $text-03;
|
|
45
|
+
|
|
46
|
+
svg {
|
|
47
|
+
fill: $interactive-01 !important;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
3
|
+
|
|
4
|
+
.admissionRequestsWorkspace {
|
|
5
|
+
padding: layout.$spacing-04;
|
|
6
|
+
display: grid;
|
|
7
|
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
8
|
+
gap: layout.$spacing-05;
|
|
9
|
+
}
|
|
10
|
+
.content {
|
|
11
|
+
padding: layout.$spacing-05;
|
|
12
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { screen } from '@testing-library/react';
|
|
3
|
+
import { defineConfigSchema } from '@openmrs/esm-framework';
|
|
4
|
+
import { useInpatientRequest } from '../../hooks/useInpatientRequest';
|
|
5
|
+
import { configSchema } from '../../config-schema';
|
|
6
|
+
import useWardLocation from '../../hooks/useWardLocation';
|
|
7
|
+
import AdmissionRequestsWorkspace from './admission-requests.workspace';
|
|
8
|
+
import { mockInpatientRequest, mockLocationInpatientWard } from '../../../../../__mocks__';
|
|
9
|
+
import { renderWithSwr } from '../../../../../tools';
|
|
10
|
+
|
|
11
|
+
defineConfigSchema('@openmrs/esm-ward-app', configSchema);
|
|
12
|
+
|
|
13
|
+
jest.mock('../../hooks/useInpatientRequest', () => ({
|
|
14
|
+
useInpatientRequest: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
jest.mock('../../hooks/useWardLocation', () => jest.fn());
|
|
17
|
+
|
|
18
|
+
const mockUseWardLocation = useWardLocation as jest.Mock;
|
|
19
|
+
mockUseWardLocation.mockReturnValue({
|
|
20
|
+
location: mockLocationInpatientWard,
|
|
21
|
+
isLoadingLocation: false,
|
|
22
|
+
errorFetchingLocation: null,
|
|
23
|
+
invalidLocation: false,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const mockInpatientRequestResponse = {
|
|
27
|
+
error: undefined,
|
|
28
|
+
mutate: jest.fn(),
|
|
29
|
+
isValidating: false,
|
|
30
|
+
isLoading: false,
|
|
31
|
+
inpatientRequests: [mockInpatientRequest],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse);
|
|
35
|
+
|
|
36
|
+
const workspaceProps = {
|
|
37
|
+
closeWorkspace: jest.fn(),
|
|
38
|
+
promptBeforeClosing: jest.fn(),
|
|
39
|
+
closeWorkspaceWithSavedChanges: jest.fn(),
|
|
40
|
+
setTitle: jest.fn(),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
describe('Admission Requests Workspace', () => {
|
|
44
|
+
it('should render a admission request card', () => {
|
|
45
|
+
renderWithSwr(<AdmissionRequestsWorkspace {...workspaceProps} />);
|
|
46
|
+
expect(screen.getByText(mockInpatientRequest.patient.person?.preferredName?.display as string)).toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styles from './admission-requests-workspace.scss';
|
|
3
|
+
import AdmissionRequestCard from '../admission-request-card/admission-request-card.component';
|
|
4
|
+
import { Search } from '@carbon/react';
|
|
5
|
+
import { ErrorState } from '@openmrs/esm-framework';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { useInpatientRequest } from '../../hooks/useInpatientRequest';
|
|
8
|
+
import { type InpatientRequest } from '../../types';
|
|
9
|
+
|
|
10
|
+
interface AdmissionRequestsWorkspaceProps {}
|
|
11
|
+
const AdmissionRequestsWorkspace: React.FC<AdmissionRequestsWorkspaceProps> = () => {
|
|
12
|
+
const {
|
|
13
|
+
inpatientRequests,
|
|
14
|
+
isLoading: isLoadingInpatientRequests,
|
|
15
|
+
error: errorFetchingInpatientRequests,
|
|
16
|
+
} = useInpatientRequest(['ADMIT', 'TRANSFER']);
|
|
17
|
+
const { t } = useTranslation();
|
|
18
|
+
const [searchTerm, setSearchTerm] = React.useState('');
|
|
19
|
+
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
20
|
+
setSearchTerm(event.target.value);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className={styles.admissionRequestsWorkspaceContainer}>
|
|
25
|
+
<Search
|
|
26
|
+
labelText=""
|
|
27
|
+
value={searchTerm}
|
|
28
|
+
onChange={handleSearch}
|
|
29
|
+
size="lg"
|
|
30
|
+
placeholder={t('searchForPatient', 'Search for a patient')}
|
|
31
|
+
disabled
|
|
32
|
+
/>
|
|
33
|
+
|
|
34
|
+
<div className={styles.content}>
|
|
35
|
+
{isLoadingInpatientRequests ? (
|
|
36
|
+
<>Loading</>
|
|
37
|
+
) : errorFetchingInpatientRequests ? (
|
|
38
|
+
<ErrorState
|
|
39
|
+
headerTitle={t('admissionRequests', 'Admission requests')}
|
|
40
|
+
error={errorFetchingInpatientRequests}
|
|
41
|
+
/>
|
|
42
|
+
) : (
|
|
43
|
+
<>
|
|
44
|
+
{inpatientRequests.map((request: InpatientRequest, i) => (
|
|
45
|
+
<AdmissionRequestCard
|
|
46
|
+
key={`admission-request-card-${i}`}
|
|
47
|
+
patient={request.patient}
|
|
48
|
+
visit={request.visit}
|
|
49
|
+
bed={null}
|
|
50
|
+
inpatientRequest={request}
|
|
51
|
+
inpatientAdmission={null}
|
|
52
|
+
/>
|
|
53
|
+
))}
|
|
54
|
+
</>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default AdmissionRequestsWorkspace;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@use '@carbon/type';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
4
|
+
|
|
5
|
+
.form {
|
|
6
|
+
display: flex;
|
|
7
|
+
height: 100%;
|
|
8
|
+
justify-content: space-between;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.formContent {
|
|
13
|
+
padding: layout.$spacing-05;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.formError {
|
|
17
|
+
margin: (-(layout.$spacing-05)) (-(layout.$spacing-05)) layout.$spacing-05 (-(layout.$spacing-05));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.buttonSet button {
|
|
21
|
+
max-width: unset !important;
|
|
22
|
+
width: 50% !important;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.bedSelectionDropdown {
|
|
26
|
+
margin-top: layout.$spacing-03;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.errorNotifications {
|
|
30
|
+
margin-top: layout.$spacing-05;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.productiveHeading02 {
|
|
34
|
+
@include type.type-style('heading-compact-02');
|
|
35
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { renderWithSwr } from '../../../../../tools';
|
|
5
|
+
import AdmitPatientFormWorkspace from './admit-patient-form.workspace';
|
|
6
|
+
import {
|
|
7
|
+
mockAdmissionLocation,
|
|
8
|
+
mockInpatientRequest,
|
|
9
|
+
mockLocationInpatientWard,
|
|
10
|
+
mockPatientAlice,
|
|
11
|
+
} from '../../../../../__mocks__';
|
|
12
|
+
import type { DispositionType } from '../../types';
|
|
13
|
+
import type { AdmitPatientFormWorkspaceProps } from './types';
|
|
14
|
+
import { useAdmissionLocation } from '../../hooks/useAdmissionLocation';
|
|
15
|
+
import { openmrsFetch, provide, showSnackbar, useFeatureFlag, useSession } from '@openmrs/esm-framework';
|
|
16
|
+
import useEmrConfiguration from '../../hooks/useEmrConfiguration';
|
|
17
|
+
import useWardLocation from '../../hooks/useWardLocation';
|
|
18
|
+
import { useInpatientRequest } from '../../hooks/useInpatientRequest';
|
|
19
|
+
|
|
20
|
+
jest.mock('../../hooks/useAdmissionLocation', () => ({
|
|
21
|
+
useAdmissionLocation: jest.fn(),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
jest.mock('../../hooks/useWardLocation', () => jest.fn());
|
|
25
|
+
|
|
26
|
+
jest.mock('../../hooks/useEmrConfiguration', () => jest.fn());
|
|
27
|
+
|
|
28
|
+
jest.mock('../../hooks/useInpatientRequest', () => ({
|
|
29
|
+
useInpatientRequest: jest.fn(),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const mockedUseInpatientRequest = jest.mocked(useInpatientRequest);
|
|
33
|
+
const mockedUseEmrConfiguration = jest.mocked(useEmrConfiguration);
|
|
34
|
+
const mockedUseWardLocation = jest.mocked(useWardLocation);
|
|
35
|
+
const mockedOpenmrsFetch = jest.mocked(openmrsFetch);
|
|
36
|
+
const mockedUseAdmissionLocation = jest.mocked(useAdmissionLocation);
|
|
37
|
+
const mockedUseFeatureFlag = jest.mocked(useFeatureFlag);
|
|
38
|
+
const mockedShowSnackbar = jest.mocked(showSnackbar);
|
|
39
|
+
const mockedUseSession = jest.mocked(useSession);
|
|
40
|
+
|
|
41
|
+
const mockWorkspaceProps: AdmitPatientFormWorkspaceProps = {
|
|
42
|
+
patient: mockPatientAlice,
|
|
43
|
+
closeWorkspace: jest.fn(),
|
|
44
|
+
closeWorkspaceWithSavedChanges: jest.fn(),
|
|
45
|
+
promptBeforeClosing: jest.fn(),
|
|
46
|
+
setTitle: jest.fn(),
|
|
47
|
+
dispositionType: 'ADMIT',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function renderAdmissionForm(dispositionType: DispositionType = 'ADMIT') {
|
|
51
|
+
renderWithSwr(<AdmitPatientFormWorkspace {...{ ...mockWorkspaceProps, dispositionType }} />);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const mockedMutateInpatientRequest = jest.fn();
|
|
55
|
+
|
|
56
|
+
describe('Testing AdmitPatientForm', () => {
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
jest.clearAllMocks();
|
|
59
|
+
mockedUseAdmissionLocation.mockReturnValue({
|
|
60
|
+
isLoading: false,
|
|
61
|
+
isValidating: false,
|
|
62
|
+
admissionLocation: mockAdmissionLocation,
|
|
63
|
+
mutate: jest.fn(),
|
|
64
|
+
error: undefined,
|
|
65
|
+
});
|
|
66
|
+
mockedUseSession.mockReturnValue({
|
|
67
|
+
currentProvider: {
|
|
68
|
+
uuid: 'current-provider-uuid',
|
|
69
|
+
identifier: 'current-provider-identifier',
|
|
70
|
+
},
|
|
71
|
+
authenticated: true,
|
|
72
|
+
sessionId: 'session-id',
|
|
73
|
+
});
|
|
74
|
+
mockedUseFeatureFlag.mockReturnValue(true);
|
|
75
|
+
mockedUseEmrConfiguration.mockReturnValue({
|
|
76
|
+
isLoadingEmrConfiguration: false,
|
|
77
|
+
errorFetchingEmrConfiguration: null,
|
|
78
|
+
// @ts-ignore - we only need these two keys for now
|
|
79
|
+
emrConfiguration: {
|
|
80
|
+
admissionEncounterType: {
|
|
81
|
+
uuid: 'admission-encounter-type-uuid',
|
|
82
|
+
display: 'Admission Encounter',
|
|
83
|
+
},
|
|
84
|
+
transferWithinHospitalEncounterType: {
|
|
85
|
+
uuid: 'transfer-within-hospital-encounter-type-uuid',
|
|
86
|
+
display: 'Transfer Within Hospital Encounter Type',
|
|
87
|
+
},
|
|
88
|
+
clinicianEncounterRole: {
|
|
89
|
+
uuid: 'clinician-encounter-role-uuid',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
mutateEmrConfiguration: jest.fn(),
|
|
93
|
+
});
|
|
94
|
+
mockedUseInpatientRequest.mockReturnValue({
|
|
95
|
+
mutate: mockedMutateInpatientRequest,
|
|
96
|
+
error: undefined,
|
|
97
|
+
inpatientRequests: [mockInpatientRequest],
|
|
98
|
+
isLoading: false,
|
|
99
|
+
isValidating: false,
|
|
100
|
+
});
|
|
101
|
+
mockedUseWardLocation.mockReturnValue({
|
|
102
|
+
location: mockLocationInpatientWard,
|
|
103
|
+
invalidLocation: false,
|
|
104
|
+
isLoadingLocation: false,
|
|
105
|
+
errorFetchingLocation: null,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should render admit patient form', async () => {
|
|
110
|
+
const user = userEvent.setup();
|
|
111
|
+
renderAdmissionForm();
|
|
112
|
+
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
|
113
|
+
await user.click(cancelButton);
|
|
114
|
+
expect(mockWorkspaceProps.closeWorkspace).toHaveBeenCalledWith({
|
|
115
|
+
ignoreChanges: true,
|
|
116
|
+
});
|
|
117
|
+
screen.getByText('Admit');
|
|
118
|
+
expect(screen.getByText('Select a bed')).toBeInTheDocument();
|
|
119
|
+
await user.click(
|
|
120
|
+
screen.getByRole('combobox', {
|
|
121
|
+
name: 'Choose an option',
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
expect(screen.getByText('bed1 · Alice Johnson')).toBeInTheDocument();
|
|
125
|
+
expect(screen.getByText('bed2 · Empty')).toBeInTheDocument();
|
|
126
|
+
expect(screen.getByText('bed3 · Empty')).toBeInTheDocument();
|
|
127
|
+
expect(screen.getByText('bed4 · Empty')).toBeInTheDocument();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should block the form if emr configuration is not fetched properly', () => {
|
|
131
|
+
mockedUseEmrConfiguration.mockReturnValue({
|
|
132
|
+
isLoadingEmrConfiguration: false,
|
|
133
|
+
errorFetchingEmrConfiguration: true,
|
|
134
|
+
emrConfiguration: null,
|
|
135
|
+
mutateEmrConfiguration: jest.fn(),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
renderAdmissionForm();
|
|
139
|
+
|
|
140
|
+
const admitButton = screen.getByText('Admit');
|
|
141
|
+
expect(admitButton).toBeDisabled();
|
|
142
|
+
expect(screen.getByText("Some parts of the form didn't load")).toBeInTheDocument();
|
|
143
|
+
expect(
|
|
144
|
+
screen.getByText(
|
|
145
|
+
'Fetching EMR configuration failed. Try refreshing the page or contact your system administrator.',
|
|
146
|
+
),
|
|
147
|
+
).toBeInTheDocument();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should render admit patient form if bed management module is not present', () => {
|
|
151
|
+
mockedUseFeatureFlag.mockReturnValue(false);
|
|
152
|
+
renderAdmissionForm();
|
|
153
|
+
expect(screen.getByText('Select a bed')).toBeInTheDocument();
|
|
154
|
+
expect(screen.getByText('Unable to select beds')).toBeInTheDocument();
|
|
155
|
+
expect(screen.getByText('Bed management module is not present to allow bed selection')).toBeInTheDocument();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should render admit patient form if bed management module is present, but no beds are configured', () => {
|
|
159
|
+
mockedUseFeatureFlag.mockReturnValue(true);
|
|
160
|
+
mockedUseAdmissionLocation.mockReturnValueOnce({
|
|
161
|
+
isLoading: false,
|
|
162
|
+
isValidating: false,
|
|
163
|
+
admissionLocation: {
|
|
164
|
+
...mockAdmissionLocation,
|
|
165
|
+
totalBeds: 0,
|
|
166
|
+
bedLayouts: [],
|
|
167
|
+
},
|
|
168
|
+
mutate: jest.fn(),
|
|
169
|
+
error: null,
|
|
170
|
+
});
|
|
171
|
+
renderAdmissionForm();
|
|
172
|
+
expect(screen.getByText('Select a bed')).toBeInTheDocument();
|
|
173
|
+
expect(screen.getByText('No beds configured for Inpatient Ward location')).toBeInTheDocument();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should submit the form, create encounter and submit bed', async () => {
|
|
177
|
+
mockedUseAdmissionLocation.mockReturnValueOnce({
|
|
178
|
+
isLoading: false,
|
|
179
|
+
isValidating: false,
|
|
180
|
+
admissionLocation: mockAdmissionLocation,
|
|
181
|
+
mutate: jest.fn(),
|
|
182
|
+
error: null,
|
|
183
|
+
});
|
|
184
|
+
// @ts-ignore - we only need these two keys for now
|
|
185
|
+
mockedOpenmrsFetch.mockResolvedValue({
|
|
186
|
+
ok: true,
|
|
187
|
+
data: {
|
|
188
|
+
uuid: 'encounter-uuid',
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
const user = userEvent.setup();
|
|
192
|
+
renderAdmissionForm();
|
|
193
|
+
const combobox = screen.getByRole('combobox', {
|
|
194
|
+
name: 'Choose an option',
|
|
195
|
+
});
|
|
196
|
+
await user.click(combobox);
|
|
197
|
+
const bedOption = screen.getByText('bed3 · Empty');
|
|
198
|
+
await user.click(bedOption);
|
|
199
|
+
const admitButton = screen.getByRole('button', { name: 'Admit' });
|
|
200
|
+
expect(admitButton).toBeEnabled();
|
|
201
|
+
await user.click(admitButton);
|
|
202
|
+
expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(2);
|
|
203
|
+
expect(mockedOpenmrsFetch).toHaveBeenCalledWith('/ws/rest/v1/encounter', {
|
|
204
|
+
method: 'POST',
|
|
205
|
+
headers: {
|
|
206
|
+
'content-type': 'application/json',
|
|
207
|
+
},
|
|
208
|
+
body: {
|
|
209
|
+
patient: mockPatientAlice.uuid,
|
|
210
|
+
encounterType: 'admission-encounter-type-uuid',
|
|
211
|
+
location: mockAdmissionLocation.ward.uuid,
|
|
212
|
+
obs: [],
|
|
213
|
+
encounterProviders: [
|
|
214
|
+
{
|
|
215
|
+
provider: 'current-provider-uuid',
|
|
216
|
+
encounterRole: 'clinician-encounter-role-uuid',
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
expect(mockedOpenmrsFetch).toHaveBeenCalledWith('/ws/rest/v1/beds/3', {
|
|
222
|
+
method: 'POST',
|
|
223
|
+
headers: {
|
|
224
|
+
'content-type': 'application/json',
|
|
225
|
+
},
|
|
226
|
+
body: {
|
|
227
|
+
patientUuid: mockPatientAlice.uuid,
|
|
228
|
+
encounterUuid: 'encounter-uuid',
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
expect(mockedShowSnackbar).toHaveBeenCalledWith({
|
|
232
|
+
kind: 'success',
|
|
233
|
+
subtitle: '{{patientName}} has been successfully admitted and assigned to bed bed3',
|
|
234
|
+
title: 'Patient admitted successfully',
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should show snackbar if there was an issue creating an encounter', async () => {
|
|
239
|
+
mockedOpenmrsFetch.mockRejectedValue(new Error('Failed to create encounter'));
|
|
240
|
+
const user = userEvent.setup();
|
|
241
|
+
renderAdmissionForm();
|
|
242
|
+
const combobox = screen.getByRole('combobox', {
|
|
243
|
+
name: 'Choose an option',
|
|
244
|
+
});
|
|
245
|
+
await user.click(combobox);
|
|
246
|
+
const bedOption = screen.getByText('bed3 · Empty');
|
|
247
|
+
await user.click(bedOption);
|
|
248
|
+
const admitButton = screen.getByRole('button', { name: 'Admit' });
|
|
249
|
+
expect(admitButton).toBeEnabled();
|
|
250
|
+
await user.click(admitButton);
|
|
251
|
+
expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(1);
|
|
252
|
+
expect(mockedShowSnackbar).toHaveBeenCalledWith({
|
|
253
|
+
kind: 'error',
|
|
254
|
+
title: 'Failed to admit patient',
|
|
255
|
+
subtitle: 'Failed to create encounter',
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should show warning snackbar if encounter was created and bed assignment was not successful', async () => {
|
|
260
|
+
// @ts-ignore - matching whole FetchResponse type is not necessary
|
|
261
|
+
mockedOpenmrsFetch.mockImplementation((url) => {
|
|
262
|
+
if (url.startsWith('/ws/rest/v1/beds')) {
|
|
263
|
+
return Promise.reject(new Error('Failed to assign bed'));
|
|
264
|
+
}
|
|
265
|
+
return Promise.resolve({
|
|
266
|
+
ok: true,
|
|
267
|
+
data: {
|
|
268
|
+
uuid: 'encounter-uuid',
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const user = userEvent.setup();
|
|
274
|
+
renderAdmissionForm();
|
|
275
|
+
const combobox = screen.getByRole('combobox', {
|
|
276
|
+
name: 'Choose an option',
|
|
277
|
+
});
|
|
278
|
+
await user.click(combobox);
|
|
279
|
+
const bedOption = screen.getByText('bed3 · Empty');
|
|
280
|
+
await user.click(bedOption);
|
|
281
|
+
const admitButton = screen.getByRole('button', { name: 'Admit' });
|
|
282
|
+
expect(admitButton).toBeEnabled();
|
|
283
|
+
await user.click(admitButton);
|
|
284
|
+
expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(2);
|
|
285
|
+
expect(mockedShowSnackbar).toHaveBeenCalledWith({
|
|
286
|
+
kind: 'warning',
|
|
287
|
+
title: 'Patient admitted successfully',
|
|
288
|
+
subtitle: 'Patient admitted successfully but fail to assign bed to patient',
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should admit patient if no beds are configured', async () => {
|
|
293
|
+
mockedUseAdmissionLocation.mockReturnValueOnce({
|
|
294
|
+
isLoading: false,
|
|
295
|
+
isValidating: false,
|
|
296
|
+
admissionLocation: {
|
|
297
|
+
...mockAdmissionLocation,
|
|
298
|
+
totalBeds: 0,
|
|
299
|
+
bedLayouts: [],
|
|
300
|
+
},
|
|
301
|
+
mutate: jest.fn(),
|
|
302
|
+
error: null,
|
|
303
|
+
});
|
|
304
|
+
// @ts-ignore - we only need these two keys for now
|
|
305
|
+
mockedOpenmrsFetch.mockResolvedValue({
|
|
306
|
+
ok: true,
|
|
307
|
+
data: {
|
|
308
|
+
uuid: 'encounter-uuid',
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
const user = userEvent.setup();
|
|
312
|
+
renderAdmissionForm();
|
|
313
|
+
const admitButton = screen.getByRole('button', { name: 'Admit' });
|
|
314
|
+
expect(admitButton).toBeEnabled();
|
|
315
|
+
await user.click(admitButton);
|
|
316
|
+
expect(mockedOpenmrsFetch).toHaveBeenCalledTimes(1);
|
|
317
|
+
expect(mockedOpenmrsFetch).toHaveBeenCalledWith('/ws/rest/v1/encounter', {
|
|
318
|
+
method: 'POST',
|
|
319
|
+
headers: {
|
|
320
|
+
'content-type': 'application/json',
|
|
321
|
+
},
|
|
322
|
+
body: {
|
|
323
|
+
patient: mockPatientAlice.uuid,
|
|
324
|
+
encounterType: 'admission-encounter-type-uuid',
|
|
325
|
+
location: mockAdmissionLocation.ward.uuid,
|
|
326
|
+
obs: [],
|
|
327
|
+
encounterProviders: [
|
|
328
|
+
{
|
|
329
|
+
provider: 'current-provider-uuid',
|
|
330
|
+
encounterRole: 'clinician-encounter-role-uuid',
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
expect(mockedShowSnackbar).toHaveBeenCalledWith({
|
|
336
|
+
kind: 'success',
|
|
337
|
+
subtitle: 'Patient admitted successfully to Inpatient Ward',
|
|
338
|
+
title: 'Patient admitted successfully',
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
});
|