@openmrs/esm-ward-app 7.0.1-pre.60 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +23 -27
- 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/331.js +1 -0
- package/dist/331.js.map +1 -0
- package/dist/430.js +1 -0
- package/dist/430.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/747.js +1 -0
- package/dist/747.js.map +1 -0
- package/dist/836.js +2 -0
- package/dist/836.js.LICENSE.txt +5 -0
- package/dist/836.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-ward-app.js +1 -1
- package/dist/openmrs-esm-ward-app.js.buildmanifest.json +190 -46
- package/dist/openmrs-esm-ward-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -3
- 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/createDashboardLink.component.tsx +42 -0
- package/src/hooks/useAdmissionLocation.ts +4 -5
- package/src/hooks/useAdmittedPatients.ts +13 -0
- package/src/hooks/useConcept.ts +11 -0
- package/src/hooks/useInpatientRequest.ts +15 -0
- package/src/hooks/useLocation.test.ts +38 -0
- package/src/hooks/useLocation.ts +9 -0
- package/src/hooks/useObs.ts +21 -0
- package/src/hooks/useWardLocation.test.ts +78 -0
- package/src/hooks/useWardLocation.ts +26 -0
- package/src/index.ts +24 -3
- package/src/root.component.tsx +2 -1
- package/src/routes.json +37 -7
- package/src/types/index.ts +40 -2
- package/src/ward-patient-card/row-elements/ward-patient-age.tsx +3 -1
- 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-header-address.tsx +3 -1
- 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 +43 -0
- package/src/ward-patient-card/ward-patient-card.tsx +12 -2
- 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 +79 -0
- package/src/ward-view/ward-bed.component.tsx +6 -9
- package/src/ward-view/ward-view.component.tsx +80 -39
- package/src/ward-view/ward-view.scss +0 -15
- package/src/ward-view/ward-view.test.tsx +38 -25
- package/src/ward-view-header/admission-requests-bar.component.tsx +50 -0
- package/src/ward-view-header/admission-requests-bar.test.tsx +43 -0
- package/src/ward-view-header/admission-requests.scss +33 -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 +32 -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 +6 -1
- 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
package/src/config-schema.ts
CHANGED
|
@@ -15,7 +15,11 @@ const defaultWardPatientCard: WardPatientCardDefinition = {
|
|
|
15
15
|
const defaultPatientAddressFields: Array<keyof PersonAddress> = ['cityVillage', 'country'];
|
|
16
16
|
|
|
17
17
|
export const defaultPatientCardElementConfig: PatientCardElementConfig = {
|
|
18
|
-
|
|
18
|
+
address: {
|
|
19
|
+
addressFields: defaultPatientAddressFields,
|
|
20
|
+
},
|
|
21
|
+
obs: null,
|
|
22
|
+
codedObsTags: null,
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
export const builtInPatientCardElements: PatientCardElementType[] = [
|
|
@@ -23,7 +27,6 @@ export const builtInPatientCardElements: PatientCardElementType[] = [
|
|
|
23
27
|
'patient-name',
|
|
24
28
|
'patient-age',
|
|
25
29
|
'patient-address',
|
|
26
|
-
'admission-time',
|
|
27
30
|
];
|
|
28
31
|
|
|
29
32
|
export const configSchema: ConfigSchema = {
|
|
@@ -43,10 +46,92 @@ export const configSchema: ConfigSchema = {
|
|
|
43
46
|
_validators: [validators.oneOf(patientCardElementTypes)],
|
|
44
47
|
},
|
|
45
48
|
config: {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
address: {
|
|
50
|
+
_description: 'Config for the patientCardElementType "patient-address"',
|
|
51
|
+
addressFields: {
|
|
52
|
+
_type: Type.Array,
|
|
53
|
+
_description: 'defines which address fields to show',
|
|
54
|
+
_default: defaultPatientAddressFields,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
obs: {
|
|
58
|
+
_description: 'Config for the patientCardElementType "patient-obs"',
|
|
59
|
+
conceptUuid: {
|
|
60
|
+
_type: Type.UUID,
|
|
61
|
+
_description: 'Required. Identifies the concept to use to identify the desired observations.',
|
|
62
|
+
_default: null,
|
|
63
|
+
},
|
|
64
|
+
label: {
|
|
65
|
+
_type: Type.String,
|
|
66
|
+
_description:
|
|
67
|
+
"Optional. The custom label or i18n key to the translated label to display. If not provided, defaults to the concept's name. (Note that this can be set to an empty string to not show a label)",
|
|
68
|
+
_default: null,
|
|
69
|
+
},
|
|
70
|
+
labelI18nModule: {
|
|
71
|
+
_type: Type.String,
|
|
72
|
+
_description: 'Optional. The custom module to use for translation of the label',
|
|
73
|
+
_default: null,
|
|
74
|
+
},
|
|
75
|
+
orderBy: {
|
|
76
|
+
_type: Type.String,
|
|
77
|
+
_description:
|
|
78
|
+
"Optional. One of 'ascending' or 'descending', specifying whether to display the obs by obsDatetime ascendingly or descendingly. Defaults to ascending.",
|
|
79
|
+
_default: 'descending',
|
|
80
|
+
_validators: [validators.oneOf(['ascending', 'descending'])],
|
|
81
|
+
},
|
|
82
|
+
limit: {
|
|
83
|
+
_type: Type.Number,
|
|
84
|
+
_description: 'Optional. Limits the max number of obs to display. Unlimited by default.',
|
|
85
|
+
_default: null,
|
|
86
|
+
},
|
|
87
|
+
onlyWithinCurrentVisit: {
|
|
88
|
+
_type: Type.Boolean,
|
|
89
|
+
_description:
|
|
90
|
+
'Optional. If true, limits display to only observations within current visit. Defaults to false',
|
|
91
|
+
_default: false,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
codedObsTags: {
|
|
95
|
+
_description: 'Config for the patientCardElementType "patient-coded-obs-tags"',
|
|
96
|
+
conceptUuid: {
|
|
97
|
+
_type: Type.UUID,
|
|
98
|
+
_description: 'Required. Identifies the concept to use to identify the desired observations.',
|
|
99
|
+
_default: null,
|
|
100
|
+
},
|
|
101
|
+
summaryLabel: {
|
|
102
|
+
_type: Type.String,
|
|
103
|
+
_description: `Optional. The custom label or i18n key to the translated label to display for the summary tag. The summary tag shows the count of the number of answers that are present but not configured to show as their own tags. If not provided, defaults to the name of the concept.`,
|
|
104
|
+
_default: null,
|
|
105
|
+
},
|
|
106
|
+
summaryLabelI18nModule: {
|
|
107
|
+
_type: Type.String,
|
|
108
|
+
_description: 'Optional. The custom module to use for translation of the summary label',
|
|
109
|
+
_default: null,
|
|
110
|
+
},
|
|
111
|
+
summaryLabelColor: {
|
|
112
|
+
_type: Type.String,
|
|
113
|
+
_description:
|
|
114
|
+
'The color of the summary tag. See https://react.carbondesignsystem.com/?path=/docs/components-tag--overview for a list of supported colors',
|
|
115
|
+
_default: null,
|
|
116
|
+
},
|
|
117
|
+
tags: {
|
|
118
|
+
_description: `An array specifying concept sets and color. Observations with coded values that are members of the specified concept sets will be displayed as their own tags with the specified color. Any observation with coded values not belonging to any concept sets specified will be summarized as a count in the summary tag. If a concept set is listed multiple times, the first matching applied-to rule takes precedence.`,
|
|
119
|
+
_type: Type.Array,
|
|
120
|
+
_elements: {
|
|
121
|
+
color: {
|
|
122
|
+
_type: Type.String,
|
|
123
|
+
_description:
|
|
124
|
+
'Color of the tag. See https://react.carbondesignsystem.com/?path=/docs/components-tag--overview for a list of supported colors.',
|
|
125
|
+
},
|
|
126
|
+
appliedToConceptSets: {
|
|
127
|
+
_type: Type.Array,
|
|
128
|
+
_description: `The concept sets which the color applies to. Observations with coded values that are members of the specified concept sets will be displayed as their own tag with the specified color. If an observation's coded value belongs to multiple concept sets, the first matching applied-to rule takes precedence.`,
|
|
129
|
+
_elements: {
|
|
130
|
+
_type: Type.UUID,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
50
135
|
},
|
|
51
136
|
},
|
|
52
137
|
},
|
|
@@ -133,4 +218,85 @@ export interface PatientAddressElementConfig {
|
|
|
133
218
|
addressFields: Array<keyof PersonAddress>;
|
|
134
219
|
}
|
|
135
220
|
|
|
136
|
-
export
|
|
221
|
+
export interface PatientObsElementConfig {
|
|
222
|
+
/**
|
|
223
|
+
* Required. Identifies the concept to use to identify the desired observations.
|
|
224
|
+
*/
|
|
225
|
+
conceptUuid: string;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Optional. The custom label or i18n key to the translated label to display. If not provided, defaults to the concept's name.
|
|
229
|
+
* (Note that this can be set to an empty string to not show a label)
|
|
230
|
+
*/
|
|
231
|
+
label?: string;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Optional. The custom module to use for translation of the label
|
|
235
|
+
*/
|
|
236
|
+
labelI18nModule?: string;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Optional. One of 'ascending' or 'descending', specifying whether to display the obs by obsDatetime ascendingly or descendingly. Defaults to descending.
|
|
240
|
+
*/
|
|
241
|
+
orderBy?: 'ascending' | 'descending';
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Optional. Limits the max number of obs to display. Unlimited by default.
|
|
245
|
+
*/
|
|
246
|
+
limit?: number;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Optional. If true, limits display to only observations within current visit
|
|
250
|
+
*/
|
|
251
|
+
onlyWithinCurrentVisit?: boolean;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface PatientCodedObsTagsElementConfig {
|
|
255
|
+
/**
|
|
256
|
+
* Required. Identifies the concept to use to identify the desired observations.
|
|
257
|
+
*/
|
|
258
|
+
conceptUuid: string;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Optional. The custom label or i18n key to the translated label to display for the summary tag. The summary tag
|
|
262
|
+
* shows the count of the number of answers that are present but not configured to show as their own tags. If not
|
|
263
|
+
* provided, defaults to the name of the concept.
|
|
264
|
+
*/
|
|
265
|
+
summaryLabel?: string;
|
|
266
|
+
/**
|
|
267
|
+
* Optional. The custom module to use for translation of the summary label
|
|
268
|
+
*/
|
|
269
|
+
summaryLabelI18nModule?: string;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* The color of the summary tag.
|
|
273
|
+
* See https://react.carbondesignsystem.com/?path=/docs/components-tag--overview for a list of supported colors
|
|
274
|
+
*/
|
|
275
|
+
summaryLabelColor?: string;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* An array specifying concept sets and color. Observations with coded values that are members of the specified concept sets
|
|
279
|
+
* will be displayed as their own tags with the specified color. Any observation with coded values not belonging to
|
|
280
|
+
* any concept sets specified will be summarized as a count in the summary tag. If a concept set is listed multiple times,
|
|
281
|
+
* the first matching applied-to rule takes precedence.
|
|
282
|
+
*/
|
|
283
|
+
tags: Array<{
|
|
284
|
+
/**
|
|
285
|
+
* Color of the tag. See https://react.carbondesignsystem.com/?path=/docs/components-tag--overview for a list of supported colors.
|
|
286
|
+
*/
|
|
287
|
+
color: string;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The concept sets which the color applies to. Observations with coded values that are members of the specified concept sets
|
|
291
|
+
* will be displayed as their own tag with the specified color.
|
|
292
|
+
* If an observation's coded value belongs to multiple concept sets, the first matching applied-to rule takes precedence.
|
|
293
|
+
*/
|
|
294
|
+
appliedToConceptSets: Array<string>;
|
|
295
|
+
}>;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export type PatientCardElementConfig = {
|
|
299
|
+
address: PatientAddressElementConfig;
|
|
300
|
+
obs: PatientObsElementConfig;
|
|
301
|
+
codedObsTags: PatientCodedObsTagsElementConfig;
|
|
302
|
+
};
|
package/src/constant.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const moduleName = '@openmrs/esm-ward-app';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { ConfigurableLink } from '@openmrs/esm-framework';
|
|
4
|
+
import { BrowserRouter, useLocation } from 'react-router-dom';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
|
|
7
|
+
export interface DashboardLinkConfig {
|
|
8
|
+
name: string;
|
|
9
|
+
title: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// TODO: extract this out into the esm-framework and all 4 copies of this file in this repo?
|
|
13
|
+
|
|
14
|
+
function DashboardExtension({ dashboardLinkConfig }: { dashboardLinkConfig: DashboardLinkConfig }) {
|
|
15
|
+
//
|
|
16
|
+
const { t } = useTranslation();
|
|
17
|
+
const { name, title } = dashboardLinkConfig;
|
|
18
|
+
const location = useLocation();
|
|
19
|
+
const spaBasePath = `${window.spaBase}/home`;
|
|
20
|
+
|
|
21
|
+
const navLink = useMemo(() => {
|
|
22
|
+
const pathArray = location.pathname.split('/home');
|
|
23
|
+
const lastElement = pathArray[pathArray.length - 1];
|
|
24
|
+
return decodeURIComponent(lastElement);
|
|
25
|
+
}, [location.pathname]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<ConfigurableLink
|
|
29
|
+
className={classNames('cds--side-nav__link', {
|
|
30
|
+
'active-left-nav-link': navLink.match(name),
|
|
31
|
+
})}
|
|
32
|
+
to={`${spaBasePath}/${name}`}>
|
|
33
|
+
{t(title)}
|
|
34
|
+
</ConfigurableLink>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const createDashboardLink = (dashboardLinkConfig: DashboardLinkConfig) => () => (
|
|
39
|
+
<BrowserRouter>
|
|
40
|
+
<DashboardExtension dashboardLinkConfig={dashboardLinkConfig} />
|
|
41
|
+
</BrowserRouter>
|
|
42
|
+
);
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
-
import useSWR from 'swr';
|
|
3
2
|
import { type AdmissionLocation } from '../types/index';
|
|
3
|
+
import useSWRImmutable from 'swr/immutable';
|
|
4
4
|
|
|
5
5
|
export function useAdmissionLocation(locationUuid: string, rep: string = 'full') {
|
|
6
|
-
const apiUrl = `${restBaseUrl}/admissionLocation/${locationUuid}
|
|
7
|
-
const { data, ...rest } =
|
|
8
|
-
|
|
6
|
+
const apiUrl = locationUuid ? `${restBaseUrl}/admissionLocation/${locationUuid}?v=${rep}` : null;
|
|
7
|
+
const { data, ...rest } = useSWRImmutable<{ data: AdmissionLocation }, Error>(apiUrl, openmrsFetch);
|
|
9
8
|
return {
|
|
10
|
-
admissionLocation: data?.data
|
|
9
|
+
admissionLocation: data?.data,
|
|
11
10
|
...rest,
|
|
12
11
|
};
|
|
13
12
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import useSWR from 'swr';
|
|
2
|
+
import { type AdmittedPatient } from '../types';
|
|
3
|
+
import { openmrsFetch } from '@openmrs/esm-framework';
|
|
4
|
+
|
|
5
|
+
export function useAdmittedPatients(locationUuid: string) {
|
|
6
|
+
const apiUrl = `/ws/rest/emrapi/inpatient/visits?currentLocation=${locationUuid}`;
|
|
7
|
+
const { data, ...rest } = useSWR<{ data: AdmittedPatient[] }, Error>(apiUrl, openmrsFetch);
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
admittedPatients: data?.data ?? null,
|
|
11
|
+
...rest,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Concept, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWRImmutable from 'swr/immutable';
|
|
3
|
+
|
|
4
|
+
export function useConcepts(uuids: string[], rep = 'default') {
|
|
5
|
+
const apiUrl = `${restBaseUrl}/concept?references=${uuids.join()}&v=${rep}`;
|
|
6
|
+
const { data, ...rest } = useSWRImmutable<{ data: { results: Array<Concept> } }, Error>(apiUrl, openmrsFetch);
|
|
7
|
+
return {
|
|
8
|
+
concepts: data?.data?.results,
|
|
9
|
+
...rest,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import type { InpatientRequest } from '../types';
|
|
3
|
+
import useSWR from 'swr';
|
|
4
|
+
|
|
5
|
+
export function useInpatientRequest(locationUuid: string) {
|
|
6
|
+
const { data, ...rest } = useSWR<FetchResponse<Array<InpatientRequest>>, Error>(
|
|
7
|
+
locationUuid ? `${restBaseUrl}/emrapi/inpatient/admissionRequests?admissionLocation=${locationUuid}` : null,
|
|
8
|
+
openmrsFetch,
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
inpatientRequests: data?.data,
|
|
13
|
+
...rest,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
import useLocation from './useLocation';
|
|
3
|
+
import useSWRImmutable from 'swr/immutable';
|
|
4
|
+
import { restBaseUrl } from '@openmrs/esm-framework';
|
|
5
|
+
|
|
6
|
+
jest.mock('swr/immutable', () =>
|
|
7
|
+
jest.fn().mockReturnValue({
|
|
8
|
+
data: {},
|
|
9
|
+
error: null,
|
|
10
|
+
isValidating: false,
|
|
11
|
+
mutate: jest.fn(),
|
|
12
|
+
}),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const useSWRImmutableMock = useSWRImmutable as jest.Mock;
|
|
16
|
+
|
|
17
|
+
describe('useLocation hook', () => {
|
|
18
|
+
it('should call useLocation', () => {
|
|
19
|
+
const { result } = renderHook(() => useLocation('testUUID'));
|
|
20
|
+
expect(useSWRImmutableMock).toHaveBeenCalledWith(
|
|
21
|
+
`${restBaseUrl}/location/testUUID?v=custom:(display,uuid)`,
|
|
22
|
+
expect.any(Function),
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should call useLocation with given rep', () => {
|
|
27
|
+
const { result } = renderHook(() => useLocation('testUUID', 'custom:(display,uuid,links)'));
|
|
28
|
+
expect(useSWRImmutableMock).toHaveBeenCalledWith(
|
|
29
|
+
`${restBaseUrl}/location/testUUID?v=custom:(display,uuid,links)`,
|
|
30
|
+
expect.any(Function),
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should call useSWR with key=null', () => {
|
|
35
|
+
const { result } = renderHook(() => useLocation(null, 'custom:(display,uuid,links)'));
|
|
36
|
+
expect(useSWRImmutableMock).toHaveBeenCalledWith(null, expect.any(Function));
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Location, openmrsFetch, restBaseUrl, type FetchResponse } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWRImmutable from 'swr/immutable';
|
|
3
|
+
|
|
4
|
+
export default function useLocation(locationUuid: string, rep: string = 'custom:(display,uuid)') {
|
|
5
|
+
return useSWRImmutable<FetchResponse<Location>>(
|
|
6
|
+
locationUuid ? `${restBaseUrl}/location/${locationUuid}?v=${rep}` : null,
|
|
7
|
+
openmrsFetch,
|
|
8
|
+
);
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import useSWR from 'swr';
|
|
2
|
+
import { type Observation } from '../types';
|
|
3
|
+
import { type Link, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
4
|
+
|
|
5
|
+
interface ObsSearchCriteria {
|
|
6
|
+
patient: string;
|
|
7
|
+
concept: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useObs(criteria?: ObsSearchCriteria, representation = 'default') {
|
|
11
|
+
const params = new URLSearchParams({
|
|
12
|
+
...criteria,
|
|
13
|
+
v: representation,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const apiUrl = `${restBaseUrl}/obs?${params}`;
|
|
17
|
+
return useSWR<{ data: { results: Array<Observation>; totalCount: number; links: Array<Link> } }, Error>(
|
|
18
|
+
apiUrl,
|
|
19
|
+
openmrsFetch,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
import { useSession } from '@openmrs/esm-framework';
|
|
3
|
+
import { useParams } from 'react-router-dom';
|
|
4
|
+
import useWardLocation from './useWardLocation';
|
|
5
|
+
import useLocation from './useLocation';
|
|
6
|
+
|
|
7
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
8
|
+
useSession: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
jest.mock('react-router-dom', () => ({
|
|
11
|
+
useParams: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
jest.mock('./useLocation', () => jest.fn());
|
|
14
|
+
|
|
15
|
+
const mockedUseParams = useParams as jest.Mock;
|
|
16
|
+
const mockedUseSession = useSession as jest.Mock;
|
|
17
|
+
const mockedUseLocation = useLocation as jest.Mock;
|
|
18
|
+
|
|
19
|
+
describe('useWardLocation', () => {
|
|
20
|
+
it('returns session location when locationUuidFromUrl is not provided', async () => {
|
|
21
|
+
mockedUseParams.mockReturnValue({});
|
|
22
|
+
mockedUseSession.mockReturnValue({ sessionLocation: 'sessionLocation' });
|
|
23
|
+
mockedUseLocation.mockReturnValue({});
|
|
24
|
+
|
|
25
|
+
const { result } = renderHook(() => useWardLocation());
|
|
26
|
+
|
|
27
|
+
expect(result.current.location).toBe('sessionLocation');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('returns location from useLocation when locationUuidFromUrl is provided', async () => {
|
|
31
|
+
mockedUseParams.mockReturnValue({ locationUuid: 'uuid' });
|
|
32
|
+
mockedUseLocation.mockReturnValue({
|
|
33
|
+
data: { data: 'locationData' },
|
|
34
|
+
isLoading: false,
|
|
35
|
+
error: null,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const { result } = renderHook(() => useWardLocation());
|
|
39
|
+
|
|
40
|
+
expect(result.current.location).toBe('locationData');
|
|
41
|
+
expect(result.current.invalidLocation).toBeFalsy();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('handles loading state correctly', async () => {
|
|
45
|
+
mockedUseParams.mockReturnValue({ locationUuid: 'uuid' });
|
|
46
|
+
mockedUseLocation.mockReturnValue({
|
|
47
|
+
isLoading: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const { result } = renderHook(() => useWardLocation());
|
|
51
|
+
|
|
52
|
+
expect(result.current.isLoadingLocation).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('handles error state correctly when fetching location fails', async () => {
|
|
56
|
+
const error = new Error('Error fetching location');
|
|
57
|
+
mockedUseParams.mockReturnValue({ locationUuid: 'uuid' });
|
|
58
|
+
mockedUseLocation.mockReturnValue({
|
|
59
|
+
error,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const { result } = renderHook(() => useWardLocation());
|
|
63
|
+
|
|
64
|
+
expect(result.current.errorFetchingLocation).toBe(error);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('identifies invalid location correctly', async () => {
|
|
68
|
+
const error = new Error('Error fetching location');
|
|
69
|
+
mockedUseParams.mockReturnValue({ locationUuid: 'uuid' });
|
|
70
|
+
mockedUseLocation.mockReturnValue({
|
|
71
|
+
error,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const { result } = renderHook(() => useWardLocation());
|
|
75
|
+
|
|
76
|
+
expect(result.current.invalidLocation).toBeTruthy();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Location, useLocations, useSession } from '@openmrs/esm-framework';
|
|
2
|
+
import { useParams } from 'react-router-dom';
|
|
3
|
+
import useLocation from './useLocation';
|
|
4
|
+
|
|
5
|
+
export default function useWardLocation(): {
|
|
6
|
+
location: Location;
|
|
7
|
+
isLoadingLocation: boolean;
|
|
8
|
+
errorFetchingLocation: Error;
|
|
9
|
+
invalidLocation: boolean;
|
|
10
|
+
} {
|
|
11
|
+
const { locationUuid: locationUuidFromUrl } = useParams();
|
|
12
|
+
const { sessionLocation } = useSession();
|
|
13
|
+
const {
|
|
14
|
+
data: locationResponse,
|
|
15
|
+
isLoading: isLoadingLocation,
|
|
16
|
+
error: errorFetchingLocation,
|
|
17
|
+
} = useLocation(locationUuidFromUrl ? locationUuidFromUrl : null);
|
|
18
|
+
const invalidLocation = locationUuidFromUrl && errorFetchingLocation;
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
location: locationUuidFromUrl ? locationResponse?.data : sessionLocation,
|
|
22
|
+
isLoadingLocation,
|
|
23
|
+
errorFetchingLocation,
|
|
24
|
+
invalidLocation,
|
|
25
|
+
};
|
|
26
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
defineConfigSchema,
|
|
3
|
+
getAsyncLifecycle,
|
|
4
|
+
getSyncLifecycle,
|
|
5
|
+
registerBreadcrumbs,
|
|
6
|
+
registerFeatureFlag,
|
|
7
|
+
} from '@openmrs/esm-framework';
|
|
2
8
|
import { configSchema } from './config-schema';
|
|
3
9
|
import rootComponent from './root.component';
|
|
10
|
+
import { moduleName } from './constant';
|
|
11
|
+
import WardPatientActionButton from './ward-patient-workspace/ward-patient-action-button.extension';
|
|
12
|
+
import { createDashboardLink } from './createDashboardLink.component';
|
|
4
13
|
|
|
5
14
|
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
|
|
6
15
|
|
|
7
|
-
const moduleName = '@openmrs/esm-ward-app';
|
|
8
|
-
|
|
9
16
|
const options = {
|
|
10
17
|
featureName: 'ward',
|
|
11
18
|
moduleName,
|
|
@@ -13,6 +20,20 @@ const options = {
|
|
|
13
20
|
|
|
14
21
|
export const root = getSyncLifecycle(rootComponent, options);
|
|
15
22
|
|
|
23
|
+
export const wardDashboardLink = getSyncLifecycle(createDashboardLink({ name: 'ward', title: 'wards' }), options);
|
|
24
|
+
|
|
25
|
+
export const admissionRequestWorkspace = getAsyncLifecycle(
|
|
26
|
+
() => import('./ward-workspace/admission-requests-workspace.component'),
|
|
27
|
+
options,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export const wardPatientWorkspace = getAsyncLifecycle(
|
|
31
|
+
() => import('./ward-patient-workspace/ward-patient.workspace'),
|
|
32
|
+
options,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const wardPatientActionButtonExtension = getSyncLifecycle(WardPatientActionButton, options);
|
|
36
|
+
|
|
16
37
|
export function startupApp() {
|
|
17
38
|
registerBreadcrumbs([]);
|
|
18
39
|
defineConfigSchema(moduleName, configSchema);
|
package/src/root.component.tsx
CHANGED
|
@@ -3,7 +3,8 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
|
|
3
3
|
import WardView from './ward-view/ward-view.component';
|
|
4
4
|
|
|
5
5
|
const Root: React.FC = () => {
|
|
6
|
-
|
|
6
|
+
// t('wards', 'Wards')
|
|
7
|
+
const wardViewBasename = window.getOpenmrsSpaBase() + 'home/ward';
|
|
7
8
|
|
|
8
9
|
return (
|
|
9
10
|
<main>
|
package/src/routes.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.openmrs.org/routes.schema.json",
|
|
3
3
|
"backendDependencies": {
|
|
4
|
-
"webservices.rest": "^2.2.0"
|
|
4
|
+
"webservices.rest": "^2.2.0",
|
|
5
|
+
"emrapi": "^2.0.0 || 2.0.0-SNAPSHOT"
|
|
5
6
|
},
|
|
6
7
|
"optionalBackendDependencies":{
|
|
7
8
|
"bedmanagement":{
|
|
8
|
-
"version": "^
|
|
9
|
+
"version": "^6.0.0 || 6.0.0-SNAPSHOT",
|
|
9
10
|
"feature": {
|
|
10
11
|
"flagName": "bedmanagement-module",
|
|
11
12
|
"label":"Ward App Patient Service",
|
|
@@ -14,11 +15,40 @@
|
|
|
14
15
|
}
|
|
15
16
|
},
|
|
16
17
|
"extensions": [
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
{
|
|
19
|
+
"name": "ward-dashboard-link",
|
|
20
|
+
"component": "wardDashboardLink",
|
|
21
|
+
"slot": "homepage-dashboard-slot",
|
|
22
|
+
"meta": {
|
|
23
|
+
"name": "ward",
|
|
24
|
+
"slot": "ward-dashboard-slot",
|
|
25
|
+
"title": "Wards"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
19
28
|
{
|
|
20
29
|
"component": "root",
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
"name": "ward-dashboard",
|
|
31
|
+
"slot": "ward-dashboard-slot"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"component": "wardPatientActionButtonExtension",
|
|
35
|
+
"name": "ward-patient-action-button",
|
|
36
|
+
"slot": "action-menu-ward-patient-items-slot"
|
|
37
|
+
}],
|
|
38
|
+
"workspaces": [
|
|
39
|
+
{
|
|
40
|
+
"name":"admission-requests-workspace",
|
|
41
|
+
"component": "admissionRequestWorkspace",
|
|
42
|
+
"title":"admissionRequests",
|
|
43
|
+
"type":"admission-requests"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "ward-patient-workspace",
|
|
47
|
+
"component": "wardPatientWorkspace",
|
|
48
|
+
"type": "ward",
|
|
49
|
+
"title": "Ward Patient",
|
|
50
|
+
"width": "extra-wide",
|
|
51
|
+
"hasOwnSidebar": true,
|
|
52
|
+
"sidebarFamily": "ward-patient"
|
|
53
|
+
}]
|
|
24
54
|
}
|