@kenyaemr/esm-ward-app 8.5.1-pre.41 → 8.5.1-pre.46
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 +8 -8
- package/dist/1580.js +1 -1
- package/dist/1580.js.LICENSE.txt +6 -0
- package/dist/1580.js.map +1 -1
- package/dist/1663.js +1 -1
- package/dist/1663.js.map +1 -1
- package/dist/1776.js +1 -1
- package/dist/1776.js.map +1 -1
- package/dist/1919.js +1 -0
- package/dist/2123.js +1 -0
- package/dist/2123.js.map +1 -0
- package/dist/2557.js +1 -1
- package/dist/2557.js.map +1 -1
- package/dist/2953.js +2 -0
- package/dist/2953.js.map +1 -0
- package/dist/3737.js +1 -1
- package/dist/3737.js.map +1 -1
- package/dist/4224.js +1 -1
- package/dist/4224.js.map +1 -1
- package/dist/4300.js +1 -1
- package/dist/4300.js.map +1 -0
- package/dist/5643.js +2 -0
- package/dist/5643.js.map +1 -0
- package/dist/6009.js +1 -0
- package/dist/6009.js.map +1 -0
- package/dist/6195.js +1 -0
- package/dist/6195.js.map +1 -0
- package/dist/681.js +2 -0
- package/dist/681.js.map +1 -0
- package/dist/7179.js +1 -1
- package/dist/7179.js.map +1 -1
- package/dist/7524.js +1 -1
- package/dist/7524.js.map +1 -1
- 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 -1
- package/dist/8308.js.map +1 -1
- package/dist/8501.js +2 -1
- package/dist/8501.js.map +1 -1
- package/dist/9045.js +1 -1
- package/dist/9045.js.map +1 -1
- package/dist/9719.js +2 -0
- package/dist/{8317.js.LICENSE.txt → 9719.js.LICENSE.txt} +0 -6
- package/dist/9719.js.map +1 -0
- package/dist/kenyaemr-esm-ward-app.js +1 -1
- package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +201 -201
- package/dist/main.js +1 -2
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +22 -0
- package/src/create-dashboard.scss +13 -0
- package/src/createDashboardLink.component.tsx +22 -15
- package/src/discharge-printouts/discharge-printout.resource.ts +258 -138
- package/src/discharge-printouts/discharge-summary.tsx +35 -36
- package/src/discharge-printouts/lab-results.tsx +96 -0
- package/src/index.ts +4 -1
- package/src/ward-patients/discharge-in-patients.tsx +2 -3
- package/dist/2059.js +0 -1
- package/dist/2059.js.map +0 -1
- package/dist/3161.js +0 -2
- package/dist/3161.js.map +0 -1
- package/dist/3373.js +0 -2
- package/dist/3373.js.map +0 -1
- package/dist/4743.js +0 -2
- package/dist/4743.js.map +0 -1
- package/dist/6012.js +0 -2
- package/dist/6012.js.LICENSE.txt +0 -5
- package/dist/6012.js.map +0 -1
- package/dist/7059.js +0 -2
- package/dist/7059.js.map +0 -1
- package/dist/8317.js +0 -2
- package/dist/8317.js.map +0 -1
- package/dist/9113.js +0 -1
- package/dist/9113.js.map +0 -1
- package/dist/main.js.LICENSE.txt +0 -5
- /package/dist/{3161.js.LICENSE.txt → 2953.js.LICENSE.txt} +0 -0
- /package/dist/{4743.js.LICENSE.txt → 5643.js.LICENSE.txt} +0 -0
- /package/dist/{7059.js.LICENSE.txt → 681.js.LICENSE.txt} +0 -0
- /package/dist/{3373.js.LICENSE.txt → 8501.js.LICENSE.txt} +0 -0
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type Encounter,
|
|
2
3
|
type FetchResponse,
|
|
3
|
-
|
|
4
|
+
type Obs,
|
|
4
5
|
openmrsFetch,
|
|
6
|
+
type OpenmrsResource,
|
|
5
7
|
restBaseUrl,
|
|
6
8
|
useConfig,
|
|
7
9
|
type Visit,
|
|
8
10
|
} from '@openmrs/esm-framework';
|
|
11
|
+
import { type Order } from '@openmrs/esm-patient-common-lib';
|
|
9
12
|
import { useMemo } from 'react';
|
|
10
13
|
import useSWR from 'swr';
|
|
11
|
-
import { useEncounterDetails } from '../hooks/useIpdDischargeEncounter';
|
|
12
14
|
import { type WardConfigObject } from '../config-schema';
|
|
13
|
-
import {
|
|
15
|
+
import { useEncounterDetails } from '../hooks/useIpdDischargeEncounter';
|
|
14
16
|
export const DATE_FORMART = 'DD/MM/YYYY';
|
|
15
17
|
export const TIME_FORMART = 'hh:mm A';
|
|
16
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
|
+
|
|
17
23
|
export const usePatientDiagnosis = (encounterUuid: string) => {
|
|
18
24
|
const customRepresentation =
|
|
19
25
|
'custom:(uuid,display,visit:(uuid,encounters:(uuid,diagnoses:(uuid,display,certainty,diagnosis:(coded:(uuid,display))))))';
|
|
@@ -34,7 +40,11 @@ export const usePatientDiagnosis = (encounterUuid: string) => {
|
|
|
34
40
|
);
|
|
35
41
|
}, [data]);
|
|
36
42
|
const display = useMemo(() => {
|
|
37
|
-
if (diagnoses?.length)
|
|
43
|
+
if (diagnoses?.length)
|
|
44
|
+
return diagnoses
|
|
45
|
+
.map((d) => d.text)
|
|
46
|
+
.join(', ')
|
|
47
|
+
?.toLowerCase();
|
|
38
48
|
return null;
|
|
39
49
|
}, [diagnoses]);
|
|
40
50
|
|
|
@@ -46,150 +56,39 @@ export const usePatientDiagnosis = (encounterUuid: string) => {
|
|
|
46
56
|
};
|
|
47
57
|
};
|
|
48
58
|
|
|
49
|
-
export interface AllergyIntoleranceResponse {
|
|
50
|
-
resourceType: string;
|
|
51
|
-
id: string;
|
|
52
|
-
meta: {
|
|
53
|
-
lastUpdated: string;
|
|
54
|
-
};
|
|
55
|
-
type: string;
|
|
56
|
-
total: number;
|
|
57
|
-
entry: Array<{
|
|
58
|
-
resource: AllergyIntolerance;
|
|
59
|
-
}>;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface AllergyIntolerance {
|
|
63
|
-
resourceType: string;
|
|
64
|
-
id: string;
|
|
65
|
-
meta: {
|
|
66
|
-
lastUpdated: string;
|
|
67
|
-
};
|
|
68
|
-
clinicalStatus: {
|
|
69
|
-
coding: [
|
|
70
|
-
{
|
|
71
|
-
system: string;
|
|
72
|
-
code: string;
|
|
73
|
-
display: string;
|
|
74
|
-
},
|
|
75
|
-
];
|
|
76
|
-
text: string;
|
|
77
|
-
};
|
|
78
|
-
verificationStatus: {
|
|
79
|
-
coding: [
|
|
80
|
-
{
|
|
81
|
-
system: string;
|
|
82
|
-
code: string;
|
|
83
|
-
display: string;
|
|
84
|
-
},
|
|
85
|
-
];
|
|
86
|
-
text: string;
|
|
87
|
-
};
|
|
88
|
-
type: string;
|
|
89
|
-
category: Array<string>;
|
|
90
|
-
criticality: string;
|
|
91
|
-
code: {
|
|
92
|
-
coding: [
|
|
93
|
-
{
|
|
94
|
-
code: string;
|
|
95
|
-
display: string;
|
|
96
|
-
},
|
|
97
|
-
];
|
|
98
|
-
text: string;
|
|
99
|
-
};
|
|
100
|
-
patient: {
|
|
101
|
-
reference: string;
|
|
102
|
-
type: string;
|
|
103
|
-
display: string;
|
|
104
|
-
};
|
|
105
|
-
recordedDate: string;
|
|
106
|
-
recorder: {
|
|
107
|
-
reference: string;
|
|
108
|
-
type: string;
|
|
109
|
-
display: string;
|
|
110
|
-
};
|
|
111
|
-
reaction: [
|
|
112
|
-
{
|
|
113
|
-
substance: {
|
|
114
|
-
coding: [
|
|
115
|
-
{
|
|
116
|
-
code: string;
|
|
117
|
-
display: string;
|
|
118
|
-
},
|
|
119
|
-
];
|
|
120
|
-
text: string;
|
|
121
|
-
};
|
|
122
|
-
manifestation: [
|
|
123
|
-
{
|
|
124
|
-
coding: [
|
|
125
|
-
{
|
|
126
|
-
code: string;
|
|
127
|
-
display: string;
|
|
128
|
-
},
|
|
129
|
-
];
|
|
130
|
-
text: string;
|
|
131
|
-
},
|
|
132
|
-
];
|
|
133
|
-
severity: string;
|
|
134
|
-
},
|
|
135
|
-
];
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export interface Coding {
|
|
139
|
-
system?: string;
|
|
140
|
-
code: string;
|
|
141
|
-
display?: string;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function getConceptCoding(codings: Coding[]): Coding {
|
|
145
|
-
return codings ? codings.find((c) => !('system' in c) || c.system === undefined) : null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function getConceptCodingDisplay(codings: Coding[]): string {
|
|
149
|
-
return getConceptCoding(codings)?.display;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function usePatientAllergies(patientUuid: string) {
|
|
153
|
-
const { data, error, isLoading } = useSWR<{ data: AllergyIntoleranceResponse }, Error>(
|
|
154
|
-
`${fhirBaseUrl}/AllergyIntolerance?patient=${patientUuid}`,
|
|
155
|
-
openmrsFetch,
|
|
156
|
-
);
|
|
157
|
-
const allergies: Array<AllergyIntolerance> = useMemo(() => {
|
|
158
|
-
const _allergies: Array<AllergyIntolerance> = [];
|
|
159
|
-
if (data) {
|
|
160
|
-
const entries = data?.data.entry;
|
|
161
|
-
entries?.map((allergy) => {
|
|
162
|
-
return _allergies.push(allergy.resource);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
return _allergies;
|
|
166
|
-
}, [data]);
|
|
167
|
-
|
|
168
|
-
const display = useMemo(() => {
|
|
169
|
-
if (allergies?.length) return allergies.map((allergy) => getConceptCodingDisplay(allergy.code.coding)).join(', ');
|
|
170
|
-
return null;
|
|
171
|
-
}, [allergies]);
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
allergies,
|
|
175
|
-
totalAllergies: data?.data.total,
|
|
176
|
-
error,
|
|
177
|
-
isLoading,
|
|
178
|
-
display,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
59
|
export function usePatientOrders(dischargeEncounterUuId: string) {
|
|
183
60
|
const rep =
|
|
184
61
|
'custom:(uuid,display,location:(display),encounterDatetime,visit:(uuid,display,encounters:(uuid,display,encounterType:(uuid,display),encounterDatetime,orders,obs)))';
|
|
185
62
|
const { encounter, error, isLoading } = useEncounterDetails(dischargeEncounterUuId, rep);
|
|
186
|
-
const {
|
|
63
|
+
const {
|
|
64
|
+
drugOrderEncounterType,
|
|
65
|
+
clinicalConsultationEncounterType,
|
|
66
|
+
ipdDischargeEncounterTypeUuid,
|
|
67
|
+
doctorsNoteEncounterType,
|
|
68
|
+
conceptUuidForWardAdmission: concepts,
|
|
69
|
+
} = useConfig<WardConfigObject>();
|
|
70
|
+
|
|
187
71
|
const orderEncounters = useMemo(() => {
|
|
188
72
|
const encounters = (encounter?.visit?.encounters ?? []).filter(
|
|
189
73
|
(enc) => enc.encounterType.uuid === drugOrderEncounterType,
|
|
190
74
|
);
|
|
191
75
|
return encounters;
|
|
192
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
|
+
|
|
193
92
|
const { drugorder, testorder } = useMemo<{ drugorder: Array<Order>; testorder: Array<Order> }>(
|
|
194
93
|
() =>
|
|
195
94
|
orderEncounters.reduce(
|
|
@@ -205,11 +104,232 @@ export function usePatientOrders(dischargeEncounterUuId: string) {
|
|
|
205
104
|
[orderEncounters],
|
|
206
105
|
);
|
|
207
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
|
+
|
|
208
148
|
return {
|
|
209
149
|
isLoading,
|
|
210
150
|
error,
|
|
211
151
|
drugOrders: drugorder,
|
|
212
152
|
testOrders: testorder,
|
|
213
153
|
orderEncounters,
|
|
154
|
+
clinicalConsultationEncounters,
|
|
155
|
+
complaints,
|
|
156
|
+
drugReactions,
|
|
157
|
+
dischargeinstructions,
|
|
158
|
+
ipdDischargeEncounter,
|
|
159
|
+
physicalExaminations,
|
|
214
160
|
};
|
|
215
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
|
+
};
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
import { InlineLoading, InlineNotification } from '@carbon/react';
|
|
2
|
+
import { useEmrConfiguration, usePatient, useSession } from '@openmrs/esm-framework';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
1
4
|
import React, { type FC, useMemo } from 'react';
|
|
2
|
-
import styles from './discharge-printouts.scss';
|
|
3
|
-
import FieldInput from './field-input';
|
|
4
5
|
import { useTranslation } from 'react-i18next';
|
|
5
|
-
import { useEmrConfiguration, usePatient, useSession } from '@openmrs/esm-framework';
|
|
6
6
|
import { useEncounterDetails } from '../hooks/useIpdDischargeEncounter';
|
|
7
|
-
import {
|
|
8
|
-
import dayjs from 'dayjs';
|
|
7
|
+
import { useProvider } from '../ward-workspace/admit-patient-form-workspace/patient-admission.resources';
|
|
9
8
|
import {
|
|
10
9
|
DATE_FORMART,
|
|
11
|
-
|
|
10
|
+
getTreatmentDisplayText,
|
|
12
11
|
usePatientDiagnosis,
|
|
13
12
|
usePatientOrders,
|
|
14
13
|
} from './discharge-printout.resource';
|
|
15
|
-
import
|
|
14
|
+
import styles from './discharge-printouts.scss';
|
|
15
|
+
import FieldInput from './field-input';
|
|
16
|
+
import LabResults from './lab-results';
|
|
16
17
|
|
|
17
18
|
type DischargeSummaryProps = {
|
|
18
19
|
dischargeEncounterUuid: string;
|
|
@@ -32,11 +33,6 @@ const DischargeSummary: FC<DischargeSummaryProps> = ({ dischargeEncounterUuid, p
|
|
|
32
33
|
error: diagnosisError,
|
|
33
34
|
display: diagnoses,
|
|
34
35
|
} = usePatientDiagnosis(dischargeEncounterUuid);
|
|
35
|
-
const {
|
|
36
|
-
display: allergies,
|
|
37
|
-
error: allergiesError,
|
|
38
|
-
isLoading: isLoadingAllergies,
|
|
39
|
-
} = usePatientAllergies(_patient.uuid);
|
|
40
36
|
const session = useSession();
|
|
41
37
|
const { error: errorProvider, isLoading: isLoadingProvider, provider } = useProvider(session.currentProvider.uuid);
|
|
42
38
|
const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
|
|
@@ -45,6 +41,11 @@ const DischargeSummary: FC<DischargeSummaryProps> = ({ dischargeEncounterUuid, p
|
|
|
45
41
|
error: orderserror,
|
|
46
42
|
drugOrders,
|
|
47
43
|
testOrders,
|
|
44
|
+
complaints,
|
|
45
|
+
drugReactions,
|
|
46
|
+
orderEncounters,
|
|
47
|
+
dischargeinstructions,
|
|
48
|
+
physicalExaminations,
|
|
48
49
|
} = usePatientOrders(dischargeEncounterUuid);
|
|
49
50
|
const admissionDate = useMemo(() => {
|
|
50
51
|
const admisionEncounter = encounter?.visit?.encounters?.find(
|
|
@@ -60,19 +61,10 @@ const DischargeSummary: FC<DischargeSummaryProps> = ({ dischargeEncounterUuid, p
|
|
|
60
61
|
isLoadingEmrConfiguration ||
|
|
61
62
|
isLoadingDiagnosis ||
|
|
62
63
|
isLoadingProvider ||
|
|
63
|
-
isLoadingAllergies ||
|
|
64
64
|
isLoadingOders
|
|
65
65
|
)
|
|
66
66
|
return <InlineLoading />;
|
|
67
|
-
if (
|
|
68
|
-
error ||
|
|
69
|
-
patientError ||
|
|
70
|
-
errorFetchingEmrConfiguration ||
|
|
71
|
-
diagnosisError ||
|
|
72
|
-
errorProvider ||
|
|
73
|
-
allergiesError ||
|
|
74
|
-
orderserror
|
|
75
|
-
)
|
|
67
|
+
if (error || patientError || errorFetchingEmrConfiguration || diagnosisError || errorProvider || orderserror)
|
|
76
68
|
return (
|
|
77
69
|
<InlineNotification
|
|
78
70
|
kind="error"
|
|
@@ -82,7 +74,6 @@ const DischargeSummary: FC<DischargeSummaryProps> = ({ dischargeEncounterUuid, p
|
|
|
82
74
|
errorFetchingEmrConfiguration?.message ??
|
|
83
75
|
diagnosisError?.message ??
|
|
84
76
|
errorProvider?.message ??
|
|
85
|
-
allergiesError?.message ??
|
|
86
77
|
orderserror?.message
|
|
87
78
|
}
|
|
88
79
|
/>
|
|
@@ -116,31 +107,39 @@ const DischargeSummary: FC<DischargeSummaryProps> = ({ dischargeEncounterUuid, p
|
|
|
116
107
|
|
|
117
108
|
<div>
|
|
118
109
|
<strong className={styles.txtUpper}>{t('diagnosis', 'Diagnosis')}</strong>
|
|
119
|
-
<p className={styles.txtTitle}>{diagnoses
|
|
110
|
+
<p className={styles.txtTitle}>{diagnoses ?? t('noDiagnoses', 'No Diagnoses')}</p>
|
|
120
111
|
</div>
|
|
121
112
|
<div>
|
|
122
113
|
<strong className={styles.txtUpper}>{t('history', 'History')}</strong>
|
|
123
114
|
<p>
|
|
124
|
-
{`${
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
? t('knownAlergies', 'Known Alergies') + ': ' + allergies
|
|
129
|
-
: t('noKnownAlergies', 'No known alergies')
|
|
115
|
+
{`${complaints ? 'Presented with ' + complaints + '.' : ''}${
|
|
116
|
+
drugReactions
|
|
117
|
+
? t('knownDrugAllergies', 'Known drug allergies') + ': ' + drugReactions
|
|
118
|
+
: t('noKnownDrugAllergies', 'No known drug allergies')
|
|
130
119
|
}`}
|
|
131
120
|
</p>
|
|
132
121
|
</div>
|
|
133
122
|
<div>
|
|
134
123
|
<strong className={styles.txtUpper}>{t('physicalExamination', 'Physical Examination')}</strong>
|
|
135
|
-
|
|
124
|
+
{physicalExaminations?.length ? (
|
|
125
|
+
physicalExaminations?.map((examination, i) => (
|
|
126
|
+
<p key={i} className={styles.txtTitle}>
|
|
127
|
+
{examination}
|
|
128
|
+
</p>
|
|
129
|
+
))
|
|
130
|
+
) : (
|
|
131
|
+
<p>{t('noExaminations', 'No Examinations')}</p>
|
|
132
|
+
)}
|
|
136
133
|
</div>
|
|
137
134
|
<div>
|
|
138
135
|
<strong className={styles.txtUpper}>{t('investigation', 'Investigation')}</strong>
|
|
139
136
|
<p>
|
|
140
137
|
{testOrders.map((order) => (
|
|
141
|
-
<
|
|
142
|
-
{order
|
|
143
|
-
|
|
138
|
+
<LabResults
|
|
139
|
+
order={order}
|
|
140
|
+
key={order.uuid}
|
|
141
|
+
labEncounter={orderEncounters.find((e) => e.uuid === order.encounter.uuid)}
|
|
142
|
+
/>
|
|
144
143
|
))}
|
|
145
144
|
</p>
|
|
146
145
|
</div>
|
|
@@ -148,13 +147,13 @@ const DischargeSummary: FC<DischargeSummaryProps> = ({ dischargeEncounterUuid, p
|
|
|
148
147
|
<strong className={styles.txtUpper}>{t('treatment', 'Treatment')}</strong>
|
|
149
148
|
<div>
|
|
150
149
|
{drugOrders.map((order) => (
|
|
151
|
-
<p key={order.uuid}>{order
|
|
150
|
+
<p key={order.uuid}>{getTreatmentDisplayText(order)}</p>
|
|
152
151
|
))}
|
|
153
152
|
</div>
|
|
154
153
|
</div>
|
|
155
154
|
<div>
|
|
156
155
|
<strong className={styles.txtUpper}>{t('dischargeInstructions', 'Discharge Instructions')}</strong>
|
|
157
|
-
<p
|
|
156
|
+
<p>{dischargeinstructions ?? t('noInstructions', 'No instructions')}</p>
|
|
158
157
|
</div>
|
|
159
158
|
<div className={styles.cols2}>
|
|
160
159
|
<FieldInput name={t('name', 'Name')} value={provider?.display?.split('-')?.at(-1)} />
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { InlineLoading, InlineNotification } from '@carbon/react';
|
|
2
|
+
import { type Encounter } from '@openmrs/esm-framework';
|
|
3
|
+
import type { Order } from '@openmrs/esm-patient-common-lib';
|
|
4
|
+
import React, { type FC, useMemo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { getObservationDisplayValue, useOrderConceptByUuid } from './discharge-printout.resource';
|
|
7
|
+
import styles from './discharge-printouts.scss';
|
|
8
|
+
type LabResultsProps = {
|
|
9
|
+
order: Order;
|
|
10
|
+
labEncounter?: Encounter;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const LabResults: FC<LabResultsProps> = ({ order, labEncounter: encounter }) => {
|
|
14
|
+
const { concept, isLoading: isLoadingTestConcepts, error } = useOrderConceptByUuid(order?.concept?.uuid);
|
|
15
|
+
const testResultObs = useMemo(() => {
|
|
16
|
+
if (!encounter || !concept) return null;
|
|
17
|
+
return encounter.obs?.find((obs) => obs.concept.uuid === concept.uuid);
|
|
18
|
+
}, [concept, encounter]);
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
const EMPTY_TEXT = t('noResults', 'No results');
|
|
21
|
+
|
|
22
|
+
const testResults = useMemo(() => {
|
|
23
|
+
if (!concept) return [];
|
|
24
|
+
|
|
25
|
+
// For panel tests (with set members)
|
|
26
|
+
if (concept.setMembers && concept.setMembers.length > 0) {
|
|
27
|
+
return concept.setMembers.map((memberConcept) => {
|
|
28
|
+
const memberObs = testResultObs?.groupMembers?.find((obs) => obs.concept.uuid === memberConcept.uuid);
|
|
29
|
+
let resultValue: string;
|
|
30
|
+
if (memberObs) {
|
|
31
|
+
resultValue = getObservationDisplayValue(memberObs.value ?? (memberObs as any));
|
|
32
|
+
} else {
|
|
33
|
+
resultValue = EMPTY_TEXT;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
id: memberConcept.uuid,
|
|
38
|
+
testType: memberConcept.display || EMPTY_TEXT,
|
|
39
|
+
result: resultValue,
|
|
40
|
+
normalRange:
|
|
41
|
+
memberConcept.hiNormal && memberConcept.lowNormal
|
|
42
|
+
? `${memberConcept.lowNormal} - ${memberConcept.hiNormal}`
|
|
43
|
+
: 'N/A',
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// For single tests (no set members)
|
|
49
|
+
let resultValue: string;
|
|
50
|
+
if (testResultObs) {
|
|
51
|
+
resultValue = getObservationDisplayValue(testResultObs.value ?? (testResultObs as any));
|
|
52
|
+
} else {
|
|
53
|
+
resultValue = EMPTY_TEXT;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
id: concept.uuid,
|
|
59
|
+
testType: concept.display || EMPTY_TEXT,
|
|
60
|
+
result: resultValue,
|
|
61
|
+
normalRange: concept.hiNormal && concept.lowNormal ? `${concept.lowNormal} - ${concept.hiNormal}` : 'N/A',
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
}, [concept, testResultObs, EMPTY_TEXT]);
|
|
65
|
+
|
|
66
|
+
if (isLoadingTestConcepts)
|
|
67
|
+
return (
|
|
68
|
+
<InlineLoading
|
|
69
|
+
status="active"
|
|
70
|
+
iconDescription="Loading"
|
|
71
|
+
description={t('loadinglabresults', 'Loading lab results') + '...'}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (error)
|
|
76
|
+
return (
|
|
77
|
+
<InlineNotification
|
|
78
|
+
kind="error"
|
|
79
|
+
title={t('labResultError', 'Error loading lab results')}
|
|
80
|
+
subtitle={error?.message}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div key={order.uuid} className={styles.txtTitle}>
|
|
86
|
+
{testResults.map((res) => (
|
|
87
|
+
<p key={res.id}>
|
|
88
|
+
<strong>{res.testType.toLowerCase()}: </strong>
|
|
89
|
+
<span>{res.result}</span>
|
|
90
|
+
</p>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default LabResults;
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,10 @@ const options = {
|
|
|
18
18
|
|
|
19
19
|
export const root = getAsyncLifecycle(() => import('./root.component'), options);
|
|
20
20
|
|
|
21
|
-
export const wardDashboardLink = getSyncLifecycle(
|
|
21
|
+
export const wardDashboardLink = getSyncLifecycle(
|
|
22
|
+
createDashboardLink({ name: 'ward', title: 'Admissions', icon: 'omrs-icon-hospital-bed' }),
|
|
23
|
+
options,
|
|
24
|
+
);
|
|
22
25
|
|
|
23
26
|
export const wardView = getAsyncLifecycle(() => import('./ward-view/ward-view.component'), options);
|
|
24
27
|
|