@kenyaemr/esm-ward-app 8.5.1-pre.37 → 8.5.1-pre.44
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 +12 -12
- package/dist/1160.js +1 -0
- package/dist/1160.js.map +1 -0
- package/dist/1917.js +1 -1
- package/dist/1917.js.map +1 -1
- package/dist/4224.js +2 -0
- package/dist/4224.js.map +1 -0
- package/dist/6009.js +1 -0
- package/dist/6009.js.map +1 -0
- package/dist/6012.js +1 -1
- package/dist/6012.js.map +1 -1
- package/dist/6871.js +1 -1
- package/dist/6871.js.map +1 -1
- package/dist/7059.js +2 -0
- package/dist/7059.js.map +1 -0
- package/dist/7661.js +1 -1
- package/dist/7661.js.map +1 -1
- package/dist/8205.js +1 -1
- package/dist/8205.js.map +1 -1
- package/dist/8308.js +1 -0
- package/dist/8308.js.map +1 -0
- package/dist/9113.js +1 -0
- package/dist/{9880.js.map → 9113.js.map} +1 -1
- package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +162 -111
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -1
- package/src/config-schema.ts +28 -0
- package/src/discharge-printouts/discharge-printout.preview-modal.tsx +40 -0
- package/src/discharge-printouts/discharge-printout.resource.ts +335 -0
- package/src/discharge-printouts/discharge-printouts.scss +125 -0
- package/src/discharge-printouts/discharge-summary.tsx +170 -0
- package/src/discharge-printouts/field-input.tsx +21 -0
- package/src/discharge-printouts/gate-pass-printout.tsx +125 -0
- package/src/discharge-printouts/lab-results.tsx +96 -0
- package/src/hooks/useIpdDischargeEncounter.ts +10 -8
- package/src/index.ts +4 -0
- package/src/routes.json +4 -0
- package/src/ward-patients/admitted-patients.tsx +2 -2
- package/src/ward-patients/discharge-in-patients.tsx +22 -18
- package/src/ward-patients/discharge-patients.tsx +23 -3
- package/src/ward-patients/patient-cells.tsx +4 -10
- package/src/ward-workspace/admit-patient-form-workspace/patient-admission.resources.ts +19 -1
- package/src/ward-workspace/kenya-emr-patient-discharge/patient-discharge.resource.tsx +2 -1
- package/src/ward-workspace/patient-transfer-bed-swap/patient-admit-or-transfer-request-form.component.tsx +1 -1
- package/dist/1352.js +0 -2
- package/dist/1352.js.map +0 -1
- package/dist/3423.js +0 -1
- package/dist/3423.js.map +0 -1
- package/dist/4701.js +0 -2
- package/dist/4701.js.map +0 -1
- package/dist/9880.js +0 -1
- /package/dist/{1352.js.LICENSE.txt → 4224.js.LICENSE.txt} +0 -0
- /package/dist/{4701.js.LICENSE.txt → 7059.js.LICENSE.txt} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenyaemr/esm-ward-app",
|
|
3
|
-
"version": "8.5.1-pre.
|
|
3
|
+
"version": "8.5.1-pre.44",
|
|
4
4
|
"description": "Ward and bed management module for O3",
|
|
5
5
|
"browser": "dist/kenyaemr-esm-ward-app.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"classnames": "^2.3.2",
|
|
43
43
|
"lodash-es": "^4.17.15",
|
|
44
44
|
"react-hook-form": "^7.54.0",
|
|
45
|
+
"react-to-print": "^2.14.13",
|
|
45
46
|
"zod": "3.24.1"
|
|
46
47
|
},
|
|
47
48
|
"peerDependencies": {
|
package/src/config-schema.ts
CHANGED
|
@@ -300,6 +300,11 @@ export const configSchema: ConfigSchema = {
|
|
|
300
300
|
emmergencyDoctorPhoneNumber: '163152AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
301
301
|
admissionDateTime: '1640AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
302
302
|
chiefComplaint: '5219AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
303
|
+
complaint: '160531AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
304
|
+
physicalExamination: '162737AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
305
|
+
dischargeInstruction: '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
306
|
+
drugReaction: '162747AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
307
|
+
reactingDrug: '1193AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
303
308
|
},
|
|
304
309
|
},
|
|
305
310
|
diagnosisConceptSourceUud: {
|
|
@@ -350,6 +355,21 @@ export const configSchema: ConfigSchema = {
|
|
|
350
355
|
_type: Type.UUID,
|
|
351
356
|
_default: '37ce491f-b2dd-4433-b203-efebb8ba1469',
|
|
352
357
|
},
|
|
358
|
+
drugOrderEncounterType: {
|
|
359
|
+
_description: 'Drug Order encounter type Uuid',
|
|
360
|
+
_type: Type.UUID,
|
|
361
|
+
_default: '7df67b83-1b84-4fe2-b1b7-794b4e9bfcc3',
|
|
362
|
+
},
|
|
363
|
+
clinicalConsultationEncounterType: {
|
|
364
|
+
_description: 'Clinical consultation encounter type Uuid',
|
|
365
|
+
_type: Type.UUID,
|
|
366
|
+
_default: '465a92f2-baf8-42e9-9612-53064be868e8',
|
|
367
|
+
},
|
|
368
|
+
doctorsNoteEncounterType: {
|
|
369
|
+
_description: 'Doctors note encounter type Uuid',
|
|
370
|
+
_type: Type.UUID,
|
|
371
|
+
_default: '14b36860-5033-4765-b91b-ace856ab64c2',
|
|
372
|
+
},
|
|
353
373
|
};
|
|
354
374
|
|
|
355
375
|
export interface WardConfigObject {
|
|
@@ -382,11 +402,19 @@ export interface WardConfigObject {
|
|
|
382
402
|
emmergencyDoctorPhoneNumber: string;
|
|
383
403
|
admissionDateTime: string;
|
|
384
404
|
chiefComplaint: string;
|
|
405
|
+
physicalExamination: string;
|
|
406
|
+
dischargeInstruction: string;
|
|
407
|
+
complaint: string;
|
|
408
|
+
drugReaction: string;
|
|
409
|
+
reactingDrug: string;
|
|
385
410
|
};
|
|
386
411
|
diagnosisConceptSourceUud: string;
|
|
387
412
|
inpatientAdmissionEncounterProviderRole: string;
|
|
388
413
|
mortuaryAdmissionLoctionTagUuid: string;
|
|
389
414
|
dailyBedFeeBillableService: string;
|
|
415
|
+
drugOrderEncounterType: string;
|
|
416
|
+
clinicalConsultationEncounterType: string;
|
|
417
|
+
doctorsNoteEncounterType: string;
|
|
390
418
|
}
|
|
391
419
|
|
|
392
420
|
export interface PendingItemsElementConfig {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ModalHeader , ModalBody , ModalFooter , ButtonSet , Button } from '@carbon/react';
|
|
2
|
+
import React, { type FC, type ReactNode, useRef } from 'react';
|
|
3
|
+
import styles from './discharge-printouts.scss';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { useReactToPrint } from 'react-to-print';
|
|
6
|
+
|
|
7
|
+
type DischargePrintoutPreviewModalProps = {
|
|
8
|
+
printout: ReactNode;
|
|
9
|
+
onClose: () => void;
|
|
10
|
+
};
|
|
11
|
+
const DischargePrintoutPreviewModal: FC<DischargePrintoutPreviewModalProps> = ({ onClose, printout }) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
14
|
+
|
|
15
|
+
const handlePrint = useReactToPrint({
|
|
16
|
+
content: () => ref.current,
|
|
17
|
+
});
|
|
18
|
+
return (
|
|
19
|
+
<React.Fragment>
|
|
20
|
+
<ModalHeader className={styles.sectionHeader} closeModal={onClose}>
|
|
21
|
+
{t('printPreview', 'Print Preview')}
|
|
22
|
+
</ModalHeader>
|
|
23
|
+
<ModalBody>
|
|
24
|
+
<div ref={ref}>{printout}</div>
|
|
25
|
+
</ModalBody>
|
|
26
|
+
<ModalFooter>
|
|
27
|
+
<ButtonSet className={styles.buttonSet}>
|
|
28
|
+
<Button kind="secondary" onClick={onClose} className={styles.button}>
|
|
29
|
+
{t('cancel', 'Cancel')}
|
|
30
|
+
</Button>
|
|
31
|
+
<Button kind="primary" onClick={handlePrint} className={styles.button}>
|
|
32
|
+
{t('print', 'Print')}
|
|
33
|
+
</Button>
|
|
34
|
+
</ButtonSet>
|
|
35
|
+
</ModalFooter>
|
|
36
|
+
</React.Fragment>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default DischargePrintoutPreviewModal;
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Encounter,
|
|
3
|
+
type FetchResponse,
|
|
4
|
+
type Obs,
|
|
5
|
+
openmrsFetch,
|
|
6
|
+
type OpenmrsResource,
|
|
7
|
+
restBaseUrl,
|
|
8
|
+
useConfig,
|
|
9
|
+
type Visit,
|
|
10
|
+
} from '@openmrs/esm-framework';
|
|
11
|
+
import { type Order } from '@openmrs/esm-patient-common-lib';
|
|
12
|
+
import { useMemo } from 'react';
|
|
13
|
+
import useSWR from 'swr';
|
|
14
|
+
import { type WardConfigObject } from '../config-schema';
|
|
15
|
+
import { useEncounterDetails } from '../hooks/useIpdDischargeEncounter';
|
|
16
|
+
export const DATE_FORMART = 'DD/MM/YYYY';
|
|
17
|
+
export const TIME_FORMART = 'hh:mm A';
|
|
18
|
+
|
|
19
|
+
const labConceptRepresentation =
|
|
20
|
+
'custom:(uuid,display,name,datatype,set,answers,hiNormal,hiAbsolute,hiCritical,lowNormal,lowAbsolute,lowCritical,units,allowDecimal,' +
|
|
21
|
+
'setMembers:(uuid,display,answers,datatype,hiNormal,hiAbsolute,hiCritical,lowNormal,lowAbsolute,lowCritical,units,allowDecimal,set,setMembers:(uuid)))';
|
|
22
|
+
|
|
23
|
+
export const usePatientDiagnosis = (encounterUuid: string) => {
|
|
24
|
+
const customRepresentation =
|
|
25
|
+
'custom:(uuid,display,visit:(uuid,encounters:(uuid,diagnoses:(uuid,display,certainty,diagnosis:(coded:(uuid,display))))))';
|
|
26
|
+
const url = `${restBaseUrl}/encounter/${encounterUuid}?v=${customRepresentation}`;
|
|
27
|
+
|
|
28
|
+
const { data, error, isLoading } = useSWR<FetchResponse<{ visit: Visit }>>(url, openmrsFetch);
|
|
29
|
+
|
|
30
|
+
const diagnoses = useMemo(() => {
|
|
31
|
+
return (
|
|
32
|
+
data?.data?.visit?.encounters?.flatMap(
|
|
33
|
+
(encounter) =>
|
|
34
|
+
encounter.diagnoses.map((diagnosis) => ({
|
|
35
|
+
id: diagnosis.diagnosis.coded.uuid,
|
|
36
|
+
text: diagnosis.display,
|
|
37
|
+
certainty: diagnosis.certainty,
|
|
38
|
+
})) || [],
|
|
39
|
+
) || []
|
|
40
|
+
);
|
|
41
|
+
}, [data]);
|
|
42
|
+
const display = useMemo(() => {
|
|
43
|
+
if (diagnoses?.length)
|
|
44
|
+
return diagnoses
|
|
45
|
+
.map((d) => d.text)
|
|
46
|
+
.join(', ')
|
|
47
|
+
?.toLowerCase();
|
|
48
|
+
return null;
|
|
49
|
+
}, [diagnoses]);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
error,
|
|
53
|
+
isLoading,
|
|
54
|
+
diagnoses: (diagnoses ?? []) as Array<{ id: string; text: string; certainty: string }>,
|
|
55
|
+
display,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function usePatientOrders(dischargeEncounterUuId: string) {
|
|
60
|
+
const rep =
|
|
61
|
+
'custom:(uuid,display,location:(display),encounterDatetime,visit:(uuid,display,encounters:(uuid,display,encounterType:(uuid,display),encounterDatetime,orders,obs)))';
|
|
62
|
+
const { encounter, error, isLoading } = useEncounterDetails(dischargeEncounterUuId, rep);
|
|
63
|
+
const {
|
|
64
|
+
drugOrderEncounterType,
|
|
65
|
+
clinicalConsultationEncounterType,
|
|
66
|
+
ipdDischargeEncounterTypeUuid,
|
|
67
|
+
doctorsNoteEncounterType,
|
|
68
|
+
conceptUuidForWardAdmission: concepts,
|
|
69
|
+
} = useConfig<WardConfigObject>();
|
|
70
|
+
|
|
71
|
+
const orderEncounters = useMemo(() => {
|
|
72
|
+
const encounters = (encounter?.visit?.encounters ?? []).filter(
|
|
73
|
+
(enc) => enc.encounterType.uuid === drugOrderEncounterType,
|
|
74
|
+
);
|
|
75
|
+
return encounters;
|
|
76
|
+
}, [encounter, drugOrderEncounterType]);
|
|
77
|
+
|
|
78
|
+
const clinicalConsultationEncounters = useMemo(() => {
|
|
79
|
+
const encounters = (encounter?.visit?.encounters ?? []).filter(
|
|
80
|
+
(enc) => enc.encounterType.uuid === clinicalConsultationEncounterType,
|
|
81
|
+
);
|
|
82
|
+
return encounters;
|
|
83
|
+
}, [encounter, clinicalConsultationEncounterType]);
|
|
84
|
+
|
|
85
|
+
const doctorsNoteEncounters = useMemo(() => {
|
|
86
|
+
const encounters = (encounter?.visit?.encounters ?? []).filter(
|
|
87
|
+
(enc) => enc.encounterType.uuid === doctorsNoteEncounterType,
|
|
88
|
+
);
|
|
89
|
+
return encounters;
|
|
90
|
+
}, [encounter, doctorsNoteEncounterType]);
|
|
91
|
+
|
|
92
|
+
const { drugorder, testorder } = useMemo<{ drugorder: Array<Order>; testorder: Array<Order> }>(
|
|
93
|
+
() =>
|
|
94
|
+
orderEncounters.reduce(
|
|
95
|
+
(prev, curr) => {
|
|
96
|
+
if (curr.orders?.length) {
|
|
97
|
+
prev['drugorder'].push(...curr.orders.filter((order) => order.type === 'drugorder'));
|
|
98
|
+
prev['testorder'].push(...curr.orders.filter((order) => order.type === 'testorder'));
|
|
99
|
+
}
|
|
100
|
+
return prev;
|
|
101
|
+
},
|
|
102
|
+
{ drugorder: [], testorder: [] },
|
|
103
|
+
),
|
|
104
|
+
[orderEncounters],
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const complaints = useMemo(() => {
|
|
108
|
+
const obs = getComplaintsObs(doctorsNoteEncounters, concepts.complaint, concepts.chiefComplaint);
|
|
109
|
+
if (obs.length) return obs.map((o) => getObservationDisplayValue(o.value, null)).join(', ');
|
|
110
|
+
return null;
|
|
111
|
+
}, [doctorsNoteEncounters, concepts]);
|
|
112
|
+
|
|
113
|
+
const drugReactions = useMemo(() => {
|
|
114
|
+
const obs = getDrugReactions(clinicalConsultationEncounters, concepts.drugReaction, concepts.reactingDrug);
|
|
115
|
+
if (obs.length)
|
|
116
|
+
return obs
|
|
117
|
+
.map((o) => getObservationDisplayValue(o.value, null))
|
|
118
|
+
.join(', ')
|
|
119
|
+
.toLowerCase();
|
|
120
|
+
return null;
|
|
121
|
+
}, [clinicalConsultationEncounters, concepts]);
|
|
122
|
+
|
|
123
|
+
const ipdDischargeEncounter = useMemo<Encounter>(() => {
|
|
124
|
+
const encounters = (encounter?.visit?.encounters ?? []).find(
|
|
125
|
+
(enc) => enc.encounterType.uuid === ipdDischargeEncounterTypeUuid,
|
|
126
|
+
);
|
|
127
|
+
return encounters;
|
|
128
|
+
}, [encounter, ipdDischargeEncounterTypeUuid]);
|
|
129
|
+
|
|
130
|
+
const physicalExaminations = useMemo(() => {
|
|
131
|
+
const obs = doctorsNoteEncounters.reduce<Array<Obs>>((prev, cur) => {
|
|
132
|
+
if (cur.obs?.length) {
|
|
133
|
+
const obs = cur.obs.filter((o) => o.concept.uuid === concepts.physicalExamination);
|
|
134
|
+
prev.push(...obs);
|
|
135
|
+
}
|
|
136
|
+
return prev;
|
|
137
|
+
}, []);
|
|
138
|
+
return obs?.map((ob) => getObservationDisplayValue(ob.value, null)?.toLowerCase());
|
|
139
|
+
}, [doctorsNoteEncounters, concepts.physicalExamination]);
|
|
140
|
+
|
|
141
|
+
const dischargeinstructions = useMemo(() => {
|
|
142
|
+
const instructionObsValue = ipdDischargeEncounter?.obs?.find(
|
|
143
|
+
(o) => o.concept.uuid === concepts.dischargeInstruction,
|
|
144
|
+
)?.value;
|
|
145
|
+
return getObservationDisplayValue(instructionObsValue, null);
|
|
146
|
+
}, [ipdDischargeEncounter, concepts.dischargeInstruction]);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
isLoading,
|
|
150
|
+
error,
|
|
151
|
+
drugOrders: drugorder,
|
|
152
|
+
testOrders: testorder,
|
|
153
|
+
orderEncounters,
|
|
154
|
+
clinicalConsultationEncounters,
|
|
155
|
+
complaints,
|
|
156
|
+
drugReactions,
|
|
157
|
+
dischargeinstructions,
|
|
158
|
+
ipdDischargeEncounter,
|
|
159
|
+
physicalExaminations,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getComplaintsObs(
|
|
164
|
+
encounters: Array<Encounter>,
|
|
165
|
+
complaintsConceptUuid: string,
|
|
166
|
+
chiefComplainConceptUuid: string,
|
|
167
|
+
) {
|
|
168
|
+
return encounters.reduce<Array<Obs>>((prev, curr) => {
|
|
169
|
+
if (curr.obs.length) {
|
|
170
|
+
const complaintObs = curr.obs
|
|
171
|
+
.filter((o) => o.concept.uuid === complaintsConceptUuid && o.groupMembers)
|
|
172
|
+
.flatMap((o) => o.groupMembers)
|
|
173
|
+
.filter((o) => o.concept.uuid === chiefComplainConceptUuid);
|
|
174
|
+
prev.push(...complaintObs);
|
|
175
|
+
}
|
|
176
|
+
return prev;
|
|
177
|
+
}, []);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getDrugReactions(encounters: Array<Encounter>, drugReactionsConceptUuid: string, drugConceptUuid: string) {
|
|
181
|
+
return encounters.reduce<Array<Obs>>((prev, curr) => {
|
|
182
|
+
if (curr.obs.length) {
|
|
183
|
+
const complaintObs = curr.obs
|
|
184
|
+
.filter((o) => o.concept.uuid === drugReactionsConceptUuid && o.groupMembers)
|
|
185
|
+
.flatMap((o) => o.groupMembers)
|
|
186
|
+
.filter((o) => o.concept.uuid === drugConceptUuid);
|
|
187
|
+
prev.push(...complaintObs);
|
|
188
|
+
}
|
|
189
|
+
return prev;
|
|
190
|
+
}, []);
|
|
191
|
+
}
|
|
192
|
+
type NullableNumber = number | null | undefined;
|
|
193
|
+
export type ObservationValue =
|
|
194
|
+
| OpenmrsResource // coded
|
|
195
|
+
| number // numeric
|
|
196
|
+
| string // text or misc
|
|
197
|
+
| boolean
|
|
198
|
+
| null;
|
|
199
|
+
|
|
200
|
+
export interface LabOrderConcept {
|
|
201
|
+
uuid: string;
|
|
202
|
+
display: string;
|
|
203
|
+
name?: ConceptName;
|
|
204
|
+
datatype: LabOrderConceptDatatype;
|
|
205
|
+
set: boolean;
|
|
206
|
+
version: string;
|
|
207
|
+
retired: boolean;
|
|
208
|
+
descriptions: Array<LabOrderConceptDescription>;
|
|
209
|
+
mappings?: Array<LabOrderConceptMapping>;
|
|
210
|
+
answers?: Array<OpenmrsResource>;
|
|
211
|
+
setMembers?: Array<LabOrderConcept>;
|
|
212
|
+
hiNormal?: NullableNumber;
|
|
213
|
+
hiAbsolute?: NullableNumber;
|
|
214
|
+
hiCritical?: NullableNumber;
|
|
215
|
+
lowNormal?: NullableNumber;
|
|
216
|
+
lowAbsolute?: NullableNumber;
|
|
217
|
+
lowCritical?: NullableNumber;
|
|
218
|
+
allowDecimal?: boolean | null;
|
|
219
|
+
units?: string;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface ConceptName {
|
|
223
|
+
display: string;
|
|
224
|
+
uuid: string;
|
|
225
|
+
name: string;
|
|
226
|
+
locale: string;
|
|
227
|
+
localePreferred: boolean;
|
|
228
|
+
conceptNameType: string;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface LabOrderConceptDatatype {
|
|
232
|
+
uuid: string;
|
|
233
|
+
display: string;
|
|
234
|
+
name: string;
|
|
235
|
+
description: string;
|
|
236
|
+
hl7Abbreviation: string;
|
|
237
|
+
retired: boolean;
|
|
238
|
+
resourceVersion: string;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface LabOrderConceptDescription {
|
|
242
|
+
display: string;
|
|
243
|
+
uuid: string;
|
|
244
|
+
description: string;
|
|
245
|
+
locale: string;
|
|
246
|
+
resourceVersion: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface LabOrderConceptMapping {
|
|
250
|
+
display: string;
|
|
251
|
+
uuid: string;
|
|
252
|
+
conceptReferenceTerm: OpenmrsResource;
|
|
253
|
+
conceptMapType: OpenmrsResource;
|
|
254
|
+
resourceVersion: string;
|
|
255
|
+
}
|
|
256
|
+
export function useOrderConceptByUuid(uuid: string) {
|
|
257
|
+
const apiUrl = `${restBaseUrl}/concept/${uuid}?v=${labConceptRepresentation}`;
|
|
258
|
+
|
|
259
|
+
const { data, error, isLoading, isValidating, mutate } = useSWR<LabOrderConcept, Error>(uuid, fetchAllSetMembers);
|
|
260
|
+
/**
|
|
261
|
+
* We are fetching 2 levels of set members at one go.
|
|
262
|
+
*/
|
|
263
|
+
|
|
264
|
+
const results = useMemo(
|
|
265
|
+
() => ({
|
|
266
|
+
concept: data,
|
|
267
|
+
isLoading,
|
|
268
|
+
error,
|
|
269
|
+
isValidating,
|
|
270
|
+
mutate,
|
|
271
|
+
}),
|
|
272
|
+
[data, error, isLoading, isValidating, mutate],
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return results;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* This function fetches all the different levels of set members for a concept,
|
|
280
|
+
* while fetching 2 levels of set members at one go.
|
|
281
|
+
* @param conceptUuid - The UUID of the concept to fetch.
|
|
282
|
+
* @returns The concept with all its set members and their set members.
|
|
283
|
+
*/
|
|
284
|
+
async function fetchAllSetMembers(conceptUuid: string): Promise<LabOrderConcept> {
|
|
285
|
+
const conceptResponse = await openmrsFetch<LabOrderConcept>(getUrlForConcept(conceptUuid));
|
|
286
|
+
let concept = conceptResponse.data;
|
|
287
|
+
const secondLevelSetMembers = concept.set
|
|
288
|
+
? concept.setMembers
|
|
289
|
+
.map((member) => (member.set ? member.setMembers.map((lowerMember) => lowerMember.uuid) : []))
|
|
290
|
+
.flat()
|
|
291
|
+
: [];
|
|
292
|
+
if (secondLevelSetMembers.length > 0) {
|
|
293
|
+
const concepts = await Promise.all(secondLevelSetMembers.map((uuid) => fetchAllSetMembers(uuid)));
|
|
294
|
+
const uuidMap = concepts.reduce(
|
|
295
|
+
(acc, c) => {
|
|
296
|
+
acc[c.uuid] = c;
|
|
297
|
+
return acc;
|
|
298
|
+
},
|
|
299
|
+
{} as Record<string, LabOrderConcept>,
|
|
300
|
+
);
|
|
301
|
+
concept.setMembers = concept.setMembers.map((member) => {
|
|
302
|
+
if (member.set) {
|
|
303
|
+
member.setMembers = member.setMembers.map((lowerMember) => uuidMap[lowerMember.uuid]);
|
|
304
|
+
}
|
|
305
|
+
return member;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return concept;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function getUrlForConcept(conceptUuid: string) {
|
|
313
|
+
return `${restBaseUrl}/concept/${conceptUuid}?v=${labConceptRepresentation}`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export const getObservationDisplayValue = (value: ObservationValue, defaultValue: string | null = '--'): string => {
|
|
317
|
+
if (value == null) return defaultValue;
|
|
318
|
+
|
|
319
|
+
switch (typeof value) {
|
|
320
|
+
case 'string':
|
|
321
|
+
return value.trim() || defaultValue;
|
|
322
|
+
case 'number':
|
|
323
|
+
return String(value);
|
|
324
|
+
case 'object':
|
|
325
|
+
if ('display' in value && typeof value.display === 'string') {
|
|
326
|
+
return value.display.trim() || defaultValue;
|
|
327
|
+
}
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return defaultValue;
|
|
332
|
+
};
|
|
333
|
+
export const getTreatmentDisplayText = (order: Order): string => {
|
|
334
|
+
return `${order.drug?.display} ${order.frequency.display} for ${order.duration} ${order.durationUnits.display}`;
|
|
335
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
@use '@carbon/type';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/colors';
|
|
4
|
+
|
|
5
|
+
.sectionHeader {
|
|
6
|
+
@include type.type-style('heading-02');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.button {
|
|
10
|
+
height: layout.$spacing-10;
|
|
11
|
+
display: flex;
|
|
12
|
+
align-content: flex-start;
|
|
13
|
+
align-items: baseline;
|
|
14
|
+
min-width: 20%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.buttonSet {
|
|
18
|
+
padding: 0rem;
|
|
19
|
+
margin-top: layout.$spacing-05;
|
|
20
|
+
display: flex;
|
|
21
|
+
justify-content: space-between;
|
|
22
|
+
width: 100%;
|
|
23
|
+
margin-bottom: layout.$spacing-05;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.content {
|
|
27
|
+
padding: 6rem;
|
|
28
|
+
margin: 0 auto;
|
|
29
|
+
max-width: 800px;
|
|
30
|
+
font-size: 14px;
|
|
31
|
+
line-height: 1.5;
|
|
32
|
+
color: #000;
|
|
33
|
+
background-color: #fff;
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
gap: layout.$spacing-06;
|
|
37
|
+
|
|
38
|
+
// Print-specific tweaks
|
|
39
|
+
@media print {
|
|
40
|
+
padding: 0.5in;
|
|
41
|
+
font-size: 12px;
|
|
42
|
+
line-height: 1.4;
|
|
43
|
+
color-adjust: exact; // prevents color fading during print
|
|
44
|
+
-webkit-print-color-adjust: exact;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Optional: section separators
|
|
48
|
+
& + .content {
|
|
49
|
+
margin-top: 2rem;
|
|
50
|
+
border-top: 1px dashed #ccc;
|
|
51
|
+
padding-top: 1rem;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.header {
|
|
56
|
+
justify-content: center;
|
|
57
|
+
align-items: center;
|
|
58
|
+
text-align: center;
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
font-weight: bold;
|
|
62
|
+
padding-top: layout.$spacing-05;
|
|
63
|
+
padding-bottom: layout.$spacing-05;
|
|
64
|
+
gap: layout.$spacing-03;
|
|
65
|
+
text-transform: uppercase;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.field {
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: baseline;
|
|
71
|
+
gap: 0;
|
|
72
|
+
width: 100%;
|
|
73
|
+
flex-grow: 1;
|
|
74
|
+
|
|
75
|
+
> .name {
|
|
76
|
+
text-transform: uppercase;
|
|
77
|
+
margin-right: 8px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
> .value {
|
|
81
|
+
text-transform: uppercase;
|
|
82
|
+
border-bottom: 2px dotted black;
|
|
83
|
+
flex: 1;
|
|
84
|
+
line-height: 1.4;
|
|
85
|
+
display: inline-block;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@mixin gridByColumnCount($columns: 3, $gap: 1rem) {
|
|
90
|
+
display: grid;
|
|
91
|
+
grid-template-columns: repeat($columns, 1fr);
|
|
92
|
+
gap: $gap;
|
|
93
|
+
align-items: baseline;
|
|
94
|
+
|
|
95
|
+
@media print {
|
|
96
|
+
padding: 0;
|
|
97
|
+
gap: 0.5rem;
|
|
98
|
+
border: none;
|
|
99
|
+
grid-template-columns: repeat($columns, 1fr);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.txtUpper {
|
|
104
|
+
text-transform: uppercase;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.txtTitle {
|
|
108
|
+
text-transform: capitalize;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.cols4 {
|
|
112
|
+
@include gridByColumnCount(4);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.cols2 {
|
|
116
|
+
@include gridByColumnCount(2);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.cols3 {
|
|
120
|
+
@include gridByColumnCount(3);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.cols7 {
|
|
124
|
+
@include gridByColumnCount(7);
|
|
125
|
+
}
|