@kenyaemr/esm-ward-app 7.0.3-pre.88 → 7.0.3-pre.94
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 +2 -2
- 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,124 +1,120 @@
|
|
|
1
|
-
import { InlineNotification } from '@carbon/react';
|
|
2
|
-
import { WorkspaceContainer, useFeatureFlag, useLocations, useSession, type Location } from '@openmrs/esm-framework';
|
|
3
1
|
import React, { useMemo } from 'react';
|
|
2
|
+
import { InlineNotification } from '@carbon/react';
|
|
4
3
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import {
|
|
4
|
+
import { WorkspaceContainer, useFeatureFlag } from '@openmrs/esm-framework';
|
|
6
5
|
import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
|
|
7
6
|
import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
|
|
8
7
|
import WardBed from './ward-bed.component';
|
|
9
8
|
import { bedLayoutToBed, filterBeds } from './ward-view.resource';
|
|
10
9
|
import styles from './ward-view.scss';
|
|
11
10
|
import WardViewHeader from '../ward-view-header/ward-view-header.component';
|
|
12
|
-
import { type
|
|
13
|
-
import {
|
|
11
|
+
import { type InpatientAdmission, type WardPatient } from '../types';
|
|
12
|
+
import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
|
|
13
|
+
import useWardLocation from '../hooks/useWardLocation';
|
|
14
|
+
import UnassignedPatient from '../beds/unassigned-patient.component';
|
|
14
15
|
|
|
15
16
|
const WardView = () => {
|
|
16
|
-
const
|
|
17
|
-
const {
|
|
18
|
-
const allLocations = useLocations();
|
|
17
|
+
const response = useWardLocation();
|
|
18
|
+
const { isLoadingLocation, invalidLocation } = response;
|
|
19
19
|
const { t } = useTranslation();
|
|
20
20
|
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const location = (locationFromUrl ?? sessionLocation) as any as Location;
|
|
24
|
-
//TODO:Display patients with admitted status (based on their observations) that have no beds assigned
|
|
25
|
-
if (!isBedManagementModuleInstalled) {
|
|
21
|
+
|
|
22
|
+
if (isLoadingLocation) {
|
|
26
23
|
return <></>;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
<InlineNotification
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
subtitle={t('unknownLocationUuid', 'Unknown location uuid: {{locationUuidFromUrl}}', {
|
|
35
|
-
locationUuidFromUrl,
|
|
36
|
-
})}
|
|
37
|
-
/>
|
|
38
|
-
) : (
|
|
26
|
+
if (invalidLocation) {
|
|
27
|
+
return <InlineNotification kind="error" title={t('invalidLocationSpecified', 'Invalid location specified')} />;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
39
31
|
<div className={styles.wardView}>
|
|
40
|
-
<WardViewHeader
|
|
32
|
+
<WardViewHeader />
|
|
41
33
|
<div className={styles.wardViewMain}>
|
|
42
|
-
<
|
|
34
|
+
{isBedManagementModuleInstalled ? <WardViewWithBedManagement /> : <WardViewWithoutBedManagement />}
|
|
43
35
|
</div>
|
|
44
|
-
<WorkspaceContainer contextKey="ward" />
|
|
36
|
+
<WorkspaceContainer overlay contextKey="ward" />
|
|
45
37
|
</div>
|
|
46
38
|
);
|
|
47
39
|
};
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
} = useAdmissionLocation(location.uuid);
|
|
55
|
-
const {
|
|
56
|
-
admittedPatients,
|
|
57
|
-
isLoading: isLoadingPatients,
|
|
58
|
-
error: errorLoadingPatients,
|
|
59
|
-
} = useAdmittedPatients(location.uuid);
|
|
41
|
+
// display to use if bed management is installed
|
|
42
|
+
const WardViewWithBedManagement = () => {
|
|
43
|
+
const { location } = useWardLocation();
|
|
44
|
+
const { admissionLocation, isLoading: isLoadingLocation, error: errorLoadingLocation } = useAdmissionLocation();
|
|
45
|
+
const { inpatientAdmissions, isLoading: isLoadingPatients, error: errorLoadingPatients } = useInpatientAdmission();
|
|
60
46
|
const { t } = useTranslation();
|
|
61
|
-
const
|
|
62
|
-
const map = new Map<string,
|
|
63
|
-
for (const
|
|
64
|
-
map.set(
|
|
47
|
+
const inpatientAdmissionsByPatientUuid = useMemo(() => {
|
|
48
|
+
const map = new Map<string, InpatientAdmission>();
|
|
49
|
+
for (const inpatientAdmission of inpatientAdmissions ?? []) {
|
|
50
|
+
map.set(inpatientAdmission.patient.uuid, inpatientAdmission);
|
|
65
51
|
}
|
|
66
52
|
return map;
|
|
67
|
-
}, [
|
|
68
|
-
|
|
69
|
-
if (admissionLocation != null && admittedPatients != null) {
|
|
70
|
-
const bedLayouts = filterBeds(admissionLocation);
|
|
53
|
+
}, [inpatientAdmissions]);
|
|
71
54
|
|
|
72
|
-
|
|
55
|
+
if (admissionLocation != null || inpatientAdmissions != null) {
|
|
56
|
+
const bedLayouts = admissionLocation && filterBeds(admissionLocation);
|
|
57
|
+
// iterate over all beds
|
|
58
|
+
const wardBeds = bedLayouts?.map((bedLayout) => {
|
|
73
59
|
const { patients } = bedLayout;
|
|
74
60
|
const bed = bedLayoutToBed(bedLayout);
|
|
75
|
-
const wardPatients: WardPatient[] = patients.map((patient) => {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
61
|
+
const wardPatients: WardPatient[] = patients.map((patient): WardPatient => {
|
|
62
|
+
const inpatientAdmission = inpatientAdmissionsByPatientUuid.get(patient.uuid);
|
|
63
|
+
if (inpatientAdmission) {
|
|
64
|
+
const { patient, visit } = inpatientAdmission;
|
|
65
|
+
return { patient, visit, bed, inpatientAdmission, inpatientRequest: null };
|
|
66
|
+
} else {
|
|
67
|
+
// for some reason this patient is in a bed but not in the list of admitted patients, so we need to use the patient data from the bed endpoint
|
|
68
|
+
return {
|
|
69
|
+
patient: patient,
|
|
70
|
+
visit: null,
|
|
71
|
+
bed,
|
|
72
|
+
inpatientAdmission: null, // populate after BED-13
|
|
73
|
+
inpatientRequest: null,
|
|
74
|
+
};
|
|
84
75
|
}
|
|
85
|
-
|
|
86
|
-
// patient assigned a bed but *not* admitted
|
|
87
|
-
// TODO: get the patient's visit and current location
|
|
88
|
-
return {
|
|
89
|
-
patient,
|
|
90
|
-
visit: null,
|
|
91
|
-
admitted: true,
|
|
92
|
-
currentLocation: null,
|
|
93
|
-
timeSinceAdmissionInMinutes: null,
|
|
94
|
-
timeAtInpatientLocationInMinutes: null,
|
|
95
|
-
};
|
|
96
76
|
});
|
|
97
77
|
return <WardBed key={bed.uuid} bed={bed} wardPatients={wardPatients} />;
|
|
98
78
|
});
|
|
99
79
|
|
|
80
|
+
const patientsInBedsUuids = bedLayouts?.flatMap((bedLayout) => bedLayout.patients.map((patient) => patient.uuid));
|
|
81
|
+
const wardUnassignedPatients =
|
|
82
|
+
inpatientAdmissions &&
|
|
83
|
+
inpatientAdmissions
|
|
84
|
+
.filter(
|
|
85
|
+
(inpatientAdmission) =>
|
|
86
|
+
!patientsInBedsUuids || !patientsInBedsUuids.includes(inpatientAdmission.patient.uuid),
|
|
87
|
+
)
|
|
88
|
+
.map((inpatientAdmission) => {
|
|
89
|
+
return (
|
|
90
|
+
<UnassignedPatient
|
|
91
|
+
wardPatient={{
|
|
92
|
+
patient: inpatientAdmission.patient,
|
|
93
|
+
visit: inpatientAdmission.visit,
|
|
94
|
+
bed: null,
|
|
95
|
+
inpatientAdmission,
|
|
96
|
+
inpatientRequest: null,
|
|
97
|
+
}}
|
|
98
|
+
key={inpatientAdmission.patient.uuid}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
100
103
|
return (
|
|
101
104
|
<>
|
|
102
105
|
{wardBeds}
|
|
103
|
-
{bedLayouts
|
|
106
|
+
{bedLayouts?.length == 0 && (
|
|
104
107
|
<InlineNotification
|
|
105
108
|
kind="warning"
|
|
106
109
|
lowContrast={true}
|
|
107
110
|
title={t('noBedsConfigured', 'No beds configured for this location')}
|
|
108
111
|
/>
|
|
109
112
|
)}
|
|
113
|
+
{wardUnassignedPatients}
|
|
110
114
|
</>
|
|
111
115
|
);
|
|
112
116
|
} else if (isLoadingLocation || isLoadingPatients) {
|
|
113
|
-
return
|
|
114
|
-
<>
|
|
115
|
-
{Array(20)
|
|
116
|
-
.fill(0)
|
|
117
|
-
.map((_, i) => (
|
|
118
|
-
<EmptyBedSkeleton key={i} />
|
|
119
|
-
))}
|
|
120
|
-
</>
|
|
121
|
-
);
|
|
117
|
+
return <EmptyBeds />;
|
|
122
118
|
} else if (errorLoadingLocation) {
|
|
123
119
|
return (
|
|
124
120
|
<InlineNotification
|
|
@@ -143,4 +139,46 @@ const WardViewByLocation = ({ location }: { location: Location }) => {
|
|
|
143
139
|
}
|
|
144
140
|
};
|
|
145
141
|
|
|
142
|
+
// display to use if not using bed management
|
|
143
|
+
const WardViewWithoutBedManagement = () => {
|
|
144
|
+
const { inpatientAdmissions, isLoading: isLoadingPatients, error: errorLoadingPatients } = useInpatientAdmission();
|
|
145
|
+
const { t } = useTranslation();
|
|
146
|
+
|
|
147
|
+
if (inpatientAdmissions) {
|
|
148
|
+
const wardPatients = inpatientAdmissions?.map((inpatientAdmission) => {
|
|
149
|
+
const { patient, visit } = inpatientAdmission;
|
|
150
|
+
return (
|
|
151
|
+
<UnassignedPatient
|
|
152
|
+
wardPatient={{ patient, visit, bed: null, inpatientAdmission, inpatientRequest: null }}
|
|
153
|
+
key={inpatientAdmission.patient.uuid}
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
return <>{wardPatients}</>;
|
|
158
|
+
} else if (isLoadingPatients) {
|
|
159
|
+
return <EmptyBeds />;
|
|
160
|
+
} else {
|
|
161
|
+
return (
|
|
162
|
+
<InlineNotification
|
|
163
|
+
kind="error"
|
|
164
|
+
lowContrast={true}
|
|
165
|
+
title={t('errorLoadingPatients', 'Error loading admitted patients')}
|
|
166
|
+
subtitle={errorLoadingPatients?.message}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const EmptyBeds = () => {
|
|
173
|
+
return (
|
|
174
|
+
<>
|
|
175
|
+
{Array(20)
|
|
176
|
+
.fill(0)
|
|
177
|
+
.map((_, i) => (
|
|
178
|
+
<EmptyBedSkeleton key={i} />
|
|
179
|
+
))}
|
|
180
|
+
</>
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
146
184
|
export default WardView;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AdmissionLocationFetchResponse, Bed, BedLayout } from '../types';
|
|
2
2
|
|
|
3
3
|
// the server side has 2 slightly incompatible types for Bed
|
|
4
4
|
export function bedLayoutToBed(bedLayout: BedLayout): Bed {
|
|
@@ -13,7 +13,7 @@ export function bedLayoutToBed(bedLayout: BedLayout): Bed {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function filterBeds(admissionLocation:
|
|
16
|
+
export function filterBeds(admissionLocation: AdmissionLocationFetchResponse): BedLayout[] {
|
|
17
17
|
// admissionLocation.bedLayouts can contain row+column positions with no bed,
|
|
18
18
|
// filter out layout positions with no real bed
|
|
19
19
|
let collator = new Intl.Collator([], { numeric: true });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@use '@carbon/
|
|
1
|
+
@use '@carbon/layout';
|
|
2
2
|
|
|
3
3
|
.wardView {
|
|
4
4
|
background-color: #e4e4e4;
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
right: 0;
|
|
10
10
|
display: flex;
|
|
11
11
|
flex-direction: column;
|
|
12
|
-
padding: 0
|
|
12
|
+
padding: 0 layout.$spacing-05;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.wardViewMain {
|
|
16
16
|
background-color: #e4e4e4;
|
|
17
17
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
18
18
|
display: grid;
|
|
19
|
-
gap:
|
|
20
|
-
padding:
|
|
19
|
+
gap: layout.$spacing-05;
|
|
20
|
+
padding: layout.$spacing-05;
|
|
21
21
|
}
|
|
@@ -1,60 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Person,
|
|
3
|
-
type ConfigSchema,
|
|
4
|
-
getDefaultsFromConfigSchema,
|
|
5
|
-
useConfig,
|
|
6
|
-
useSession,
|
|
7
|
-
useFeatureFlag,
|
|
8
|
-
} from '@openmrs/esm-framework';
|
|
9
|
-
import { screen } from '@testing-library/react';
|
|
10
1
|
import React from 'react';
|
|
2
|
+
import { screen } from '@testing-library/react';
|
|
3
|
+
import { type ConfigSchema, getDefaultsFromConfigSchema, useConfig, useFeatureFlag } from '@openmrs/esm-framework';
|
|
11
4
|
import { useParams } from 'react-router-dom';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { renderWithSwr } from '../../../../tools/test-utils';
|
|
5
|
+
import { mockAdmissionLocation, mockInpatientAdmissions } from '__mocks__';
|
|
6
|
+
import { renderWithSwr } from 'tools';
|
|
15
7
|
import { configSchema } from '../config-schema';
|
|
16
8
|
import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
|
|
9
|
+
import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
|
|
10
|
+
import useWardLocation from '../hooks/useWardLocation';
|
|
17
11
|
import WardView from './ward-view.component';
|
|
18
|
-
import { mockPatientAlice } from '../../../../__mocks__/patient.mock';
|
|
19
|
-
import { useAdmittedPatients } from '../hooks/useAdmittedPatients';
|
|
20
|
-
|
|
21
|
-
jest.replaceProperty(mockPatientAlice.person as Person, 'preferredName', {
|
|
22
|
-
uuid: '',
|
|
23
|
-
givenName: 'Alice',
|
|
24
|
-
familyName: 'Johnson',
|
|
25
|
-
});
|
|
26
12
|
|
|
27
13
|
jest.mocked(useConfig).mockReturnValue({
|
|
28
14
|
...getDefaultsFromConfigSchema<ConfigSchema>(configSchema),
|
|
29
15
|
});
|
|
30
16
|
|
|
31
|
-
const
|
|
32
|
-
jest.mocked(useSession).mockReturnValue({
|
|
33
|
-
sessionLocation: mockedSessionLocation,
|
|
34
|
-
authenticated: true,
|
|
35
|
-
sessionId: 'sessionId',
|
|
36
|
-
});
|
|
17
|
+
const mockUseFeatureFlag = jest.mocked(useFeatureFlag);
|
|
37
18
|
|
|
38
|
-
|
|
19
|
+
jest.mock('../hooks/useWardLocation', () =>
|
|
20
|
+
jest.fn().mockReturnValue({
|
|
21
|
+
location: { uuid: 'abcd', display: 'mock location' },
|
|
22
|
+
isLoadingLocation: false,
|
|
23
|
+
errorFetchingLocation: null,
|
|
24
|
+
invalidLocation: false,
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
39
27
|
|
|
40
|
-
jest.
|
|
41
|
-
return {
|
|
42
|
-
...jest.requireActual('@openmrs/esm-framework'),
|
|
43
|
-
useLocations: jest.fn().mockImplementation(() => mockLocations.data.results),
|
|
44
|
-
};
|
|
45
|
-
});
|
|
28
|
+
const mockUseWardLocation = jest.mocked(useWardLocation);
|
|
46
29
|
|
|
47
30
|
jest.mock('react-router-dom', () => ({
|
|
48
31
|
...jest.requireActual('react-router-dom'),
|
|
49
32
|
useParams: jest.fn().mockReturnValue({}),
|
|
50
33
|
}));
|
|
51
|
-
const
|
|
34
|
+
const mockUseParams = useParams as jest.Mock;
|
|
52
35
|
|
|
53
36
|
jest.mock('../hooks/useAdmissionLocation', () => ({
|
|
54
37
|
useAdmissionLocation: jest.fn(),
|
|
55
38
|
}));
|
|
56
|
-
jest.mock('../hooks/
|
|
57
|
-
|
|
39
|
+
jest.mock('../hooks/useInpatientAdmission', () => ({
|
|
40
|
+
useInpatientAdmission: jest.fn(),
|
|
58
41
|
}));
|
|
59
42
|
|
|
60
43
|
jest.mocked(useAdmissionLocation).mockReturnValue({
|
|
@@ -64,26 +47,25 @@ jest.mocked(useAdmissionLocation).mockReturnValue({
|
|
|
64
47
|
isLoading: false,
|
|
65
48
|
admissionLocation: mockAdmissionLocation,
|
|
66
49
|
});
|
|
67
|
-
jest.mocked(
|
|
50
|
+
jest.mocked(useInpatientAdmission).mockReturnValue({
|
|
68
51
|
error: undefined,
|
|
69
52
|
mutate: jest.fn(),
|
|
70
53
|
isValidating: false,
|
|
71
54
|
isLoading: false,
|
|
72
|
-
|
|
55
|
+
inpatientAdmissions: mockInpatientAdmissions,
|
|
73
56
|
});
|
|
74
57
|
|
|
75
|
-
describe('WardView
|
|
58
|
+
describe('WardView', () => {
|
|
76
59
|
it('renders the session location when no location provided in URL', () => {
|
|
77
60
|
renderWithSwr(<WardView />);
|
|
78
|
-
const header = screen.getByRole('heading', { name:
|
|
61
|
+
const header = screen.getByRole('heading', { name: 'mock location' });
|
|
79
62
|
expect(header).toBeInTheDocument();
|
|
80
63
|
});
|
|
81
64
|
|
|
82
65
|
it('renders the location provided in URL', () => {
|
|
83
|
-
|
|
84
|
-
mockedUseParams.mockReturnValueOnce({ locationUuid: locationToUse.uuid });
|
|
66
|
+
mockUseParams.mockReturnValueOnce({ locationUuid: 'abcd' });
|
|
85
67
|
renderWithSwr(<WardView />);
|
|
86
|
-
const header = screen.getByRole('heading', { name:
|
|
68
|
+
const header = screen.getByRole('heading', { name: 'mock location' });
|
|
87
69
|
expect(header).toBeInTheDocument();
|
|
88
70
|
});
|
|
89
71
|
|
|
@@ -93,18 +75,63 @@ describe('WardView:', () => {
|
|
|
93
75
|
expect(emptyBedCards).toHaveLength(3);
|
|
94
76
|
});
|
|
95
77
|
|
|
78
|
+
it('renders admitted patient without bed', async () => {
|
|
79
|
+
renderWithSwr(<WardView />);
|
|
80
|
+
const admittedPatientWithoutBed = screen.queryByText('Brian Johnson');
|
|
81
|
+
expect(admittedPatientWithoutBed).toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('renders all admitted patients even if bed management module not installed', async () => {
|
|
85
|
+
mockUseFeatureFlag.mockReturnValueOnce(false);
|
|
86
|
+
renderWithSwr(<WardView />);
|
|
87
|
+
const admittedPatientWithoutBed = screen.queryByText('Brian Johnson');
|
|
88
|
+
expect(admittedPatientWithoutBed).toBeInTheDocument();
|
|
89
|
+
});
|
|
90
|
+
|
|
96
91
|
it('renders notification for invalid location uuid', () => {
|
|
97
|
-
|
|
92
|
+
mockUseWardLocation.mockReturnValueOnce({
|
|
93
|
+
location: null,
|
|
94
|
+
isLoadingLocation: false,
|
|
95
|
+
errorFetchingLocation: null,
|
|
96
|
+
invalidLocation: true,
|
|
97
|
+
});
|
|
98
|
+
|
|
98
99
|
renderWithSwr(<WardView />);
|
|
99
100
|
const notification = screen.getByRole('status');
|
|
100
101
|
expect(notification).toBeInTheDocument();
|
|
101
|
-
const invalidText = screen.
|
|
102
|
+
const invalidText = screen.queryByText('Invalid location specified');
|
|
102
103
|
expect(invalidText).toBeInTheDocument();
|
|
103
104
|
});
|
|
104
105
|
|
|
105
|
-
it('screen should
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
it('screen should render warning if backend module installed and no beds configured', () => {
|
|
107
|
+
// override the default response so that no beds are returned
|
|
108
|
+
jest.mocked(useAdmissionLocation).mockReturnValue({
|
|
109
|
+
error: undefined,
|
|
110
|
+
mutate: jest.fn(),
|
|
111
|
+
isValidating: false,
|
|
112
|
+
isLoading: false,
|
|
113
|
+
admissionLocation: { ...mockAdmissionLocation, bedLayouts: [] },
|
|
114
|
+
});
|
|
115
|
+
mockUseFeatureFlag.mockReturnValueOnce(true);
|
|
116
|
+
|
|
117
|
+
renderWithSwr(<WardView />);
|
|
118
|
+
const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
|
|
119
|
+
expect(noBedsConfiguredForThisLocation).toBeInTheDocument();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('screen not should render warning if backend module installed and no beds configured', () => {
|
|
123
|
+
// override the default response so that no beds are returned
|
|
124
|
+
jest.mocked(useAdmissionLocation).mockReturnValue({
|
|
125
|
+
error: undefined,
|
|
126
|
+
mutate: jest.fn(),
|
|
127
|
+
isValidating: false,
|
|
128
|
+
isLoading: false,
|
|
129
|
+
admissionLocation: { ...mockAdmissionLocation, bedLayouts: [] },
|
|
130
|
+
});
|
|
131
|
+
mockUseFeatureFlag.mockReturnValueOnce(false);
|
|
132
|
+
|
|
133
|
+
renderWithSwr(<WardView />);
|
|
134
|
+
const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
|
|
135
|
+
expect(noBedsConfiguredForThisLocation).not.toBeInTheDocument();
|
|
109
136
|
});
|
|
110
137
|
});
|
|
@@ -1,45 +1,46 @@
|
|
|
1
|
-
import { SkeletonIcon } from '@carbon/react';
|
|
2
|
-
import { Movement } from '@carbon/react/icons';
|
|
3
|
-
import { launchWorkspace, showNotification, type Location } from '@openmrs/esm-framework';
|
|
4
1
|
import React from 'react';
|
|
5
|
-
import {
|
|
2
|
+
import { Movement } from '@carbon/react/icons';
|
|
3
|
+
import { Button, InlineNotification } from '@carbon/react';
|
|
4
|
+
import { ArrowRightIcon, isDesktop, launchWorkspace, useLayoutType } from '@openmrs/esm-framework';
|
|
6
5
|
import { useInpatientRequest } from '../hooks/useInpatientRequest';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
7
|
import styles from './admission-requests.scss';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const AdmissionRequestsBar: React.FC<AdmissionRequestsBarProps> = ({ location }) => {
|
|
14
|
-
const { inpatientRequests, isLoading, error } = useInpatientRequest(location.uuid);
|
|
15
|
-
const admissionRequests = inpatientRequests?.filter((request) => request.type == 'ADMISSION');
|
|
9
|
+
const AdmissionRequestsBar = () => {
|
|
10
|
+
const { inpatientRequests, isLoading, error } = useInpatientRequest(['ADMIT', 'TRANSFER']);
|
|
16
11
|
const { t } = useTranslation();
|
|
12
|
+
const layout = useLayoutType();
|
|
17
13
|
|
|
18
|
-
if (isLoading) {
|
|
19
|
-
return
|
|
14
|
+
if (isLoading || !inpatientRequests?.length) {
|
|
15
|
+
return null;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
if (error) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
console.error(error);
|
|
20
|
+
return (
|
|
21
|
+
<InlineNotification
|
|
22
|
+
kind="error"
|
|
23
|
+
title={t('errorLoadingPatientAdmissionRequests', 'Error Loading patient admission requests')}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
29
26
|
}
|
|
30
27
|
|
|
31
|
-
return
|
|
28
|
+
return (
|
|
32
29
|
<div className={styles.admissionRequestsContainer}>
|
|
33
30
|
<Movement className={styles.movementIcon} size="24" />
|
|
34
|
-
<span className={styles.content}>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
<span className={styles.content}>
|
|
32
|
+
{t('admissionRequestsCount', '{{count}} admission request(s)', {
|
|
33
|
+
count: inpatientRequests.length,
|
|
34
|
+
})}
|
|
35
|
+
</span>
|
|
36
|
+
<Button
|
|
37
|
+
onClick={() => launchWorkspace('admission-requests-workspace')}
|
|
38
|
+
renderIcon={ArrowRightIcon}
|
|
39
|
+
kind="ghost"
|
|
40
|
+
size={isDesktop(layout) ? 'sm' : 'lg'}>
|
|
41
|
+
{t('manage', 'Manage')}
|
|
42
|
+
</Button>
|
|
40
43
|
</div>
|
|
41
|
-
) : (
|
|
42
|
-
<></>
|
|
43
44
|
);
|
|
44
45
|
};
|
|
45
46
|
|
|
@@ -1,23 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import userEvent from '@testing-library/user-event';
|
|
2
|
-
import { renderWithSwr } from '../../../../tools/test-utils';
|
|
3
3
|
import { screen } from '@testing-library/react';
|
|
4
4
|
import { launchWorkspace } from '@openmrs/esm-framework';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { mockInpatientRequest } from '../../../../__mocks__/ward-patient';
|
|
5
|
+
import { renderWithSwr } from 'tools';
|
|
6
|
+
import { mockInpatientRequest } from '__mocks__';
|
|
8
7
|
import { useInpatientRequest } from '../hooks/useInpatientRequest';
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
jest.mock('@openmrs/esm-framework', () => {
|
|
12
|
-
return {
|
|
13
|
-
...jest.requireActual('@openmrs/esm-framework'),
|
|
14
|
-
launchWorkspace: jest.fn(),
|
|
15
|
-
};
|
|
16
|
-
});
|
|
8
|
+
import AdmissionRequestsBar from './admission-requests-bar.component';
|
|
17
9
|
|
|
18
10
|
jest.mock('../hooks/useInpatientRequest', () => ({
|
|
19
11
|
useInpatientRequest: jest.fn(),
|
|
20
12
|
}));
|
|
13
|
+
|
|
21
14
|
const mockInpatientRequestResponse = {
|
|
22
15
|
error: undefined,
|
|
23
16
|
mutate: jest.fn(),
|
|
@@ -25,18 +18,21 @@ const mockInpatientRequestResponse = {
|
|
|
25
18
|
isLoading: false,
|
|
26
19
|
inpatientRequests: [mockInpatientRequest],
|
|
27
20
|
};
|
|
21
|
+
|
|
28
22
|
jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse);
|
|
29
23
|
|
|
30
24
|
describe('Admission Requests Button', () => {
|
|
31
25
|
it('call launch workspace when clicked on manage button', async () => {
|
|
32
26
|
const user = userEvent.setup();
|
|
33
|
-
renderWithSwr(<AdmissionRequestsBar
|
|
27
|
+
renderWithSwr(<AdmissionRequestsBar />);
|
|
28
|
+
|
|
34
29
|
await user.click(screen.getByRole('button', { name: /manage/i }));
|
|
35
30
|
expect(launchWorkspace).toHaveBeenCalled();
|
|
36
31
|
});
|
|
37
32
|
|
|
38
33
|
it('there should be one admission request', () => {
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
renderWithSwr(<AdmissionRequestsBar />);
|
|
35
|
+
|
|
36
|
+
expect(screen.getByText('1 admission request(s)')).toBeInTheDocument();
|
|
41
37
|
});
|
|
42
38
|
});
|
|
@@ -1,42 +1,37 @@
|
|
|
1
|
-
@use '@carbon/
|
|
2
|
-
@use '@carbon/
|
|
3
|
-
@
|
|
1
|
+
@use '@carbon/type';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
4
4
|
|
|
5
5
|
.admissionRequestsContainer {
|
|
6
|
-
width: fit-content;
|
|
7
|
-
border-left: 2px solid $color-blue-60-2;
|
|
8
6
|
background-color: $color-gray-70;
|
|
9
7
|
display: flex;
|
|
10
8
|
align-items: center;
|
|
11
|
-
padding: spacing.$spacing-02;
|
|
9
|
+
padding: layout.$spacing-02 0 layout.$spacing-02 layout.$spacing-04;
|
|
10
|
+
background-color: #393939;
|
|
11
|
+
|
|
12
|
+
& > button {
|
|
13
|
+
color: #78a9ff;
|
|
14
|
+
|
|
15
|
+
svg {
|
|
16
|
+
fill: #78a9ff !important;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
& > button:hover {
|
|
21
|
+
color: #78a9ff;
|
|
22
|
+
}
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
.movementIcon {
|
|
15
|
-
padding:
|
|
26
|
+
padding: layout.$spacing-02;
|
|
16
27
|
border-radius: 50%;
|
|
17
28
|
fill: $ui-03;
|
|
18
29
|
background-color: $color-blue-60-2;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
.manageButton {
|
|
22
|
-
background-color: transparent;
|
|
23
|
-
border: none;
|
|
24
|
-
color: $inverse-link;
|
|
25
|
-
cursor: pointer;
|
|
26
|
-
margin-left: spacing.$spacing-04;
|
|
27
|
-
&::after {
|
|
28
|
-
content: '→';
|
|
29
|
-
padding: spacing.$spacing-02;
|
|
30
|
-
}
|
|
30
|
+
margin-right: layout.$spacing-03;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.content {
|
|
34
34
|
@include type.type-style('heading-compact-01');
|
|
35
35
|
color: $ui-02;
|
|
36
|
-
margin-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.skeleton {
|
|
40
|
-
height: 20px;
|
|
41
|
-
width: 120px;
|
|
36
|
+
margin-right: layout.$spacing-03;
|
|
42
37
|
}
|