@kenyaemr/esm-ward-app 7.0.2-pre.66 → 7.0.2-pre.68
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 +23 -29
- package/dist/130.js +1 -1
- package/dist/130.js.map +1 -1
- package/dist/152.js +1 -0
- package/dist/152.js.map +1 -0
- package/dist/255.js +2 -0
- package/dist/{697.js.LICENSE.txt → 255.js.LICENSE.txt} +0 -6
- package/dist/255.js.map +1 -0
- package/dist/303.js +1 -0
- package/dist/303.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/589.js +1 -0
- package/dist/589.js.map +1 -0
- package/dist/695.js +2 -0
- package/dist/695.js.LICENSE.txt +5 -0
- package/dist/695.js.map +1 -0
- package/dist/kenyaemr-esm-ward-app.js +1 -1
- package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +131 -35
- 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 +1 -1
- package/src/beds/occupied-bed.component.tsx +7 -12
- package/src/beds/occupied-bed.scss +1 -1
- package/src/beds/occupied-bed.test.tsx +14 -5
- package/src/config-schema.ts +173 -7
- package/src/constant.ts +1 -0
- package/src/hooks/useAdmittedPatients.ts +13 -0
- package/src/hooks/useConcept.ts +11 -0
- package/src/hooks/useInpatientRequest.ts +13 -0
- package/src/hooks/useObs.ts +21 -0
- package/src/index.ts +2 -0
- package/src/routes.json +10 -3
- package/src/types/index.ts +40 -2
- package/src/ward-patient-card/row-elements/ward-patient-bed-number.tsx +3 -0
- package/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx +80 -0
- package/src/ward-patient-card/row-elements/ward-patient-obs.resource.ts +52 -0
- package/src/ward-patient-card/row-elements/ward-patient-obs.tsx +58 -0
- package/src/ward-patient-card/ward-patient-card-row.resources.tsx +12 -5
- package/src/ward-patient-card/ward-patient-card.scss +17 -0
- package/src/ward-patient-card/ward-patient-card.tsx +2 -2
- package/src/ward-view/ward-bed.component.tsx +6 -9
- package/src/ward-view/ward-view.component.tsx +77 -31
- package/src/ward-view/ward-view.scss +0 -15
- package/src/ward-view/ward-view.test.tsx +12 -0
- package/src/ward-view-header/admission-requests-bar.component.tsx +46 -0
- package/src/ward-view-header/admission-requests-bar.test.tsx +42 -0
- package/src/ward-view-header/admission-requests.scss +42 -0
- package/src/ward-view-header/ward-view-header.component.tsx +18 -0
- package/src/ward-view-header/ward-view-header.scss +8 -0
- package/src/ward-workspace/admission-request-card.component.tsx +23 -0
- package/src/ward-workspace/admission-request-card.scss +34 -0
- package/src/ward-workspace/admission-request-workspace.test.tsx +38 -0
- package/src/ward-workspace/admission-requests-workspace.component.tsx +21 -0
- package/src/ward-workspace/admission-requests-workspace.scss +13 -0
- package/translations/en.json +1 -0
- package/dist/49.js +0 -1
- package/dist/49.js.map +0 -1
- package/dist/697.js +0 -2
- package/dist/697.js.map +0 -1
|
@@ -13,6 +13,8 @@ import wardPatientAddress from './row-elements/ward-patient-header-address';
|
|
|
13
13
|
import WardPatientName from './row-elements/ward-patient-name';
|
|
14
14
|
import React from 'react';
|
|
15
15
|
import styles from './ward-patient-card.scss';
|
|
16
|
+
import wardPatientObs from './row-elements/ward-patient-obs';
|
|
17
|
+
import wardPatientCodedObsTags from './row-elements/ward-patient-coded-obs-tags';
|
|
16
18
|
|
|
17
19
|
export function usePatientCardRows(location: string) {
|
|
18
20
|
const { wardPatientCards } = useConfig<WardConfigObject>();
|
|
@@ -48,11 +50,11 @@ export function usePatientCardRows(location: string) {
|
|
|
48
50
|
return slot;
|
|
49
51
|
});
|
|
50
52
|
|
|
51
|
-
const WardPatientCardRow: React.FC<WardPatientCardProps> = (
|
|
53
|
+
const WardPatientCardRow: React.FC<WardPatientCardProps> = (props) => {
|
|
52
54
|
return (
|
|
53
|
-
<div className={rowType == 'header' ? styles.wardPatientCardHeader : ''}>
|
|
55
|
+
<div className={styles.wardPatientCardRow + ' ' + (rowType == 'header' ? styles.wardPatientCardHeader : '')}>
|
|
54
56
|
{patientCardElements.map((PatientCardElement, i) => (
|
|
55
|
-
<PatientCardElement
|
|
57
|
+
<PatientCardElement {...props} key={i} />
|
|
56
58
|
))}
|
|
57
59
|
</div>
|
|
58
60
|
);
|
|
@@ -78,8 +80,13 @@ function getPatientCardElementFromDefinition(
|
|
|
78
80
|
case 'patient-age':
|
|
79
81
|
return WardPatientAge;
|
|
80
82
|
case 'patient-address': {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
return wardPatientAddress(config.address);
|
|
84
|
+
}
|
|
85
|
+
case 'patient-obs': {
|
|
86
|
+
return wardPatientObs(config.obs);
|
|
87
|
+
}
|
|
88
|
+
case 'patient-coded-obs-tags': {
|
|
89
|
+
return wardPatientCodedObsTags(config.codedObsTags);
|
|
83
90
|
}
|
|
84
91
|
}
|
|
85
92
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
@use '@carbon/styles/scss/spacing';
|
|
2
2
|
@use '@carbon/styles/scss/type';
|
|
3
|
+
@use '@carbon/colors';
|
|
3
4
|
@import '~@openmrs/esm-styleguide/src/vars';
|
|
4
5
|
|
|
5
6
|
.wardPatientCard {
|
|
@@ -11,9 +12,21 @@
|
|
|
11
12
|
align-items: center;
|
|
12
13
|
gap: spacing.$spacing-02;
|
|
13
14
|
background-color: $ui-02;
|
|
15
|
+
|
|
16
|
+
> .wardPatientCardRow:not(:first-child) {
|
|
17
|
+
border-top: 1px colors.$gray-20 solid;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.wardPatientCardRow {
|
|
22
|
+
width: 100%;
|
|
14
23
|
padding: spacing.$spacing-04;
|
|
15
24
|
}
|
|
16
25
|
|
|
26
|
+
.wardPatientCardRow:empty {
|
|
27
|
+
display: none;
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
.wardPatientCardHeader {
|
|
18
31
|
@extend .dotSeparatedChildren;
|
|
19
32
|
display: flex;
|
|
@@ -59,6 +72,10 @@
|
|
|
59
72
|
gap: spacing.$spacing-02;
|
|
60
73
|
}
|
|
61
74
|
|
|
75
|
+
.wardPatientObsLabel {
|
|
76
|
+
padding-right: spacing.$spacing-02;
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
.dotSeparatedChildren {
|
|
63
80
|
> div:not(div:first-of-type) {
|
|
64
81
|
display: flex;
|
|
@@ -10,8 +10,8 @@ const WardPatientCard: React.FC<WardPatientCardProps> = (props) => {
|
|
|
10
10
|
|
|
11
11
|
return (
|
|
12
12
|
<div className={styles.wardPatientCard}>
|
|
13
|
-
{patientCardRows.map((WardPatientCardRow) => (
|
|
14
|
-
<WardPatientCardRow {...props} />
|
|
13
|
+
{patientCardRows.map((WardPatientCardRow, i) => (
|
|
14
|
+
<WardPatientCardRow key={i} {...props} />
|
|
15
15
|
))}
|
|
16
16
|
</div>
|
|
17
17
|
);
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import { type Patient } from '@openmrs/esm-framework';
|
|
1
|
+
import { type Visit, type Patient } from '@openmrs/esm-framework';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import EmptyBed from '../beds/empty-bed.component';
|
|
4
|
-
import { type Bed } from '../types';
|
|
4
|
+
import { type WardPatient, type Bed } from '../types';
|
|
5
5
|
import OccupiedBed from '../beds/occupied-bed.component';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
export interface WardBedProps {
|
|
7
|
+
wardPatients: Array<WardPatient>;
|
|
8
8
|
bed: Bed;
|
|
9
|
-
patients: Patient[];
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
const WardBed: React.FC<WardBedProps> = ({ bed,
|
|
13
|
-
return
|
|
14
|
-
<OccupiedBed bed={bed} patients={patients} />
|
|
15
|
-
: <EmptyBed bed={bed} />;
|
|
11
|
+
const WardBed: React.FC<WardBedProps> = ({ bed, wardPatients }) => {
|
|
12
|
+
return wardPatients?.length > 0 ? <OccupiedBed bed={bed} wardPatients={wardPatients} /> : <EmptyBed bed={bed} />;
|
|
16
13
|
};
|
|
17
14
|
|
|
18
15
|
export default WardBed;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InlineNotification } from '@carbon/react';
|
|
2
|
-
import { useFeatureFlag, useLocations, useSession, type Location } from '@openmrs/esm-framework';
|
|
3
|
-
import React from 'react';
|
|
2
|
+
import { WorkspaceContainer, useFeatureFlag, useLocations, useSession, type Location } from '@openmrs/esm-framework';
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { useParams } from 'react-router-dom';
|
|
6
6
|
import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
|
|
@@ -8,6 +8,9 @@ import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
|
|
|
8
8
|
import WardBed from './ward-bed.component';
|
|
9
9
|
import { bedLayoutToBed, filterBeds } from './ward-view.resource';
|
|
10
10
|
import styles from './ward-view.scss';
|
|
11
|
+
import WardViewHeader from '../ward-view-header/ward-view-header.component';
|
|
12
|
+
import { type AdmittedPatient, type WardPatient } from '../types';
|
|
13
|
+
import { useAdmittedPatients } from '../hooks/useAdmittedPatients';
|
|
11
14
|
|
|
12
15
|
const WardView = () => {
|
|
13
16
|
const { locationUuid: locationUuidFromUrl } = useParams();
|
|
@@ -23,46 +26,80 @@ const WardView = () => {
|
|
|
23
26
|
return <></>;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
return (
|
|
29
|
+
return invalidLocation ? (
|
|
30
|
+
<InlineNotification
|
|
31
|
+
kind="error"
|
|
32
|
+
lowContrast={true}
|
|
33
|
+
title={t('invalidLocationSpecified', 'Invalid location specified')}
|
|
34
|
+
subtitle={t('unknownLocationUuid', 'Unknown location uuid: {{locationUuidFromUrl}}', {
|
|
35
|
+
locationUuidFromUrl,
|
|
36
|
+
})}
|
|
37
|
+
/>
|
|
38
|
+
) : (
|
|
27
39
|
<div className={styles.wardView}>
|
|
28
|
-
<
|
|
29
|
-
<div className={styles.wardViewHeaderLocationDisplay}>
|
|
30
|
-
<h4>{location?.display}</h4>
|
|
31
|
-
</div>
|
|
32
|
-
<div className={styles.wardViewHeaderAdmissionRequestMenuBar}>{/* TODO: Admission Request bar */}</div>
|
|
33
|
-
</div>
|
|
40
|
+
<WardViewHeader location={location} />
|
|
34
41
|
<div className={styles.wardViewMain}>
|
|
35
|
-
{
|
|
36
|
-
<InlineNotification
|
|
37
|
-
kind="error"
|
|
38
|
-
lowContrast={true}
|
|
39
|
-
title={t('invalidLocationSpecified', 'Invalid location specified')}
|
|
40
|
-
subtitle={t('unknownLocationUuid', 'Unknown location uuid: {{locationUuidFromUrl}}', {
|
|
41
|
-
locationUuidFromUrl,
|
|
42
|
-
})}
|
|
43
|
-
/>
|
|
44
|
-
) : (
|
|
45
|
-
<WardViewByLocation location={location} />
|
|
46
|
-
)}
|
|
42
|
+
<WardViewByLocation location={location} />
|
|
47
43
|
</div>
|
|
44
|
+
<WorkspaceContainer contextKey="ward" />
|
|
48
45
|
</div>
|
|
49
46
|
);
|
|
50
47
|
};
|
|
51
48
|
|
|
52
49
|
const WardViewByLocation = ({ location }: { location: Location }) => {
|
|
53
|
-
const {
|
|
50
|
+
const {
|
|
51
|
+
admissionLocation,
|
|
52
|
+
isLoading: isLoadingLocation,
|
|
53
|
+
error: errorLoadingLocation,
|
|
54
|
+
} = useAdmissionLocation(location.uuid);
|
|
55
|
+
const {
|
|
56
|
+
admittedPatients,
|
|
57
|
+
isLoading: isLoadingPatients,
|
|
58
|
+
error: errorLoadingPatients,
|
|
59
|
+
} = useAdmittedPatients(location.uuid);
|
|
54
60
|
const { t } = useTranslation();
|
|
61
|
+
const admittedPatientsByUuid = useMemo(() => {
|
|
62
|
+
const map = new Map<string, AdmittedPatient>();
|
|
63
|
+
for (const admittedPatient of admittedPatients ?? []) {
|
|
64
|
+
map.set(admittedPatient.patient.uuid, admittedPatient);
|
|
65
|
+
}
|
|
66
|
+
return map;
|
|
67
|
+
}, [admittedPatients]);
|
|
55
68
|
|
|
56
|
-
if (admissionLocation) {
|
|
69
|
+
if (admissionLocation != null && admittedPatients != null) {
|
|
57
70
|
const bedLayouts = filterBeds(admissionLocation);
|
|
58
71
|
|
|
72
|
+
const wardBeds = bedLayouts.map((bedLayout, i) => {
|
|
73
|
+
const { patients } = bedLayout;
|
|
74
|
+
const bed = bedLayoutToBed(bedLayout);
|
|
75
|
+
const wardPatients: WardPatient[] = patients.map((patient) => {
|
|
76
|
+
const admittedPatient = admittedPatientsByUuid.get(patient.uuid);
|
|
77
|
+
|
|
78
|
+
if (admittedPatient) {
|
|
79
|
+
// ideally, we can just use the patient object within admittedPatient
|
|
80
|
+
// and not need the one from bedLayouts, however, the emr api
|
|
81
|
+
// does not respect custom representation right now and does not return
|
|
82
|
+
// all required fields for the patient object
|
|
83
|
+
return { ...admittedPatient, admitted: true };
|
|
84
|
+
}
|
|
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
|
+
});
|
|
97
|
+
return <WardBed key={bed.uuid} bed={bed} wardPatients={wardPatients} />;
|
|
98
|
+
});
|
|
99
|
+
|
|
59
100
|
return (
|
|
60
101
|
<>
|
|
61
|
-
{
|
|
62
|
-
const { patient } = bedLayout;
|
|
63
|
-
const bed = bedLayoutToBed(bedLayout);
|
|
64
|
-
return <WardBed key={bed.uuid} bed={bed} patients={patient ? [patient] : null} />;
|
|
65
|
-
})}
|
|
102
|
+
{wardBeds}
|
|
66
103
|
{bedLayouts.length == 0 && (
|
|
67
104
|
<InlineNotification
|
|
68
105
|
kind="warning"
|
|
@@ -72,7 +109,7 @@ const WardViewByLocation = ({ location }: { location: Location }) => {
|
|
|
72
109
|
)}
|
|
73
110
|
</>
|
|
74
111
|
);
|
|
75
|
-
} else if (
|
|
112
|
+
} else if (isLoadingLocation || isLoadingPatients) {
|
|
76
113
|
return (
|
|
77
114
|
<>
|
|
78
115
|
{Array(20)
|
|
@@ -82,18 +119,27 @@ const WardViewByLocation = ({ location }: { location: Location }) => {
|
|
|
82
119
|
))}
|
|
83
120
|
</>
|
|
84
121
|
);
|
|
85
|
-
} else {
|
|
122
|
+
} else if (errorLoadingLocation) {
|
|
86
123
|
return (
|
|
87
124
|
<InlineNotification
|
|
88
125
|
kind="error"
|
|
89
126
|
lowContrast={true}
|
|
90
127
|
title={t('errorLoadingWardLocation', 'Error loading ward location')}
|
|
91
128
|
subtitle={
|
|
92
|
-
|
|
129
|
+
errorLoadingLocation?.message ??
|
|
93
130
|
t('invalidWardLocation', 'Invalid ward location: {{location}}', { location: location.display })
|
|
94
131
|
}
|
|
95
132
|
/>
|
|
96
133
|
);
|
|
134
|
+
} else {
|
|
135
|
+
return (
|
|
136
|
+
<InlineNotification
|
|
137
|
+
kind="error"
|
|
138
|
+
lowContrast={true}
|
|
139
|
+
title={t('errorLoadingPatients', 'Error loading admitted patients')}
|
|
140
|
+
subtitle={errorLoadingPatients?.message}
|
|
141
|
+
/>
|
|
142
|
+
);
|
|
97
143
|
}
|
|
98
144
|
};
|
|
99
145
|
|
|
@@ -12,21 +12,6 @@
|
|
|
12
12
|
padding: 0 spacing.$spacing-05;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
.wardViewHeader {
|
|
16
|
-
display: flex;
|
|
17
|
-
margin: spacing.$spacing-05 0;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.wardViewHeaderLocationDisplay {
|
|
21
|
-
flex: 1;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.wardViewHeaderAdmissionRequestMenuBar {
|
|
25
|
-
background-color: black;
|
|
26
|
-
color: white;
|
|
27
|
-
padding: 4px 0 4px 12px;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
15
|
.wardViewMain {
|
|
31
16
|
background-color: #e4e4e4;
|
|
32
17
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
@@ -16,6 +16,7 @@ import { configSchema } from '../config-schema';
|
|
|
16
16
|
import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
|
|
17
17
|
import WardView from './ward-view.component';
|
|
18
18
|
import { mockPatientAlice } from '../../../../__mocks__/patient.mock';
|
|
19
|
+
import { useAdmittedPatients } from '../hooks/useAdmittedPatients';
|
|
19
20
|
|
|
20
21
|
jest.replaceProperty(mockPatientAlice.person as Person, 'preferredName', {
|
|
21
22
|
uuid: '',
|
|
@@ -52,6 +53,10 @@ const mockedUseParams = useParams as jest.Mock;
|
|
|
52
53
|
jest.mock('../hooks/useAdmissionLocation', () => ({
|
|
53
54
|
useAdmissionLocation: jest.fn(),
|
|
54
55
|
}));
|
|
56
|
+
jest.mock('../hooks/useAdmittedPatients', () => ({
|
|
57
|
+
useAdmittedPatients: jest.fn(),
|
|
58
|
+
}));
|
|
59
|
+
|
|
55
60
|
jest.mocked(useAdmissionLocation).mockReturnValue({
|
|
56
61
|
error: undefined,
|
|
57
62
|
mutate: jest.fn(),
|
|
@@ -59,6 +64,13 @@ jest.mocked(useAdmissionLocation).mockReturnValue({
|
|
|
59
64
|
isLoading: false,
|
|
60
65
|
admissionLocation: mockAdmissionLocation,
|
|
61
66
|
});
|
|
67
|
+
jest.mocked(useAdmittedPatients).mockReturnValue({
|
|
68
|
+
error: undefined,
|
|
69
|
+
mutate: jest.fn(),
|
|
70
|
+
isValidating: false,
|
|
71
|
+
isLoading: false,
|
|
72
|
+
admittedPatients: [],
|
|
73
|
+
});
|
|
62
74
|
|
|
63
75
|
describe('WardView:', () => {
|
|
64
76
|
it('renders the session location when no location provided in URL', () => {
|
|
@@ -0,0 +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
|
+
import React from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { useInpatientRequest } from '../hooks/useInpatientRequest';
|
|
7
|
+
import styles from './admission-requests.scss';
|
|
8
|
+
|
|
9
|
+
interface AdmissionRequestsBarProps {
|
|
10
|
+
location: Location;
|
|
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');
|
|
16
|
+
const { t } = useTranslation();
|
|
17
|
+
|
|
18
|
+
if (isLoading) {
|
|
19
|
+
return <SkeletonIcon className={styles.skeleton} />;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (error) {
|
|
23
|
+
showNotification({
|
|
24
|
+
kind: 'error',
|
|
25
|
+
title: t('errorLoadingPatientAdmissionRequests', 'Error Loading Patient Admission Requests'),
|
|
26
|
+
description: error.message,
|
|
27
|
+
});
|
|
28
|
+
return <></>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return admissionRequests.length > 0 ? (
|
|
32
|
+
<div className={styles.admissionRequestsContainer}>
|
|
33
|
+
<Movement className={styles.movementIcon} size="24" />
|
|
34
|
+
<span className={styles.content}>{admissionRequests.length} admission requests</span>
|
|
35
|
+
<button
|
|
36
|
+
className={styles.manageButton}
|
|
37
|
+
onClick={() => launchWorkspace('admission-requests-cards', { admissionRequests })}>
|
|
38
|
+
Manage
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
) : (
|
|
42
|
+
<></>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default AdmissionRequestsBar;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import userEvent from '@testing-library/user-event';
|
|
2
|
+
import { renderWithSwr } from '../../../../tools/test-utils';
|
|
3
|
+
import { screen } from '@testing-library/react';
|
|
4
|
+
import { launchWorkspace } from '@openmrs/esm-framework';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import AdmissionRequestsBar from './admission-requests-bar.component';
|
|
7
|
+
import { mockInpatientRequest } from '../../../../__mocks__/ward-patient';
|
|
8
|
+
import { useInpatientRequest } from '../hooks/useInpatientRequest';
|
|
9
|
+
import { mockLocationInpatientWard } from '../../../../__mocks__/locations.mock';
|
|
10
|
+
|
|
11
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
12
|
+
return {
|
|
13
|
+
...jest.requireActual('@openmrs/esm-framework'),
|
|
14
|
+
launchWorkspace: jest.fn(),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
jest.mock('../hooks/useInpatientRequest', () => ({
|
|
19
|
+
useInpatientRequest: jest.fn(),
|
|
20
|
+
}));
|
|
21
|
+
const mockInpatientRequestResponse = {
|
|
22
|
+
error: undefined,
|
|
23
|
+
mutate: jest.fn(),
|
|
24
|
+
isValidating: false,
|
|
25
|
+
isLoading: false,
|
|
26
|
+
inpatientRequests: [mockInpatientRequest],
|
|
27
|
+
};
|
|
28
|
+
jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse);
|
|
29
|
+
|
|
30
|
+
describe('Admission Requests Button', () => {
|
|
31
|
+
it('call launch workspace when clicked on manage button', async () => {
|
|
32
|
+
const user = userEvent.setup();
|
|
33
|
+
renderWithSwr(<AdmissionRequestsBar location={mockLocationInpatientWard} />);
|
|
34
|
+
await user.click(screen.getByRole('button', { name: /manage/i }));
|
|
35
|
+
expect(launchWorkspace).toHaveBeenCalled();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('there should be one admission request', () => {
|
|
39
|
+
const { getByText } = renderWithSwr(<AdmissionRequestsBar location={mockLocationInpatientWard} />);
|
|
40
|
+
expect(getByText('1 admission requests')).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/type';
|
|
2
|
+
@use '@carbon/styles/scss/spacing';
|
|
3
|
+
@import '~@openmrs/esm-styleguide/src/vars';
|
|
4
|
+
|
|
5
|
+
.admissionRequestsContainer {
|
|
6
|
+
width: fit-content;
|
|
7
|
+
border-left: 2px solid $color-blue-60-2;
|
|
8
|
+
background-color: $color-gray-70;
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
padding: spacing.$spacing-02;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.movementIcon {
|
|
15
|
+
padding: spacing.$spacing-02;
|
|
16
|
+
border-radius: 50%;
|
|
17
|
+
fill: $ui-03;
|
|
18
|
+
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
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.content {
|
|
34
|
+
@include type.type-style('heading-compact-01');
|
|
35
|
+
color: $ui-02;
|
|
36
|
+
margin-left: spacing.$spacing-02;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.skeleton {
|
|
40
|
+
height: 20px;
|
|
41
|
+
width: 120px;
|
|
42
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styles from './ward-view-header.scss';
|
|
3
|
+
import AdmissionRequestsBar from './admission-requests-bar.component';
|
|
4
|
+
import { type Location } from '@openmrs/esm-framework';
|
|
5
|
+
|
|
6
|
+
interface WardViewHeaderProps {
|
|
7
|
+
location: Location;
|
|
8
|
+
}
|
|
9
|
+
const WardViewHeader: React.FC<WardViewHeaderProps> = ({ location }) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className={styles.wardViewHeader}>
|
|
12
|
+
<h4>{location.display}</h4>
|
|
13
|
+
<AdmissionRequestsBar location={location} />
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default WardViewHeader;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styles from './admission-request-card.scss';
|
|
3
|
+
import { useParams } from 'react-router-dom';
|
|
4
|
+
import { type Patient } from '@openmrs/esm-framework';
|
|
5
|
+
import { usePatientCardRows } from '../ward-patient-card/ward-patient-card-row.resources';
|
|
6
|
+
|
|
7
|
+
interface AdmissionRequestCardProps {
|
|
8
|
+
patient: Patient;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const AdmissionRequestCard: React.FC<AdmissionRequestCardProps> = ({ patient }) => {
|
|
12
|
+
const { locationUuid } = useParams();
|
|
13
|
+
const admissionPatientCardSlots = usePatientCardRows(locationUuid);
|
|
14
|
+
return (
|
|
15
|
+
<div className={styles.admissionRequestCardHeader}>
|
|
16
|
+
{admissionPatientCardSlots.map((AdmissionPatientCard, i) => (
|
|
17
|
+
<AdmissionPatientCard key={i} patient={patient} bed={null} visit={null} />
|
|
18
|
+
))}
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default AdmissionRequestCard;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/type';
|
|
2
|
+
@use '@carbon/styles/scss/spacing';
|
|
3
|
+
@import '~@openmrs/esm-styleguide/src/vars';
|
|
4
|
+
|
|
5
|
+
.admissionRequestCardHeader {
|
|
6
|
+
background-color: $ui-03;
|
|
7
|
+
padding: spacing.$spacing-03;
|
|
8
|
+
border: 1px solid $color-gray-30;
|
|
9
|
+
height: fit-content;
|
|
10
|
+
color: $color-gray-70;
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
flex-wrap: wrap;
|
|
14
|
+
> div:not(:first-of-type)::before {
|
|
15
|
+
content: '·';
|
|
16
|
+
padding: spacing.$spacing-02;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.patientName,
|
|
21
|
+
.patientGender,
|
|
22
|
+
.patientAge {
|
|
23
|
+
@include type.type-style('heading-01');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.patientName {
|
|
27
|
+
color: $color-gray-100;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.time,
|
|
31
|
+
.city,
|
|
32
|
+
.ward {
|
|
33
|
+
@include type.type-style('body-compact-01');
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { renderWithSwr } from '../../../../tools/test-utils';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import AdmissionRequestsWorkspace from './admission-requests-workspace.component';
|
|
4
|
+
import {
|
|
5
|
+
type ConfigSchema,
|
|
6
|
+
type Person,
|
|
7
|
+
closeWorkspace,
|
|
8
|
+
getDefaultsFromConfigSchema,
|
|
9
|
+
useConfig,
|
|
10
|
+
} from '@openmrs/esm-framework';
|
|
11
|
+
import { configSchema } from '../config-schema';
|
|
12
|
+
import { useInpatientRequest } from '../hooks/useInpatientRequest';
|
|
13
|
+
import { mockInpatientRequest } from '../../../../__mocks__/ward-patient';
|
|
14
|
+
|
|
15
|
+
jest.replaceProperty(mockInpatientRequest.patient.person as Person, 'preferredName', {
|
|
16
|
+
uuid: '',
|
|
17
|
+
givenName: 'Alice',
|
|
18
|
+
familyName: 'Johnson',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
jest.mocked(useConfig).mockReturnValue({
|
|
22
|
+
...getDefaultsFromConfigSchema<ConfigSchema>(configSchema),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
26
|
+
return {
|
|
27
|
+
...jest.requireActual('@openmrs/esm-framework'),
|
|
28
|
+
closeWorkspace: jest.fn(),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('Admission Requests Workspace', () => {
|
|
33
|
+
it('should render a admission request card', () => {
|
|
34
|
+
const { getByText } = renderWithSwr(<AdmissionRequestsWorkspace admissionRequests={[mockInpatientRequest]} />);
|
|
35
|
+
const { givenName, familyName } = mockInpatientRequest.patient.person!.preferredName!;
|
|
36
|
+
expect(getByText(givenName + ' ' + familyName)).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styles from './admission-requests-workspace.scss';
|
|
3
|
+
import AdmissionRequestCard from './admission-request-card.component';
|
|
4
|
+
import { type InpatientRequest } from '../types';
|
|
5
|
+
|
|
6
|
+
interface AdmissionRequestsWorkspaceProps {
|
|
7
|
+
admissionRequests: InpatientRequest[];
|
|
8
|
+
}
|
|
9
|
+
const AdmissionRequestsWorkspace: React.FC<AdmissionRequestsWorkspaceProps> = ({ admissionRequests }) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className={styles.admissionRequestsWorkspaceContainer}>
|
|
12
|
+
<div className={styles.admissionRequestsWorkspace}>
|
|
13
|
+
{admissionRequests.map((admissionRequest) => (
|
|
14
|
+
<AdmissionRequestCard patient={admissionRequest.patient} />
|
|
15
|
+
))}
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default AdmissionRequestsWorkspace;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/type';
|
|
2
|
+
@use '@carbon/styles/scss/spacing';
|
|
3
|
+
@import '~@openmrs/esm-styleguide/src/vars';
|
|
4
|
+
|
|
5
|
+
.admissionRequestsWorkspaceContainer {
|
|
6
|
+
min-height: var(--desktop-workspace-window-height);
|
|
7
|
+
}
|
|
8
|
+
.admissionRequestsWorkspace {
|
|
9
|
+
padding: spacing.$spacing-04;
|
|
10
|
+
display: grid;
|
|
11
|
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
12
|
+
gap: 1rem;
|
|
13
|
+
}
|
package/translations/en.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bedShare": "Bed share",
|
|
3
3
|
"emptyBed": "Empty bed",
|
|
4
|
+
"errorLoadingPatientAdmissionRequests": "Error Loading Patient Admission Requests",
|
|
4
5
|
"errorLoadingWardLocation": "Error loading ward location",
|
|
5
6
|
"invalidLocationSpecified": "Invalid location specified",
|
|
6
7
|
"invalidWardLocation": "Invalid ward location: {{location}}",
|