@kenyaemr/esm-ward-app 8.5.1-pre.41 → 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.
@@ -1,19 +1,25 @@
1
1
  import {
2
+ type Encounter,
2
3
  type FetchResponse,
3
- fhirBaseUrl,
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 { type Order } from '@openmrs/esm-patient-common-lib';
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) return diagnoses.map((d) => d.text).join(', ');
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 { drugOrderEncounterType } = useConfig<WardConfigObject>();
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 { InlineLoading, InlineNotification } from '@carbon/react';
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
- usePatientAllergies,
10
+ getTreatmentDisplayText,
12
11
  usePatientDiagnosis,
13
12
  usePatientOrders,
14
13
  } from './discharge-printout.resource';
15
- import { useProvider } from '../ward-workspace/admit-patient-form-workspace/patient-admission.resources';
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?.toLowerCase() ?? t('noDiagnoses', 'No Diagnoses')}</p>
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
- {`${_patient.name}, a ${Math.abs(dayjs(patient.birthDate).diff(dayjs(), 'years'))} year ${
125
- patient.gender
126
- } presented with .${
127
- allergies
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
- <p></p>
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
- <p key={order.uuid} className={styles.txtTitle}>
142
- {order.display?.toLowerCase()}
143
- </p>
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.display}</p>
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></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;