@kenyaemr/esm-ward-app 8.1.1-pre.111 → 8.1.1-pre.116
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 +19 -19
- package/dist/109.js +1 -0
- package/dist/109.js.map +1 -0
- package/dist/125.js +1 -0
- package/dist/125.js.map +1 -0
- package/dist/126.js +1 -0
- package/dist/126.js.map +1 -0
- package/dist/130.js +1 -1
- package/dist/130.js.LICENSE.txt +2 -0
- package/dist/130.js.map +1 -1
- package/dist/146.js +1 -0
- package/dist/146.js.map +1 -0
- package/dist/15.js +1 -0
- package/dist/15.js.map +1 -0
- package/dist/161.js +2 -0
- package/dist/161.js.LICENSE.txt +15 -0
- package/dist/161.js.map +1 -0
- package/dist/2.js +1 -0
- package/dist/2.js.map +1 -0
- package/dist/269.js +1 -1
- package/dist/269.js.map +1 -1
- package/dist/325.js +1 -0
- package/dist/325.js.map +1 -0
- package/dist/372.js +2 -0
- package/dist/372.js.map +1 -0
- package/dist/466.js +1 -1
- package/dist/466.js.map +1 -1
- package/dist/500.js +1 -0
- package/dist/500.js.map +1 -0
- package/dist/53.js +1 -0
- package/dist/53.js.map +1 -0
- package/dist/557.js +1 -0
- package/dist/557.js.map +1 -0
- package/dist/559.js +1 -0
- package/dist/559.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/577.js +1 -1
- package/dist/577.js.map +1 -1
- package/dist/659.js +1 -1
- package/dist/659.js.map +1 -1
- package/dist/701.js +1 -0
- package/dist/701.js.map +1 -0
- package/dist/749.js +1 -1
- package/dist/749.js.map +1 -1
- package/dist/908.js +1 -0
- package/dist/908.js.map +1 -0
- package/dist/922.js +1 -0
- package/dist/922.js.map +1 -0
- package/dist/969.js +1 -0
- package/dist/969.js.map +1 -0
- package/dist/kenyaemr-esm-ward-app.js +1 -1
- package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +300 -107
- 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/mock.tsx +54 -0
- package/package.json +1 -1
- package/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx +37 -0
- package/src/action-menu-buttons/discharge-workspace-siderail.component.tsx +20 -0
- package/src/beds/empty-bed-skeleton.tsx +2 -1
- package/src/beds/empty-bed.scss +0 -4
- package/src/beds/occupied-bed.scss +1 -0
- package/src/config-schema-mother-child-row.ts +26 -0
- package/src/config-schema-pending-items-extension.ts +29 -0
- package/src/config-schema.ts +12 -14
- package/src/hooks/useAdmissionLocation.ts +22 -4
- package/src/hooks/useBeds.ts +3 -4
- package/src/hooks/useConcept.ts +3 -4
- package/src/hooks/useEmrConfiguration.ts +5 -0
- package/src/hooks/useInpatientAdmission.ts +9 -14
- package/src/hooks/useInpatientRequest.ts +4 -15
- package/src/hooks/useLocations.ts +8 -51
- package/src/hooks/useMotherAndChildren.ts +46 -0
- package/src/hooks/useObs.ts +2 -6
- package/src/hooks/usePatientPendingOrders.ts +16 -0
- package/src/hooks/useWardPatientGrouping.ts +25 -0
- package/src/index.ts +50 -3
- package/src/location-selector/location-selector.component.tsx +18 -21
- package/src/routes.json +43 -0
- package/src/types/index.ts +34 -0
- package/src/ward-patient-card/card-rows/admission-request-note.extension.tsx +7 -2
- package/src/ward-patient-card/card-rows/mother-child-row.extension.tsx +110 -0
- package/src/ward-patient-card/card-rows/mother-child-row.scss +22 -0
- package/src/ward-patient-card/card-rows/pending-items-car-row.extension.tsx +50 -0
- package/src/ward-patient-card/row-elements/ward-pateint-skeleton-text.tsx +9 -0
- package/src/ward-patient-card/row-elements/ward-patient-age.tsx +1 -1
- package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +54 -36
- package/src/ward-patient-card/row-elements/ward-patient-identifier.tsx +2 -3
- package/src/ward-patient-card/row-elements/ward-patient-location.tsx +19 -0
- package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +36 -32
- package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +15 -9
- package/src/ward-patient-card/row-elements/ward-patient-pending-order.component.tsx +45 -0
- package/src/ward-patient-card/row-elements/ward-patient-pending-transfer.tsx +38 -0
- package/src/ward-patient-card/row-elements/ward-patient-responsive-tooltip.tsx +32 -0
- package/src/ward-patient-card/ward-patient-card-element.component.tsx +4 -0
- package/src/ward-patient-card/ward-patient-card.component.tsx +21 -14
- package/src/ward-patient-card/ward-patient-card.scss +61 -8
- package/src/ward-patient-card/ward-patient-resource.ts +15 -0
- package/src/ward-view/ward-view.component.tsx +124 -132
- package/src/ward-view/ward-view.resource.ts +121 -1
- package/src/ward-view/ward-view.scss +16 -6
- package/src/ward-view/ward-view.test.tsx +27 -42
- package/src/ward-view-header/admission-requests-bar.component.tsx +8 -7
- package/src/ward-view-header/admission-requests-bar.test.tsx +8 -21
- package/src/ward-view-header/admission-requests.scss +1 -1
- package/src/ward-view-header/ward-metric.component.tsx +24 -0
- package/src/ward-view-header/ward-metric.scss +25 -0
- package/src/ward-view-header/ward-metrics.component.tsx +77 -0
- package/src/ward-view-header/ward-metrics.scss +8 -0
- package/src/ward-view-header/ward-metrics.test.tsx +91 -0
- package/src/ward-view-header/ward-view-header.component.tsx +3 -0
- package/src/ward-view-header/ward-view-header.scss +0 -1
- package/src/ward-workspace/admission-request-card/admission-request-card-actions.component.tsx +11 -3
- package/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx +4 -5
- package/src/ward-workspace/admission-request-card/admission-request-card.scss +8 -4
- package/src/ward-workspace/admission-request-workspace/admission-requests-workspace.test.tsx +8 -3
- package/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +2 -0
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +29 -61
- package/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +37 -21
- package/src/ward-workspace/patient-banner/patient-banner.component.tsx +3 -3
- package/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx +23 -0
- package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient-action-button.extension.tsx +2 -1
- package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.workspace.tsx +7 -5
- package/src/ward-workspace/patient-discharge/patient-discharge.scss +41 -0
- package/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +120 -0
- package/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +40 -30
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +29 -22
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.scss +12 -2
- package/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-swap.workspace.tsx +2 -2
- package/src/ward-workspace/patient-transfer-request-workspace/patient-transfer-request.workspace.tsx +11 -0
- package/src/ward-workspace/ward-patient-notes/form/notes-form.component.tsx +1 -1
- package/src/ward-workspace/ward-patient-notes/form/notes-form.test.tsx +1 -1
- package/src/ward-workspace/ward-patient-notes/history/notes-container.component.tsx +2 -2
- package/src/ward-workspace/ward-patient-notes/notes.resource.ts +5 -7
- package/src/ward-workspace/ward-patient-notes/notes.workspace.tsx +1 -1
- package/src/ward-workspace/ward-patient-notes/types.ts +0 -4
- package/src/ward.resource.ts +6 -0
- package/translations/en.json +18 -1
- package/dist/152.js +0 -1
- package/dist/152.js.map +0 -1
- package/dist/255.js +0 -2
- package/dist/255.js.map +0 -1
- package/dist/303.js +0 -1
- package/dist/303.js.map +0 -1
- package/dist/346.js +0 -1
- package/dist/346.js.map +0 -1
- package/dist/729.js +0 -1
- package/dist/729.js.map +0 -1
- package/dist/76.js +0 -1
- package/dist/76.js.map +0 -1
- package/dist/793.js +0 -2
- package/dist/793.js.LICENSE.txt +0 -5
- package/dist/793.js.map +0 -1
- package/dist/803.js +0 -1
- package/dist/803.js.map +0 -1
- package/dist/960.js +0 -1
- package/dist/960.js.map +0 -1
- /package/dist/{255.js.LICENSE.txt → 372.js.LICENSE.txt} +0 -0
- /package/src/{ward-patient-workspace → ward-workspace/patient-details}/ward-patient.style.scss +0 -0
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
2
1
|
import { InlineNotification } from '@carbon/react';
|
|
2
|
+
import { useAppContext, useDefineAppContext, useFeatureFlag, WorkspaceContainer } from '@openmrs/esm-framework';
|
|
3
|
+
import React, { useEffect, useRef } from 'react';
|
|
3
4
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { WorkspaceContainer, useFeatureFlag } from '@openmrs/esm-framework';
|
|
5
5
|
import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
|
|
6
|
-
import
|
|
6
|
+
import UnassignedPatient from '../beds/unassigned-patient.component';
|
|
7
|
+
import useWardLocation from '../hooks/useWardLocation';
|
|
8
|
+
import { useWardPatientGrouping } from '../hooks/useWardPatientGrouping';
|
|
9
|
+
import { type WardPatient, type WardPatientGroupDetails } from '../types';
|
|
10
|
+
import WardViewHeader from '../ward-view-header/ward-view-header.component';
|
|
7
11
|
import WardBed from './ward-bed.component';
|
|
8
|
-
import { bedLayoutToBed
|
|
12
|
+
import { bedLayoutToBed } from './ward-view.resource';
|
|
9
13
|
import styles from './ward-view.scss';
|
|
10
|
-
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
|
+
import classNames from 'classnames';
|
|
15
15
|
|
|
16
16
|
const WardView = () => {
|
|
17
17
|
const response = useWardLocation();
|
|
18
18
|
const { isLoadingLocation, invalidLocation } = response;
|
|
19
19
|
const { t } = useTranslation();
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
const wardPatientsGroupDetails = useWardPatientGrouping();
|
|
22
|
+
useDefineAppContext<WardPatientGroupDetails>('ward-patients-group', wardPatientsGroupDetails);
|
|
23
|
+
const isVertical = useFeatureFlag('ward-view-vertical-tiling');
|
|
21
24
|
|
|
22
25
|
if (isLoadingLocation) {
|
|
23
26
|
return <></>;
|
|
@@ -28,145 +31,134 @@ const WardView = () => {
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
return (
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
<>
|
|
35
|
+
<div className={classNames(styles.wardView, { [styles.verticalTiling]: isVertical })}>
|
|
36
|
+
<WardViewHeader />
|
|
37
|
+
<WardViewMain />
|
|
35
38
|
</div>
|
|
36
39
|
<WorkspaceContainer overlay contextKey="ward" />
|
|
37
|
-
|
|
40
|
+
</>
|
|
38
41
|
);
|
|
39
42
|
};
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
const WardViewWithBedManagement = () => {
|
|
44
|
+
const WardViewMain = () => {
|
|
43
45
|
const { location } = useWardLocation();
|
|
44
|
-
const { admissionLocation, isLoading: isLoadingLocation, error: errorLoadingLocation } = useAdmissionLocation();
|
|
45
|
-
const { inpatientAdmissions, isLoading: isLoadingPatients, error: errorLoadingPatients } = useInpatientAdmission();
|
|
46
46
|
const { t } = useTranslation();
|
|
47
|
-
const
|
|
48
|
-
const map = new Map<string, InpatientAdmission>();
|
|
49
|
-
for (const inpatientAdmission of inpatientAdmissions ?? []) {
|
|
50
|
-
map.set(inpatientAdmission.patient.uuid, inpatientAdmission);
|
|
51
|
-
}
|
|
52
|
-
return map;
|
|
53
|
-
}, [inpatientAdmissions]);
|
|
47
|
+
const isVertical = useFeatureFlag('ward-view-vertical-tiling');
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
};
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
return <WardBed key={bed.uuid} bed={bed} wardPatients={wardPatients} />;
|
|
78
|
-
});
|
|
49
|
+
const wardPatientsGrouping = useAppContext<WardPatientGroupDetails>('ward-patients-group');
|
|
50
|
+
const { bedLayouts, wardAdmittedPatientsWithBed, wardUnassignedPatientsList } = wardPatientsGrouping ?? {};
|
|
51
|
+
const { isLoading: isLoadingAdmissionLocation, error: errorLoadingAdmissionLocation } =
|
|
52
|
+
wardPatientsGrouping?.admissionLocationResponse ?? {};
|
|
53
|
+
const {
|
|
54
|
+
isLoading: isLoadingInpatientAdmissions,
|
|
55
|
+
error: errorLoadingInpatientAdmissions,
|
|
56
|
+
hasMore: hasMoreInpatientAdmissions,
|
|
57
|
+
loadMore: loadMoreInpatientAdmissions,
|
|
58
|
+
} = wardPatientsGrouping?.inpatientAdmissionResponse ?? {};
|
|
59
|
+
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
|
|
79
60
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
inpatientAdmission,
|
|
96
|
-
inpatientRequest: null,
|
|
97
|
-
}}
|
|
98
|
-
key={inpatientAdmission.patient.uuid}
|
|
99
|
-
/>
|
|
100
|
-
);
|
|
101
|
-
});
|
|
61
|
+
const scrollToLoadMoreTrigger = useRef<HTMLDivElement>(null);
|
|
62
|
+
useEffect(
|
|
63
|
+
function scrollToLoadMore() {
|
|
64
|
+
const observer = new IntersectionObserver(
|
|
65
|
+
(entries) => {
|
|
66
|
+
entries.forEach((entry) => {
|
|
67
|
+
if (entry.isIntersecting) {
|
|
68
|
+
if (hasMoreInpatientAdmissions && !errorLoadingInpatientAdmissions && !isLoadingInpatientAdmissions) {
|
|
69
|
+
loadMoreInpatientAdmissions();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
{ threshold: 1 },
|
|
75
|
+
);
|
|
102
76
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
lowContrast={true}
|
|
110
|
-
title={t('noBedsConfigured', 'No beds configured for this location')}
|
|
111
|
-
/>
|
|
112
|
-
)}
|
|
113
|
-
{wardUnassignedPatients}
|
|
114
|
-
</>
|
|
115
|
-
);
|
|
116
|
-
} else if (isLoadingLocation || isLoadingPatients) {
|
|
117
|
-
return <EmptyBeds />;
|
|
118
|
-
} else if (errorLoadingLocation) {
|
|
119
|
-
return (
|
|
120
|
-
<InlineNotification
|
|
121
|
-
kind="error"
|
|
122
|
-
lowContrast={true}
|
|
123
|
-
title={t('errorLoadingWardLocation', 'Error loading ward location')}
|
|
124
|
-
subtitle={
|
|
125
|
-
errorLoadingLocation?.message ??
|
|
126
|
-
t('invalidWardLocation', 'Invalid ward location: {{location}}', { location: location.display })
|
|
77
|
+
if (scrollToLoadMoreTrigger.current) {
|
|
78
|
+
observer.observe(scrollToLoadMoreTrigger.current);
|
|
79
|
+
}
|
|
80
|
+
return () => {
|
|
81
|
+
if (scrollToLoadMoreTrigger.current) {
|
|
82
|
+
observer.unobserve(scrollToLoadMoreTrigger.current);
|
|
127
83
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<InlineNotification
|
|
133
|
-
kind="error"
|
|
134
|
-
lowContrast={true}
|
|
135
|
-
title={t('errorLoadingPatients', 'Error loading admitted patients')}
|
|
136
|
-
subtitle={errorLoadingPatients?.message}
|
|
137
|
-
/>
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
};
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
[scrollToLoadMoreTrigger, hasMoreInpatientAdmissions, errorLoadingInpatientAdmissions, loadMoreInpatientAdmissions],
|
|
87
|
+
);
|
|
141
88
|
|
|
142
|
-
|
|
143
|
-
const WardViewWithoutBedManagement = () => {
|
|
144
|
-
const { inpatientAdmissions, isLoading: isLoadingPatients, error: errorLoadingPatients } = useInpatientAdmission();
|
|
145
|
-
const { t } = useTranslation();
|
|
89
|
+
if (!wardPatientsGrouping) return <></>;
|
|
146
90
|
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
91
|
+
const wardBeds = bedLayouts?.map((bedLayout) => {
|
|
92
|
+
const { patients } = bedLayout;
|
|
93
|
+
const bed = bedLayoutToBed(bedLayout);
|
|
94
|
+
const wardPatients: WardPatient[] = patients.map((patient): WardPatient => {
|
|
95
|
+
const inpatientAdmission = wardAdmittedPatientsWithBed.get(patient.uuid);
|
|
96
|
+
if (inpatientAdmission) {
|
|
97
|
+
const { patient, visit, currentInpatientRequest } = inpatientAdmission;
|
|
98
|
+
return { patient, visit, bed, inpatientAdmission, inpatientRequest: currentInpatientRequest || null };
|
|
99
|
+
} else {
|
|
100
|
+
// 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
|
|
101
|
+
return {
|
|
102
|
+
patient: patient,
|
|
103
|
+
visit: null,
|
|
104
|
+
bed,
|
|
105
|
+
inpatientAdmission: null, // populate after BED-13
|
|
106
|
+
inpatientRequest: null,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
156
109
|
});
|
|
157
|
-
return
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
110
|
+
return <WardBed key={bed.uuid} bed={bed} wardPatients={wardPatients} />;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const wardUnassignedPatients = wardUnassignedPatientsList?.map((inpatientAdmission) => {
|
|
161
114
|
return (
|
|
162
|
-
<
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
115
|
+
<UnassignedPatient
|
|
116
|
+
wardPatient={{
|
|
117
|
+
patient: inpatientAdmission.patient,
|
|
118
|
+
visit: inpatientAdmission.visit,
|
|
119
|
+
bed: null,
|
|
120
|
+
inpatientAdmission,
|
|
121
|
+
inpatientRequest: null,
|
|
122
|
+
}}
|
|
123
|
+
key={inpatientAdmission.patient.uuid}
|
|
167
124
|
/>
|
|
168
125
|
);
|
|
169
|
-
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div className={classNames(styles.wardViewMain, { [styles.verticalTiling]: isVertical })}>
|
|
130
|
+
{wardBeds}
|
|
131
|
+
{bedLayouts?.length == 0 && isBedManagementModuleInstalled && (
|
|
132
|
+
<InlineNotification
|
|
133
|
+
kind="warning"
|
|
134
|
+
lowContrast={true}
|
|
135
|
+
title={t('noBedsConfigured', 'No beds configured for this location')}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
{wardUnassignedPatients}
|
|
139
|
+
{(isLoadingAdmissionLocation || isLoadingInpatientAdmissions) && <EmptyBeds />}
|
|
140
|
+
{errorLoadingAdmissionLocation && (
|
|
141
|
+
<InlineNotification
|
|
142
|
+
kind="error"
|
|
143
|
+
lowContrast={true}
|
|
144
|
+
title={t('errorLoadingWardLocation', 'Error loading ward location')}
|
|
145
|
+
subtitle={
|
|
146
|
+
errorLoadingAdmissionLocation?.message ??
|
|
147
|
+
t('invalidWardLocation', 'Invalid ward location: {{location}}', { location: location.display })
|
|
148
|
+
}
|
|
149
|
+
/>
|
|
150
|
+
)}
|
|
151
|
+
{errorLoadingInpatientAdmissions && (
|
|
152
|
+
<InlineNotification
|
|
153
|
+
kind="error"
|
|
154
|
+
lowContrast={true}
|
|
155
|
+
title={t('errorLoadingPatients', 'Error loading admitted patients')}
|
|
156
|
+
subtitle={errorLoadingInpatientAdmissions?.message}
|
|
157
|
+
/>
|
|
158
|
+
)}
|
|
159
|
+
<div ref={scrollToLoadMoreTrigger}></div>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
170
162
|
};
|
|
171
163
|
|
|
172
164
|
const EmptyBeds = () => {
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type Patient } from '@openmrs/esm-framework';
|
|
2
|
+
import type {
|
|
3
|
+
AdmissionLocationFetchResponse,
|
|
4
|
+
Bed,
|
|
5
|
+
BedLayout,
|
|
6
|
+
InpatientAdmission,
|
|
7
|
+
InpatientRequest,
|
|
8
|
+
WardMetrics,
|
|
9
|
+
WardPatientGroupDetails,
|
|
10
|
+
} from '../types';
|
|
11
|
+
import type { TFunction } from 'i18next';
|
|
2
12
|
|
|
3
13
|
// the server side has 2 slightly incompatible types for Bed
|
|
4
14
|
export function bedLayoutToBed(bedLayout: BedLayout): Bed {
|
|
@@ -20,5 +30,115 @@ export function filterBeds(admissionLocation: AdmissionLocationFetchResponse): B
|
|
|
20
30
|
const bedLayouts = admissionLocation.bedLayouts
|
|
21
31
|
.filter((bl) => bl.bedId)
|
|
22
32
|
.sort((bedA, bedB) => collator.compare(bedA.bedNumber, bedB.bedNumber));
|
|
33
|
+
|
|
23
34
|
return bedLayouts;
|
|
24
35
|
}
|
|
36
|
+
|
|
37
|
+
//TODO: This implementation will change when the api is ready
|
|
38
|
+
export function getWardMetrics(bedLayouts: BedLayout[], wardPatientGroup: WardPatientGroupDetails): WardMetrics {
|
|
39
|
+
const bedMetrics = {
|
|
40
|
+
patients: '--',
|
|
41
|
+
freeBeds: '--',
|
|
42
|
+
capacity: '--',
|
|
43
|
+
};
|
|
44
|
+
if (bedLayouts == null || bedLayouts.length == 0) return bedMetrics;
|
|
45
|
+
const total = bedLayouts.length;
|
|
46
|
+
const occupiedBeds = bedLayouts.filter((bed) => bed.patients.length>0);
|
|
47
|
+
const patients = occupiedBeds.length;
|
|
48
|
+
const freeBeds = total - patients;
|
|
49
|
+
const capacity = total != 0 ? Math.trunc((wardPatientGroup.totalPatientsCount / total) * 100) : 0;
|
|
50
|
+
return {
|
|
51
|
+
patients: wardPatientGroup?.totalPatientsCount.toString() ?? '--',
|
|
52
|
+
freeBeds: freeBeds.toString(),
|
|
53
|
+
capacity: capacity.toString(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getInpatientAdmissionsUuidMap(inpatientAdmissions: InpatientAdmission[]) {
|
|
58
|
+
const map = new Map<string, InpatientAdmission>();
|
|
59
|
+
for (const inpatientAdmission of inpatientAdmissions ?? []) {
|
|
60
|
+
map.set(inpatientAdmission.patient.uuid, inpatientAdmission);
|
|
61
|
+
}
|
|
62
|
+
return map;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createAndGetWardPatientGrouping(
|
|
66
|
+
inpatientAdmissions: InpatientAdmission[],
|
|
67
|
+
admissionLocation: AdmissionLocationFetchResponse,
|
|
68
|
+
inpatientRequests: InpatientRequest[],
|
|
69
|
+
) {
|
|
70
|
+
const inpatientAdmissionsByPatientUuid = getInpatientAdmissionsUuidMap(inpatientAdmissions);
|
|
71
|
+
|
|
72
|
+
const wardAdmittedPatientsWithBed = new Map<string, InpatientAdmission>();
|
|
73
|
+
const wardUnadmittedPatientsWithBed = new Map<string, Patient>();
|
|
74
|
+
const bedLayouts = admissionLocation && filterBeds(admissionLocation);
|
|
75
|
+
const allWardPatientUuids = new Set<string>();
|
|
76
|
+
let wardPatientPendingCount = 0;
|
|
77
|
+
bedLayouts?.map((bedLayout) => {
|
|
78
|
+
const { patients } = bedLayout;
|
|
79
|
+
patients.map((patient) => {
|
|
80
|
+
const patientAdmittedWithBed = inpatientAdmissionsByPatientUuid.get(patient.uuid);
|
|
81
|
+
allWardPatientUuids.add(patient.uuid);
|
|
82
|
+
if (patientAdmittedWithBed) {
|
|
83
|
+
wardAdmittedPatientsWithBed.set(patient.uuid, patientAdmittedWithBed);
|
|
84
|
+
//count the pending metric
|
|
85
|
+
const dispositionType = patientAdmittedWithBed.currentInpatientRequest?.dispositionType;
|
|
86
|
+
if (dispositionType == 'TRANSFER' || dispositionType == 'DISCHARGE') wardPatientPendingCount++;
|
|
87
|
+
} else {
|
|
88
|
+
wardUnadmittedPatientsWithBed.set(patient.uuid, patient);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const wardUnassignedPatientsList =
|
|
94
|
+
inpatientAdmissions?.filter((inpatientAdmission) => {
|
|
95
|
+
allWardPatientUuids.add(inpatientAdmission.patient.uuid);
|
|
96
|
+
return (
|
|
97
|
+
!wardAdmittedPatientsWithBed.has(inpatientAdmission.patient.uuid) &&
|
|
98
|
+
!wardUnadmittedPatientsWithBed.has(inpatientAdmission.patient.uuid)
|
|
99
|
+
);
|
|
100
|
+
}) ?? [];
|
|
101
|
+
|
|
102
|
+
//excluding inpatientRequests
|
|
103
|
+
const totalPatientsCount = allWardPatientUuids.size;
|
|
104
|
+
|
|
105
|
+
for (const inpatientRequest of inpatientRequests ?? []) {
|
|
106
|
+
allWardPatientUuids.add(inpatientRequest.patient.uuid);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
wardAdmittedPatientsWithBed,
|
|
111
|
+
wardUnadmittedPatientsWithBed,
|
|
112
|
+
wardPatientPendingCount,
|
|
113
|
+
bedLayouts,
|
|
114
|
+
wardUnassignedPatientsList,
|
|
115
|
+
allWardPatientUuids,
|
|
116
|
+
totalPatientsCount,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getWardMetricNameTranslation(name: string, t: TFunction) {
|
|
121
|
+
switch (name) {
|
|
122
|
+
case 'patients':
|
|
123
|
+
return t('patients', 'Patients');
|
|
124
|
+
case 'freeBeds':
|
|
125
|
+
return t('freeBeds', 'Free beds');
|
|
126
|
+
case 'capacity':
|
|
127
|
+
return t('capacity', 'Capacity');
|
|
128
|
+
case 'pendingOut':
|
|
129
|
+
return t('pendingOut', 'Pending out');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getWardMetricValueTranslation(name: string, t: TFunction, value: string) {
|
|
134
|
+
switch (name) {
|
|
135
|
+
case 'patients':
|
|
136
|
+
return t('patientsMetricValue', '{{ metricValue }}', { metricValue: value });
|
|
137
|
+
case 'freeBeds':
|
|
138
|
+
return t('freeBedsMetricValue', '{{ metricValue }}', { metricValue: value });
|
|
139
|
+
case 'capacity':
|
|
140
|
+
return t('capacityMetricValue', '{{ metricValue }} %', { metricValue: value });
|
|
141
|
+
case 'pendingOut':
|
|
142
|
+
return t('pendingOutMetricValue', '{{ metricValue }}', { metricValue: value });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
@use '@carbon/layout';
|
|
2
|
+
@import '~@openmrs/esm-styleguide/src/vars';
|
|
2
3
|
|
|
3
4
|
.wardView {
|
|
4
5
|
background-color: #e4e4e4;
|
|
5
|
-
position: absolute;
|
|
6
|
-
top: var(--omrs-topnav-height);
|
|
7
|
-
bottom: 0;
|
|
8
|
-
left: 0;
|
|
9
|
-
right: 0;
|
|
10
6
|
display: flex;
|
|
11
7
|
flex-direction: column;
|
|
12
8
|
padding: 0 layout.$spacing-05;
|
|
9
|
+
|
|
10
|
+
&.verticalTiling {
|
|
11
|
+
height: calc(100vh - var(--omrs-topnav-height));
|
|
12
|
+
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.wardViewMain {
|
|
@@ -17,5 +17,15 @@
|
|
|
17
17
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
18
18
|
display: grid;
|
|
19
19
|
gap: layout.$spacing-05;
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
&.verticalTiling {
|
|
22
|
+
display: block;
|
|
23
|
+
column-width: 280px;
|
|
24
|
+
overflow-x: auto;
|
|
25
|
+
|
|
26
|
+
> div {
|
|
27
|
+
break-inside: avoid;
|
|
28
|
+
margin-bottom: 10px;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
21
31
|
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
type ConfigSchema,
|
|
3
|
+
getDefaultsFromConfigSchema,
|
|
4
|
+
useAppContext,
|
|
5
|
+
useConfig,
|
|
6
|
+
useFeatureFlag,
|
|
7
|
+
} from '@openmrs/esm-framework';
|
|
2
8
|
import { screen } from '@testing-library/react';
|
|
3
|
-
import
|
|
9
|
+
import React from 'react';
|
|
4
10
|
import { useParams } from 'react-router-dom';
|
|
5
|
-
import { mockAdmissionLocation, mockInpatientAdmissions } from '__mocks__';
|
|
6
11
|
import { renderWithSwr } from 'tools';
|
|
12
|
+
import { mockWardPatientGroupDetails } from '../../mock';
|
|
7
13
|
import { configSchema } from '../config-schema';
|
|
8
|
-
import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
|
|
9
|
-
import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
|
|
10
14
|
import useWardLocation from '../hooks/useWardLocation';
|
|
11
15
|
import WardView from './ward-view.component';
|
|
12
16
|
|
|
@@ -33,29 +37,16 @@ jest.mock('react-router-dom', () => ({
|
|
|
33
37
|
}));
|
|
34
38
|
const mockUseParams = useParams as jest.Mock;
|
|
35
39
|
|
|
36
|
-
jest.
|
|
37
|
-
useAdmissionLocation: jest.fn(),
|
|
38
|
-
}));
|
|
39
|
-
jest.mock('../hooks/useInpatientAdmission', () => ({
|
|
40
|
-
useInpatientAdmission: jest.fn(),
|
|
41
|
-
}));
|
|
40
|
+
jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails());
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
mutate: jest.fn(),
|
|
46
|
-
isValidating: false,
|
|
47
|
-
isLoading: false,
|
|
48
|
-
admissionLocation: mockAdmissionLocation,
|
|
49
|
-
});
|
|
50
|
-
jest.mocked(useInpatientAdmission).mockReturnValue({
|
|
51
|
-
error: undefined,
|
|
52
|
-
mutate: jest.fn(),
|
|
53
|
-
isValidating: false,
|
|
54
|
-
isLoading: false,
|
|
55
|
-
inpatientAdmissions: mockInpatientAdmissions,
|
|
42
|
+
const intersectionObserverMock = () => ({
|
|
43
|
+
observe: () => null,
|
|
56
44
|
});
|
|
45
|
+
window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock);
|
|
57
46
|
|
|
58
47
|
describe('WardView', () => {
|
|
48
|
+
let replacedProperty: jest.ReplaceProperty<any> | null = null;
|
|
49
|
+
|
|
59
50
|
it('renders the session location when no location provided in URL', () => {
|
|
60
51
|
renderWithSwr(<WardView />);
|
|
61
52
|
const header = screen.getByRole('heading', { name: 'mock location' });
|
|
@@ -103,35 +94,29 @@ describe('WardView', () => {
|
|
|
103
94
|
expect(invalidText).toBeInTheDocument();
|
|
104
95
|
});
|
|
105
96
|
|
|
106
|
-
it('
|
|
97
|
+
it('should render warning if backend module installed and no beds configured', () => {
|
|
107
98
|
// override the default response so that no beds are returned
|
|
108
|
-
jest.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
isValidating: false,
|
|
112
|
-
isLoading: false,
|
|
113
|
-
admissionLocation: { ...mockAdmissionLocation, bedLayouts: [] },
|
|
114
|
-
});
|
|
115
|
-
mockUseFeatureFlag.mockReturnValueOnce(true);
|
|
99
|
+
replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
|
|
100
|
+
|
|
101
|
+
mockUseFeatureFlag.mockReturnValue(true);
|
|
116
102
|
|
|
117
103
|
renderWithSwr(<WardView />);
|
|
118
104
|
const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
|
|
119
105
|
expect(noBedsConfiguredForThisLocation).toBeInTheDocument();
|
|
120
106
|
});
|
|
121
107
|
|
|
122
|
-
it('
|
|
108
|
+
it('should not render warning if backend module installed and no beds configured', () => {
|
|
123
109
|
// override the default response so that no beds are returned
|
|
124
|
-
jest.
|
|
125
|
-
|
|
126
|
-
mutate: jest.fn(),
|
|
127
|
-
isValidating: false,
|
|
128
|
-
isLoading: false,
|
|
129
|
-
admissionLocation: { ...mockAdmissionLocation, bedLayouts: [] },
|
|
130
|
-
});
|
|
131
|
-
mockUseFeatureFlag.mockReturnValueOnce(false);
|
|
110
|
+
replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
|
|
111
|
+
mockUseFeatureFlag.mockReturnValue(false);
|
|
132
112
|
|
|
133
113
|
renderWithSwr(<WardView />);
|
|
134
114
|
const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location');
|
|
135
115
|
expect(noBedsConfiguredForThisLocation).not.toBeInTheDocument();
|
|
136
116
|
});
|
|
117
|
+
|
|
118
|
+
afterEach(() => {
|
|
119
|
+
replacedProperty?.restore();
|
|
120
|
+
replacedProperty = null;
|
|
121
|
+
});
|
|
137
122
|
});
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Movement } from '@carbon/react/icons';
|
|
3
1
|
import { Button, InlineNotification } from '@carbon/react';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { Movement } from '@carbon/react/icons';
|
|
3
|
+
import { ArrowRightIcon, isDesktop, launchWorkspace, useAppContext, useLayoutType } from '@openmrs/esm-framework';
|
|
4
|
+
import React from 'react';
|
|
6
5
|
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { type WardPatientGroupDetails } from '../types';
|
|
7
7
|
import styles from './admission-requests.scss';
|
|
8
8
|
|
|
9
9
|
const AdmissionRequestsBar = () => {
|
|
10
|
-
const
|
|
10
|
+
const wardPatientGrouping = useAppContext<WardPatientGroupDetails>('ward-patients-group');
|
|
11
|
+
const { inpatientRequests, isLoading, error } = wardPatientGrouping?.inpatientRequestResponse ?? {};
|
|
11
12
|
const { t } = useTranslation();
|
|
12
13
|
const layout = useLayoutType();
|
|
13
14
|
|
|
@@ -20,7 +21,7 @@ const AdmissionRequestsBar = () => {
|
|
|
20
21
|
return (
|
|
21
22
|
<InlineNotification
|
|
22
23
|
kind="error"
|
|
23
|
-
title={t('errorLoadingPatientAdmissionRequests', 'Error
|
|
24
|
+
title={t('errorLoadingPatientAdmissionRequests', 'Error loading patient admission requests')}
|
|
24
25
|
/>
|
|
25
26
|
);
|
|
26
27
|
}
|
|
@@ -29,7 +30,7 @@ const AdmissionRequestsBar = () => {
|
|
|
29
30
|
<div className={styles.admissionRequestsContainer}>
|
|
30
31
|
<Movement className={styles.movementIcon} size="24" />
|
|
31
32
|
<span className={styles.content}>
|
|
32
|
-
{t('admissionRequestsCount', '{{count}} admission request
|
|
33
|
+
{t('admissionRequestsCount', '{{count}} admission request', {
|
|
33
34
|
count: inpatientRequests.length,
|
|
34
35
|
})}
|
|
35
36
|
</span>
|
|
@@ -1,28 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import userEvent from '@testing-library/user-event';
|
|
1
|
+
import { launchWorkspace, useAppContext } from '@openmrs/esm-framework';
|
|
3
2
|
import { screen } from '@testing-library/react';
|
|
4
|
-
import
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import React from 'react';
|
|
5
5
|
import { renderWithSwr } from 'tools';
|
|
6
|
-
import {
|
|
7
|
-
import { useInpatientRequest } from '../hooks/useInpatientRequest';
|
|
6
|
+
import { mockWardPatientGroupDetails } from '../../mock';
|
|
8
7
|
import AdmissionRequestsBar from './admission-requests-bar.component';
|
|
9
8
|
|
|
10
|
-
jest.
|
|
11
|
-
useInpatientRequest: jest.fn(),
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
const mockInpatientRequestResponse = {
|
|
15
|
-
error: undefined,
|
|
16
|
-
mutate: jest.fn(),
|
|
17
|
-
isValidating: false,
|
|
18
|
-
isLoading: false,
|
|
19
|
-
inpatientRequests: [mockInpatientRequest],
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse);
|
|
9
|
+
jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails());
|
|
23
10
|
|
|
24
11
|
describe('Admission Requests Button', () => {
|
|
25
|
-
it('
|
|
12
|
+
it('should launch workspace when clicked on manage button', async () => {
|
|
26
13
|
const user = userEvent.setup();
|
|
27
14
|
renderWithSwr(<AdmissionRequestsBar />);
|
|
28
15
|
|
|
@@ -30,9 +17,9 @@ describe('Admission Requests Button', () => {
|
|
|
30
17
|
expect(launchWorkspace).toHaveBeenCalled();
|
|
31
18
|
});
|
|
32
19
|
|
|
33
|
-
it('
|
|
20
|
+
it('should have one admission request', () => {
|
|
34
21
|
renderWithSwr(<AdmissionRequestsBar />);
|
|
35
22
|
|
|
36
|
-
expect(screen.getByText('1 admission request
|
|
23
|
+
expect(screen.getByText('1 admission request')).toBeInTheDocument();
|
|
37
24
|
});
|
|
38
25
|
});
|