@openmrs/esm-generic-patient-widgets-app 11.3.0 → 11.3.1-patch.9310

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.
Files changed (132) hide show
  1. package/.turbo/turbo-build.log +33 -26
  2. package/dist/1119.js +1 -1
  3. package/dist/1197.js +1 -1
  4. package/dist/2146.js +1 -1
  5. package/dist/2499.js +2 -0
  6. package/dist/2499.js.map +1 -0
  7. package/dist/251.js +2 -0
  8. package/dist/251.js.map +1 -0
  9. package/dist/2690.js +1 -1
  10. package/dist/3099.js +1 -1
  11. package/dist/3204.js +2 -0
  12. package/dist/{9582.js.LICENSE.txt → 3204.js.LICENSE.txt} +1 -1
  13. package/dist/3204.js.map +1 -0
  14. package/dist/3584.js +1 -1
  15. package/dist/4055.js +1 -1
  16. package/dist/4132.js +1 -1
  17. package/dist/4300.js +1 -1
  18. package/dist/4335.js +1 -1
  19. package/dist/4341.js +1 -0
  20. package/dist/4341.js.map +1 -0
  21. package/dist/4618.js +1 -1
  22. package/dist/4652.js +1 -1
  23. package/dist/4944.js +1 -1
  24. package/dist/5173.js +1 -1
  25. package/dist/5241.js +1 -1
  26. package/dist/5442.js +1 -1
  27. package/dist/5661.js +1 -1
  28. package/dist/5670.js +1 -0
  29. package/dist/5670.js.map +1 -0
  30. package/dist/6022.js +1 -1
  31. package/dist/6336.js +1 -0
  32. package/dist/6336.js.map +1 -0
  33. package/dist/6468.js +1 -1
  34. package/dist/6679.js +1 -1
  35. package/dist/6840.js +1 -1
  36. package/dist/6859.js +1 -1
  37. package/dist/7097.js +1 -1
  38. package/dist/7159.js +1 -1
  39. package/dist/723.js +1 -1
  40. package/dist/7617.js +1 -1
  41. package/dist/795.js +1 -1
  42. package/dist/8163.js +1 -1
  43. package/dist/8349.js +1 -1
  44. package/dist/8618.js +1 -1
  45. package/dist/890.js +1 -1
  46. package/dist/9214.js +1 -1
  47. package/dist/9351.js +1 -0
  48. package/dist/9351.js.map +1 -0
  49. package/dist/9538.js +1 -1
  50. package/dist/9569.js +1 -1
  51. package/dist/986.js +1 -1
  52. package/dist/9879.js +1 -1
  53. package/dist/9895.js +1 -1
  54. package/dist/9900.js +1 -1
  55. package/dist/9913.js +1 -1
  56. package/dist/main.js +1 -1
  57. package/dist/main.js.LICENSE.txt +1 -1
  58. package/dist/main.js.map +1 -1
  59. package/dist/openmrs-esm-generic-patient-widgets-app.js +1 -1
  60. package/dist/openmrs-esm-generic-patient-widgets-app.js.buildmanifest.json +241 -292
  61. package/dist/openmrs-esm-generic-patient-widgets-app.js.map +1 -1
  62. package/dist/routes.json +1 -1
  63. package/package.json +6 -5
  64. package/src/config-schema-obs-switchable.ts +9 -2
  65. package/src/obs-graph/obs-graph.component.tsx +175 -83
  66. package/src/obs-graph/obs-graph.scss +21 -22
  67. package/src/obs-switchable/obs-switchable.component.tsx +27 -27
  68. package/src/obs-switchable/obs-switchable.scss +1 -1
  69. package/src/obs-switchable/obs-switchable.test.tsx +291 -0
  70. package/src/obs-table/obs-table.component.tsx +6 -4
  71. package/src/obs-table-horizontal/obs-table-horizontal.component.tsx +7 -4
  72. package/src/resources/useConcepts.ts +41 -0
  73. package/src/resources/useObs.ts +32 -5
  74. package/translations/am.json +2 -4
  75. package/translations/ar.json +2 -4
  76. package/translations/ar_SY.json +2 -4
  77. package/translations/bn.json +2 -4
  78. package/translations/de.json +2 -4
  79. package/translations/en.json +2 -4
  80. package/translations/en_US.json +2 -4
  81. package/translations/es.json +2 -4
  82. package/translations/es_MX.json +2 -4
  83. package/translations/fr.json +2 -4
  84. package/translations/he.json +2 -4
  85. package/translations/hi.json +2 -4
  86. package/translations/hi_IN.json +2 -4
  87. package/translations/id.json +2 -4
  88. package/translations/it.json +2 -4
  89. package/translations/ka.json +2 -4
  90. package/translations/km.json +2 -4
  91. package/translations/ku.json +2 -4
  92. package/translations/ky.json +2 -4
  93. package/translations/lg.json +2 -4
  94. package/translations/ne.json +2 -4
  95. package/translations/pl.json +2 -4
  96. package/translations/pt.json +2 -4
  97. package/translations/pt_BR.json +2 -4
  98. package/translations/qu.json +2 -4
  99. package/translations/ro_RO.json +2 -4
  100. package/translations/ru_RU.json +2 -4
  101. package/translations/si.json +2 -4
  102. package/translations/sw.json +2 -4
  103. package/translations/sw_KE.json +2 -4
  104. package/translations/tr.json +2 -4
  105. package/translations/tr_TR.json +2 -4
  106. package/translations/uk.json +2 -4
  107. package/translations/uz.json +2 -4
  108. package/translations/uz@Latn.json +2 -4
  109. package/translations/uz_UZ.json +2 -4
  110. package/translations/vi.json +2 -4
  111. package/translations/zh.json +2 -4
  112. package/translations/zh_CN.json +2 -4
  113. package/dist/30.js +0 -2
  114. package/dist/30.js.map +0 -1
  115. package/dist/4051.js +0 -1
  116. package/dist/4051.js.map +0 -1
  117. package/dist/5048.js +0 -1
  118. package/dist/5048.js.map +0 -1
  119. package/dist/521.js +0 -2
  120. package/dist/521.js.map +0 -1
  121. package/dist/5639.js +0 -1
  122. package/dist/5639.js.map +0 -1
  123. package/dist/5986.js +0 -1
  124. package/dist/5986.js.map +0 -1
  125. package/dist/6432.js +0 -1
  126. package/dist/6432.js.map +0 -1
  127. package/dist/717.js +0 -1
  128. package/dist/717.js.map +0 -1
  129. package/dist/9582.js +0 -2
  130. package/dist/9582.js.map +0 -1
  131. /package/dist/{521.js.LICENSE.txt → 2499.js.LICENSE.txt} +0 -0
  132. /package/dist/{30.js.LICENSE.txt → 251.js.LICENSE.txt} +0 -0
@@ -0,0 +1,291 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { LineChart } from '@carbon/charts-react';
5
+ import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
6
+ import ObsSwitchable from './obs-switchable.component';
7
+ import { useObs, type ObsResult } from '../resources/useObs';
8
+ import { configSchemaSwitchable } from '../config-schema-obs-switchable';
9
+
10
+ jest.mock('../resources/useObs', () => ({
11
+ useObs: jest.fn(),
12
+ }));
13
+
14
+ const mockLineChart = jest.mocked(LineChart);
15
+
16
+ const mockUseConfig = jest.mocked(useConfig);
17
+
18
+ // Make sure this respects the sort order of useObs
19
+ const mockObsData = [
20
+ {
21
+ code: { text: 'Height' },
22
+ conceptUuid: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
23
+ dataType: 'Number',
24
+ effectiveDateTime: '2021-02-01T00:00:00Z',
25
+ valueQuantity: { value: 182 },
26
+ encounter: { reference: 'Encounter/234' },
27
+ },
28
+ {
29
+ code: { text: 'Weight' },
30
+ conceptUuid: '2154AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
31
+ dataType: 'Number',
32
+ effectiveDateTime: '2021-02-01T00:00:00Z',
33
+ valueQuantity: { value: 72 },
34
+ encounter: { reference: 'Encounter/234' },
35
+ },
36
+ {
37
+ code: { text: 'Height' },
38
+ conceptUuid: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
39
+ dataType: 'Number',
40
+ effectiveDateTime: '2021-01-01T00:00:00Z',
41
+ valueQuantity: { value: 180 },
42
+ encounter: { reference: 'Encounter/123' },
43
+ },
44
+ {
45
+ code: { text: 'Weight' },
46
+ conceptUuid: '2154AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
47
+ dataType: 'Number',
48
+ effectiveDateTime: '2021-01-01T00:00:00Z',
49
+ valueQuantity: { value: 70 },
50
+ encounter: { reference: 'Encounter/123' },
51
+ },
52
+ {
53
+ code: { text: 'Chief Complaint' },
54
+ conceptUuid: '164162AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
55
+ dataType: 'Text',
56
+ effectiveDateTime: '2021-01-01T00:00:00Z',
57
+ valueString: 'Too strong',
58
+ encounter: { reference: 'Encounter/123' },
59
+ },
60
+ {
61
+ code: { text: 'Power Level' },
62
+ conceptUuid: '164163AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
63
+ dataType: 'Number',
64
+ effectiveDateTime: '2021-01-01T00:00:00Z',
65
+ valueQuantity: { value: 9001 },
66
+ encounter: { reference: 'Encounter/123' },
67
+ },
68
+ ];
69
+
70
+ const mockConceptData = [
71
+ { uuid: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Height' },
72
+ { uuid: '2154AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Weight' },
73
+ { uuid: '164162AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Chief Complaint' },
74
+ { uuid: '164163AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Power Level' },
75
+ ];
76
+
77
+ const mockUseObs = jest.mocked(useObs);
78
+
79
+ describe('ObsSwitchable', () => {
80
+ it('should render all obs in table and numeric obs in graph', async () => {
81
+ mockUseObs.mockReturnValue({
82
+ data: { observations: mockObsData as Array<ObsResult>, concepts: mockConceptData },
83
+ error: null,
84
+ isLoading: false,
85
+ isValidating: false,
86
+ });
87
+ mockUseConfig.mockReturnValue({
88
+ ...(getDefaultsFromConfigSchema(configSchemaSwitchable) as Object),
89
+ title: 'My Stats',
90
+ data: [
91
+ {
92
+ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
93
+ label: 'Tallitude',
94
+ },
95
+ {
96
+ concept: '2154AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
97
+ },
98
+ { concept: '164162AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' },
99
+ { concept: '164163AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' },
100
+ ],
101
+ });
102
+
103
+ render(<ObsSwitchable patientUuid="123" />);
104
+
105
+ // Check table
106
+ expect(screen.getAllByRole('row')).toHaveLength(3);
107
+ const headerRow = screen.getAllByRole('row')[0];
108
+ expect(headerRow).toHaveTextContent('Tallitude');
109
+ expect(headerRow).toHaveTextContent('Weight');
110
+ expect(headerRow).toHaveTextContent('Chief Complaint');
111
+ expect(headerRow).toHaveTextContent('Power Level');
112
+ const firstRow = screen.getAllByRole('row')[1];
113
+ expect(firstRow).toHaveTextContent('Jan');
114
+ expect(firstRow).toHaveTextContent('180');
115
+ expect(firstRow).toHaveTextContent('70');
116
+ expect(firstRow).toHaveTextContent('Too strong');
117
+ expect(firstRow).toHaveTextContent('9001');
118
+ const secondRow = screen.getAllByRole('row')[2];
119
+ expect(secondRow).toHaveTextContent('Feb');
120
+ expect(secondRow).toHaveTextContent('182');
121
+ expect(secondRow).toHaveTextContent('72');
122
+ expect(secondRow).toHaveTextContent('--');
123
+ expect(secondRow).toHaveTextContent('--');
124
+
125
+ const user = userEvent.setup();
126
+ const chartViewButton = screen.getByLabelText('Chart view');
127
+ expect(chartViewButton).toBeInTheDocument();
128
+ await user.click(chartViewButton);
129
+
130
+ const tabs = screen.getByLabelText('Obs tabs');
131
+ expect(tabs).toHaveTextContent('Tallitude');
132
+ expect(tabs).toHaveTextContent('Weight');
133
+ expect(tabs).not.toHaveTextContent('Chief Complaint');
134
+ expect(tabs).toHaveTextContent('Power Level');
135
+ expect(tabs).not.toHaveTextContent('Mystery Concept');
136
+
137
+ expect(mockLineChart).toHaveBeenNthCalledWith(
138
+ 1,
139
+ expect.objectContaining({
140
+ data: [
141
+ { group: 'Tallitude', key: new Date('2021-02-01T00:00:00.000Z'), value: 182 },
142
+ { group: 'Tallitude', key: new Date('2021-01-01T00:00:00.000Z'), value: 180 },
143
+ ],
144
+ options: expect.any(Object),
145
+ }),
146
+ {},
147
+ );
148
+
149
+ expect(mockLineChart).toHaveBeenNthCalledWith(
150
+ 2,
151
+ expect.objectContaining({
152
+ data: [
153
+ { group: 'Weight', key: new Date('2021-02-01T00:00:00.000Z'), value: 72 },
154
+ { group: 'Weight', key: new Date('2021-01-01T00:00:00.000Z'), value: 70 },
155
+ ],
156
+ options: expect.any(Object),
157
+ }),
158
+ {},
159
+ );
160
+
161
+ expect(mockLineChart).not.toHaveBeenCalledWith(
162
+ expect.objectContaining({
163
+ data: [expect.objectContaining({ group: 'Chief Complaint' })],
164
+ options: expect.any(Object),
165
+ }),
166
+ {},
167
+ );
168
+
169
+ expect(mockLineChart).toHaveBeenNthCalledWith(
170
+ 3,
171
+ expect.objectContaining({
172
+ data: [{ group: 'Power Level', key: new Date('2021-01-01T00:00:00.000Z'), value: 9001 }],
173
+ options: expect.any(Object),
174
+ }),
175
+ {},
176
+ );
177
+ });
178
+
179
+ it('should support showing graph tab by default', async () => {
180
+ mockUseObs.mockReturnValue({
181
+ data: { observations: mockObsData as Array<ObsResult>, concepts: mockConceptData },
182
+ error: null,
183
+ isLoading: false,
184
+ isValidating: false,
185
+ });
186
+ mockUseConfig.mockReturnValue({
187
+ ...(getDefaultsFromConfigSchema(configSchemaSwitchable) as Object),
188
+ title: 'My Stats',
189
+ data: [{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }, { concept: '2154AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }],
190
+ showGraphByDefault: true,
191
+ });
192
+
193
+ render(<ObsSwitchable patientUuid="123" />);
194
+
195
+ const tabs = screen.getByLabelText('Obs tabs');
196
+ expect(tabs).toHaveTextContent('Height');
197
+ expect(tabs).toHaveTextContent('Weight');
198
+
199
+ expect(mockLineChart).toHaveBeenNthCalledWith(
200
+ 1,
201
+ expect.objectContaining({
202
+ data: [
203
+ { group: 'Height', key: new Date('2021-02-01T00:00:00.000Z'), value: 182 },
204
+ { group: 'Height', key: new Date('2021-01-01T00:00:00.000Z'), value: 180 },
205
+ ],
206
+ options: expect.any(Object),
207
+ }),
208
+ {},
209
+ );
210
+ });
211
+
212
+ it('should support grouping into multiline graphs', async () => {
213
+ mockUseObs.mockReturnValue({
214
+ data: { observations: mockObsData as Array<ObsResult>, concepts: mockConceptData },
215
+ error: null,
216
+ isLoading: false,
217
+ isValidating: false,
218
+ });
219
+ mockUseConfig.mockReturnValue({
220
+ ...(getDefaultsFromConfigSchema(configSchemaSwitchable) as Object),
221
+ title: 'My Stats',
222
+ showGraphByDefault: true,
223
+ data: [
224
+ { concept: '164163AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', graphGroup: 'Power Level' },
225
+ { concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', graphGroup: 'Biometrics' },
226
+ { concept: '2154AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', graphGroup: 'Biometrics' },
227
+ ],
228
+ });
229
+
230
+ render(<ObsSwitchable patientUuid="123" />);
231
+
232
+ const tabs = screen.getByLabelText('Obs tabs');
233
+ expect(tabs).toHaveTextContent('Power Level');
234
+ expect(tabs).toHaveTextContent('Biometrics');
235
+
236
+ expect(mockLineChart).toHaveBeenNthCalledWith(
237
+ 1,
238
+ expect.objectContaining({
239
+ data: [{ group: 'Power Level', key: new Date('2021-01-01T00:00:00.000Z'), value: 9001 }],
240
+ options: expect.any(Object),
241
+ }),
242
+ {},
243
+ );
244
+
245
+ expect(mockLineChart).toHaveBeenNthCalledWith(
246
+ 2,
247
+ expect.objectContaining({
248
+ data: [
249
+ { group: 'Height', key: new Date('2021-02-01T00:00:00.000Z'), value: 182 },
250
+ { group: 'Height', key: new Date('2021-01-01T00:00:00.000Z'), value: 180 },
251
+ { group: 'Weight', key: new Date('2021-02-01T00:00:00.000Z'), value: 72 },
252
+ { group: 'Weight', key: new Date('2021-01-01T00:00:00.000Z'), value: 70 },
253
+ ],
254
+ options: expect.any(Object),
255
+ }),
256
+ {},
257
+ );
258
+ });
259
+
260
+ it('should hide the graph tab selection if there is only one graph', async () => {
261
+ mockUseObs.mockReturnValue({
262
+ data: {
263
+ observations: mockObsData.filter(
264
+ (o) => o.conceptUuid === '164163AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
265
+ ) as Array<ObsResult>,
266
+ concepts: mockConceptData,
267
+ },
268
+ error: null,
269
+ isLoading: false,
270
+ isValidating: false,
271
+ });
272
+ mockUseConfig.mockReturnValue({
273
+ ...(getDefaultsFromConfigSchema(configSchemaSwitchable) as Object),
274
+ title: 'My Stats',
275
+ showGraphByDefault: true,
276
+ data: [{ concept: '164163AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }],
277
+ });
278
+
279
+ render(<ObsSwitchable patientUuid="123" />);
280
+
281
+ expect(screen.queryByLabelText('Obs tabs')).not.toBeInTheDocument();
282
+
283
+ expect(mockLineChart).toHaveBeenCalledWith(
284
+ expect.objectContaining({
285
+ data: [{ group: 'Power Level', key: new Date('2021-01-01T00:00:00.000Z'), value: 9001 }],
286
+ options: expect.any(Object),
287
+ }),
288
+ {},
289
+ );
290
+ });
291
+ });
@@ -23,17 +23,19 @@ interface ObsTableProps {
23
23
  const ObsTable: React.FC<ObsTableProps> = ({ patientUuid }) => {
24
24
  const { t } = useTranslation();
25
25
  const config = useConfig<ConfigObjectSwitchable>();
26
- const { data: obss } = useObs(patientUuid, config.showEncounterType);
27
- const uniqueEncounterReferences = [...new Set(obss.map((o) => o.encounter.reference))].sort();
26
+ const {
27
+ data: { observations, concepts },
28
+ } = useObs(patientUuid);
29
+ const uniqueEncounterReferences = [...new Set(observations.map((o) => o.encounter.reference))].sort();
28
30
  const obssGroupedByEncounters = uniqueEncounterReferences.map((reference) =>
29
- obss.filter((o) => o.encounter.reference === reference),
31
+ observations.filter((o) => o.encounter.reference === reference),
30
32
  );
31
33
 
32
34
  const tableHeaders = [
33
35
  { key: 'date', header: t('dateAndTime', 'Date and time'), isSortable: true },
34
36
  ...config.data.map(({ concept, label }) => ({
35
37
  key: concept,
36
- header: label,
38
+ header: label || concepts.find((c) => c.uuid == concept)?.display,
37
39
  })),
38
40
  ];
39
41
 
@@ -23,10 +23,13 @@ interface ObsTableHorizontalProps {
23
23
  const ObsTableHorizontal: React.FC<ObsTableHorizontalProps> = ({ patientUuid }) => {
24
24
  const { t } = useTranslation();
25
25
  const config = useConfig<ConfigObjectHorizontal>();
26
- const { data: obss, isValidating } = useObs(patientUuid, config.showEncounterType);
27
- const uniqueEncounterReferences = [...new Set(obss.map((o) => o.encounter.reference))].sort();
26
+ const {
27
+ data: { observations, concepts },
28
+ isValidating,
29
+ } = useObs(patientUuid);
30
+ const uniqueEncounterReferences = [...new Set(observations.map((o) => o.encounter.reference))].sort();
28
31
  let obssGroupedByEncounters = uniqueEncounterReferences.map((reference) =>
29
- obss.filter((o) => o.encounter.reference === reference),
32
+ observations.filter((o) => o.encounter.reference === reference),
30
33
  );
31
34
 
32
35
  if (config.oldestFirst) {
@@ -41,7 +44,7 @@ const ObsTableHorizontal: React.FC<ObsTableHorizontalProps> = ({ patientUuid })
41
44
 
42
45
  let tableRowLabels = config.data.map(({ concept, label }) => ({
43
46
  key: concept,
44
- header: t(label, label) || obss.find((o) => o.conceptUuid === concept)?.code?.text,
47
+ header: t(label, label) || concepts.find((c) => c.uuid === concept)?.display,
45
48
  }));
46
49
 
47
50
  if (config.showEncounterType) {
@@ -0,0 +1,41 @@
1
+ import { openmrsFetch, type FetchResponse, restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
2
+ import chunk from 'lodash/chunk';
3
+ import useSWRImmutable from 'swr/immutable';
4
+
5
+ export interface ConceptReferenceResponse {
6
+ [key: string]: {
7
+ uuid: string;
8
+ display: string;
9
+ };
10
+ }
11
+
12
+ export function useConcepts(conceptUuids: Array<string>) {
13
+ const { data, error, isLoading } = useSWRImmutable<Array<FetchResponse<ConceptReferenceResponse>>, Error>(
14
+ conceptUuids && conceptUuids.length > 0 ? getConceptReferenceUrls(conceptUuids) : null,
15
+ (key: Array<string>) => Promise.all(key.map((url) => openmrsFetch<ConceptReferenceResponse>(url))),
16
+ );
17
+
18
+ const ob: ConceptReferenceResponse = data?.reduce((acc, response) => ({ ...acc, ...response.data }), {});
19
+ const concepts = ob
20
+ ? Object.values(ob).map((value) => ({
21
+ uuid: value.uuid,
22
+ display: value.display,
23
+ }))
24
+ : [];
25
+
26
+ if (error) {
27
+ showSnackbar({
28
+ title: error.name,
29
+ subtitle: error.message,
30
+ kind: 'error',
31
+ });
32
+ }
33
+
34
+ return { concepts, isLoading };
35
+ }
36
+
37
+ function getConceptReferenceUrls(conceptUuids: Array<string>) {
38
+ return chunk(conceptUuids, 10).map(
39
+ (partition) => `${restBaseUrl}/conceptreferences?references=${partition.join(',')}&v=custom:(uuid,display)`,
40
+ );
41
+ }
@@ -2,17 +2,21 @@ import useSWR from 'swr';
2
2
  import { openmrsFetch, fhirBaseUrl, useConfig } from '@openmrs/esm-framework';
3
3
  import { type ConfigObjectSwitchable } from '../config-schema-obs-switchable';
4
4
  import { type ConfigObjectHorizontal } from '../config-schema-obs-horizontal';
5
+ import { useConcepts } from './useConcepts';
5
6
 
6
7
  type CommonConfig = ConfigObjectSwitchable | ConfigObjectHorizontal;
7
8
 
8
9
  export interface UseObsResult {
9
- data: Array<ObsResult>;
10
+ data: {
11
+ observations: Array<ObsResult>;
12
+ concepts: Array<{ uuid: string; display: string }>;
13
+ };
10
14
  error: Error;
11
15
  isLoading: boolean;
12
16
  isValidating: boolean;
13
17
  }
14
18
 
15
- type ObsResult = fhir.Observation & {
19
+ export type ObsResult = fhir.Observation & {
16
20
  conceptUuid: string;
17
21
  dataType?: string;
18
22
  valueDateTime?: string;
@@ -27,10 +31,16 @@ type ObsResult = fhir.Observation & {
27
31
 
28
32
  export const pageSize = 100;
29
33
 
30
- export function useObs(patientUuid: string, includeEncounters: boolean = false): UseObsResult {
34
+ /**
35
+ * Fetches the observations for the concepts in the config for this widget.
36
+ * For any concept that has neither label nor obs, the concept is fetched to
37
+ * get the label.
38
+ */
39
+ export function useObs(patientUuid: string): UseObsResult {
31
40
  const { encounterTypes, data, showEncounterType } = useConfig<CommonConfig>();
32
41
  const urlEncounterTypes: string = encounterTypes.length ? `&encounter.type=${encounterTypes.toString()}` : '';
33
42
 
43
+ // TODO: Make sorting respect oldestFirst/graphOldestFirst
34
44
  let url = `${fhirBaseUrl}/Observation?subject:Patient=${patientUuid}&code=${data
35
45
  .map((d) => d.concept)
36
46
  .join(',')}&_summary=data&_sort=-date&_count=${pageSize}${urlEncounterTypes}`;
@@ -41,11 +51,28 @@ export function useObs(patientUuid: string, includeEncounters: boolean = false):
41
51
 
42
52
  const { data: result, error, isLoading, isValidating } = useSWR<{ data: fhir.Bundle }, Error>(url, openmrsFetch);
43
53
 
54
+ const obsForConcept = Object.fromEntries(
55
+ data.map((d) => [
56
+ d.concept,
57
+ result?.data?.entry?.find((e) => (e.resource as fhir.Observation).code.coding.find((c) => d.concept === c.code)),
58
+ ]),
59
+ );
60
+
61
+ const conceptsNeedingLabel = data.filter((d) => result && !d.label && !obsForConcept[d.concept]);
62
+
63
+ const { concepts: conceptsForLabels } = useConcepts(conceptsNeedingLabel.map((d) => d.concept));
64
+ const concepts = data.map((d) => ({
65
+ uuid: d.concept,
66
+ display:
67
+ d.label ||
68
+ obsForConcept[d.concept]?.resource?.code?.text ||
69
+ conceptsForLabels.find((c) => c.uuid === d.concept)?.display,
70
+ }));
71
+
44
72
  const encounters = showEncounterType ? getEncountersByResources(result?.data?.entry) : [];
45
73
  const observations = filterAndMapObservations(result?.data?.entry, encounters);
46
-
47
74
  return {
48
- data: observations,
75
+ data: { observations, concepts },
49
76
  error: error,
50
77
  isLoading,
51
78
  isValidating,
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Chart View",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date and time",
4
- "displaying": "Displaying",
5
- "encounterType": "Encounter type",
6
- "tableView": "Table View"
4
+ "encounterType": "Encounter type"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "عرض الرسم البياني",
2
+ "date": "Date",
3
3
  "dateAndTime": "التاريخ والوقت",
4
- "displaying": "عرض",
5
- "encounterType": "نوع اللقاء",
6
- "tableView": "عرض الجدول"
4
+ "encounterType": "نوع اللقاء"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Chart View",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date and time",
4
- "displaying": "Displaying",
5
- "encounterType": "Encounter type",
6
- "tableView": "Table View"
4
+ "encounterType": "Encounter type"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Chart View",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date and time",
4
- "displaying": "Displaying",
5
- "encounterType": "Encounter type",
6
- "tableView": "Table View"
4
+ "encounterType": "Encounter type"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Chart View",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date and time",
4
- "displaying": "Displaying",
5
- "encounterType": "Encounter type",
6
- "tableView": "Table View"
4
+ "encounterType": "Encounter type"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Chart View",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date and time",
4
- "displaying": "Displaying",
5
- "encounterType": "Encounter type",
6
- "tableView": "Table View"
4
+ "encounterType": "Encounter type"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Chart View",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date and time",
4
- "displaying": "Displaying",
5
- "encounterType": "Encounter type",
6
- "tableView": "Table View"
4
+ "encounterType": "Encounter type"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Vista de Gráfico",
2
+ "date": "Date",
3
3
  "dateAndTime": "Fecha y hora",
4
- "displaying": "Mostrando",
5
- "encounterType": "Tipo de encuentro",
6
- "tableView": "Vista de Tabla"
4
+ "encounterType": "Tipo de encuentro"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Chart View",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date and time",
4
- "displaying": "Displaying",
5
- "encounterType": "Encounter type",
6
- "tableView": "Table View"
4
+ "encounterType": "Encounter type"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Vue graphique",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date et heure",
4
- "displaying": "En cours d'affichage",
5
- "encounterType": "Type de consultation",
6
- "tableView": "Vue tabulaire"
4
+ "encounterType": "Type de consultation"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "תצוגת תרשים",
2
+ "date": "Date",
3
3
  "dateAndTime": "תאריך ושעה",
4
- "displaying": "מציג",
5
- "encounterType": "סוג הביקור",
6
- "tableView": "תצוגת טבלה"
4
+ "encounterType": "סוג הביקור"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Chart View",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date and time",
4
- "displaying": "Displaying",
5
- "encounterType": "Encounter type",
6
- "tableView": "Table View"
4
+ "encounterType": "Encounter type"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Chart View",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date and time",
4
- "displaying": "Displaying",
5
- "encounterType": "Encounter type",
6
- "tableView": "Table View"
4
+ "encounterType": "Encounter type"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Tampilan Grafik",
2
+ "date": "Date",
3
3
  "dateAndTime": "Tanggal dan waktu",
4
- "displaying": "Menampilkan",
5
- "encounterType": "Jenis pertemuan",
6
- "tableView": "Tampilan Tabel"
4
+ "encounterType": "Jenis pertemuan"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Vista grafico",
2
+ "date": "Date",
3
3
  "dateAndTime": "Data e ora",
4
- "displaying": "Visualizzazione",
5
- "encounterType": "Tipo di incontro",
6
- "tableView": "Vista tabella"
4
+ "encounterType": "Tipo di incontro"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "გრაფიკის ხედი",
2
+ "date": "Date",
3
3
  "dateAndTime": "თარიღი და დრო",
4
- "displaying": "ნაჩვენებია",
5
- "encounterType": "კონსულტაციის ტიპი",
6
- "tableView": "ცხრილის ხედი"
4
+ "encounterType": "კონსულტაციის ტიპი"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "មើលគំនូសតាង",
2
+ "date": "Date",
3
3
  "dateAndTime": "កាលបរិច្ឆេទ និងម៉ោង",
4
- "displaying": "ការបង្ហាញ",
5
- "encounterType": "ប្រភេទនៃការជួបប្រទះ",
6
- "tableView": "មើលតារាង"
4
+ "encounterType": "ប្រភេទនៃការជួបប្រទះ"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Chart View",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date and time",
4
- "displaying": "Displaying",
5
- "encounterType": "Encounter type",
6
- "tableView": "Table View"
4
+ "encounterType": "Encounter type"
7
5
  }
@@ -1,7 +1,5 @@
1
1
  {
2
- "chartView": "Chart View",
2
+ "date": "Date",
3
3
  "dateAndTime": "Date and time",
4
- "displaying": "Displaying",
5
- "encounterType": "Encounter type",
6
- "tableView": "Table View"
4
+ "encounterType": "Encounter type"
7
5
  }