@openmrs/esm-generic-patient-widgets-app 11.3.1-patch.9064 → 11.3.1-patch.9508
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +23 -23
- package/dist/1119.js +1 -1
- package/dist/1197.js +1 -1
- package/dist/1936.js +1 -0
- package/dist/1936.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2606.js +2 -0
- package/dist/2606.js.map +1 -0
- package/dist/2690.js +1 -1
- package/dist/3099.js +1 -1
- package/dist/3204.js +2 -0
- package/dist/3204.js.map +1 -0
- package/dist/3584.js +1 -1
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/439.js +1 -0
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4944.js +1 -1
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5442.js +1 -1
- package/dist/5661.js +1 -1
- package/dist/5670.js +1 -1
- package/dist/5670.js.map +1 -1
- package/dist/6022.js +1 -1
- package/dist/6336.js +1 -0
- package/dist/6336.js.map +1 -0
- package/dist/6468.js +1 -1
- package/dist/6589.js +1 -0
- package/dist/6679.js +1 -1
- package/dist/6840.js +1 -1
- package/dist/6859.js +1 -1
- package/dist/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7545.js +2 -0
- package/dist/7545.js.map +1 -0
- package/dist/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8371.js +1 -0
- package/dist/8618.js +1 -1
- package/dist/8803.js +1 -1
- package/dist/8803.js.map +1 -1
- package/dist/890.js +1 -1
- package/dist/9214.js +1 -1
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9895.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9913.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-generic-patient-widgets-app.js +1 -1
- package/dist/openmrs-esm-generic-patient-widgets-app.js.buildmanifest.json +285 -219
- package/dist/openmrs-esm-generic-patient-widgets-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +4 -4
- package/src/config-schema-obs-horizontal.ts +12 -0
- package/src/config-schema-obs-switchable.ts +7 -1
- package/src/obs-graph/obs-graph.component.tsx +85 -36
- package/src/obs-graph/obs-graph.scss +19 -11
- package/src/obs-switchable/obs-switchable.component.tsx +12 -11
- package/src/obs-switchable/obs-switchable.test.tsx +145 -42
- package/src/obs-table/obs-table.component.tsx +104 -20
- package/src/obs-table-horizontal/obs-table-horizontal.component.tsx +470 -57
- package/src/obs-table-horizontal/obs-table-horizontal.resource.ts +67 -0
- package/src/obs-table-horizontal/obs-table-horizontal.scss +47 -0
- package/src/obs-table-horizontal/obs-table-horizontal.test.tsx +923 -0
- package/src/resources/useConcepts.ts +51 -0
- package/src/resources/useEncounterTypes.ts +34 -0
- package/src/resources/useObs.ts +40 -31
- package/translations/am.json +7 -1
- package/translations/ar.json +7 -1
- package/translations/ar_SY.json +7 -1
- package/translations/bn.json +7 -1
- package/translations/cs.json +10 -0
- package/translations/de.json +7 -1
- package/translations/en.json +7 -1
- package/translations/en_US.json +7 -1
- package/translations/es.json +7 -1
- package/translations/es_MX.json +7 -1
- package/translations/fr.json +7 -1
- package/translations/he.json +7 -1
- package/translations/hi.json +7 -1
- package/translations/hi_IN.json +7 -1
- package/translations/id.json +7 -1
- package/translations/it.json +7 -1
- package/translations/ka.json +7 -1
- package/translations/km.json +7 -1
- package/translations/ku.json +7 -1
- package/translations/ky.json +7 -1
- package/translations/lg.json +7 -1
- package/translations/ne.json +7 -1
- package/translations/pl.json +7 -1
- package/translations/pt.json +7 -1
- package/translations/pt_BR.json +7 -1
- package/translations/qu.json +7 -1
- package/translations/ro_RO.json +7 -1
- package/translations/ru_RU.json +7 -1
- package/translations/si.json +7 -1
- package/translations/sq.json +10 -0
- package/translations/sw.json +7 -1
- package/translations/sw_KE.json +7 -1
- package/translations/tr.json +7 -1
- package/translations/tr_TR.json +7 -1
- package/translations/uk.json +7 -1
- package/translations/uz.json +7 -1
- package/translations/uz@Latn.json +7 -1
- package/translations/uz_UZ.json +7 -1
- package/translations/vi.json +7 -1
- package/translations/zh.json +7 -1
- package/translations/zh_CN.json +7 -1
- package/translations/zh_TW.json +10 -0
- package/dist/1559.js +0 -2
- package/dist/1559.js.map +0 -1
- package/dist/251.js +0 -2
- package/dist/251.js.map +0 -1
- package/dist/5639.js +0 -1
- package/dist/5639.js.map +0 -1
- package/dist/5986.js +0 -1
- package/dist/5986.js.map +0 -1
- package/dist/6781.js +0 -2
- package/dist/6781.js.map +0 -1
- /package/dist/{251.js.LICENSE.txt → 2606.js.LICENSE.txt} +0 -0
- /package/dist/{6781.js.LICENSE.txt → 3204.js.LICENSE.txt} +0 -0
- /package/dist/{1559.js.LICENSE.txt → 7545.js.LICENSE.txt} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render, screen
|
|
2
|
+
import { prettyDOM, render, screen } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
4
|
import { LineChart } from '@carbon/charts-react';
|
|
5
5
|
import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
|
|
@@ -15,19 +15,12 @@ const mockLineChart = jest.mocked(LineChart);
|
|
|
15
15
|
|
|
16
16
|
const mockUseConfig = jest.mocked(useConfig);
|
|
17
17
|
|
|
18
|
+
// Make sure this respects the sort order of useObs
|
|
18
19
|
const mockObsData = [
|
|
19
20
|
{
|
|
20
21
|
code: { text: 'Height' },
|
|
21
22
|
conceptUuid: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
22
|
-
dataType: '
|
|
23
|
-
effectiveDateTime: '2021-01-01T00:00:00Z',
|
|
24
|
-
valueQuantity: { value: 180 },
|
|
25
|
-
encounter: { reference: 'Encounter/123' },
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
code: { text: 'Height' },
|
|
29
|
-
conceptUuid: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
30
|
-
dataType: 'Number',
|
|
23
|
+
dataType: 'Numeric',
|
|
31
24
|
effectiveDateTime: '2021-02-01T00:00:00Z',
|
|
32
25
|
valueQuantity: { value: 182 },
|
|
33
26
|
encounter: { reference: 'Encounter/234' },
|
|
@@ -35,18 +28,26 @@ const mockObsData = [
|
|
|
35
28
|
{
|
|
36
29
|
code: { text: 'Weight' },
|
|
37
30
|
conceptUuid: '2154AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
38
|
-
dataType: '
|
|
31
|
+
dataType: 'Numeric',
|
|
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: 'Numeric',
|
|
39
40
|
effectiveDateTime: '2021-01-01T00:00:00Z',
|
|
40
|
-
valueQuantity: { value:
|
|
41
|
+
valueQuantity: { value: 180 },
|
|
41
42
|
encounter: { reference: 'Encounter/123' },
|
|
42
43
|
},
|
|
43
44
|
{
|
|
44
45
|
code: { text: 'Weight' },
|
|
45
46
|
conceptUuid: '2154AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
46
|
-
dataType: '
|
|
47
|
-
effectiveDateTime: '2021-
|
|
48
|
-
valueQuantity: { value:
|
|
49
|
-
encounter: { reference: 'Encounter/
|
|
47
|
+
dataType: 'Numeric',
|
|
48
|
+
effectiveDateTime: '2021-01-01T00:00:00Z',
|
|
49
|
+
valueQuantity: { value: 70 },
|
|
50
|
+
encounter: { reference: 'Encounter/123' },
|
|
50
51
|
},
|
|
51
52
|
{
|
|
52
53
|
code: { text: 'Chief Complaint' },
|
|
@@ -59,22 +60,35 @@ const mockObsData = [
|
|
|
59
60
|
{
|
|
60
61
|
code: { text: 'Power Level' },
|
|
61
62
|
conceptUuid: '164163AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
62
|
-
dataType: '
|
|
63
|
+
dataType: 'Numeric',
|
|
63
64
|
effectiveDateTime: '2021-01-01T00:00:00Z',
|
|
64
65
|
valueQuantity: { value: 9001 },
|
|
65
66
|
encounter: { reference: 'Encounter/123' },
|
|
66
67
|
},
|
|
67
68
|
];
|
|
68
69
|
|
|
70
|
+
const mockConceptData = [
|
|
71
|
+
{ uuid: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Height', dataType: 'Numeric' },
|
|
72
|
+
{ uuid: '2154AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Weight', dataType: 'Numeric' },
|
|
73
|
+
{ uuid: '164162AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Chief Complaint', dataType: 'Text' },
|
|
74
|
+
{ uuid: '164163AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Power Level', dataType: 'Numeric' },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const mockEncounters = [
|
|
78
|
+
{ reference: 'Encounter/123', display: 'Outpatient Visit', encounterTypeUuid: 'encounter-type-uuid-1' },
|
|
79
|
+
{ reference: 'Encounter/234', display: 'Outpatient Visit', encounterTypeUuid: 'encounter-type-uuid-1' },
|
|
80
|
+
];
|
|
81
|
+
|
|
69
82
|
const mockUseObs = jest.mocked(useObs);
|
|
70
83
|
|
|
71
84
|
describe('ObsSwitchable', () => {
|
|
72
85
|
it('should render all obs in table and numeric obs in graph', async () => {
|
|
73
86
|
mockUseObs.mockReturnValue({
|
|
74
|
-
data: mockObsData as Array<ObsResult>,
|
|
87
|
+
data: { observations: mockObsData as Array<ObsResult>, concepts: mockConceptData, encounters: mockEncounters },
|
|
75
88
|
error: null,
|
|
76
89
|
isLoading: false,
|
|
77
90
|
isValidating: false,
|
|
91
|
+
mutate: jest.fn(),
|
|
78
92
|
});
|
|
79
93
|
mockUseConfig.mockReturnValue({
|
|
80
94
|
...(getDefaultsFromConfigSchema(configSchemaSwitchable) as Object),
|
|
@@ -102,15 +116,17 @@ describe('ObsSwitchable', () => {
|
|
|
102
116
|
expect(headerRow).toHaveTextContent('Chief Complaint');
|
|
103
117
|
expect(headerRow).toHaveTextContent('Power Level');
|
|
104
118
|
const firstRow = screen.getAllByRole('row')[1];
|
|
105
|
-
expect(firstRow).toHaveTextContent('
|
|
106
|
-
expect(firstRow).toHaveTextContent('
|
|
107
|
-
expect(firstRow).toHaveTextContent('
|
|
108
|
-
expect(firstRow).toHaveTextContent('
|
|
119
|
+
expect(firstRow).toHaveTextContent('Feb');
|
|
120
|
+
expect(firstRow).toHaveTextContent('182');
|
|
121
|
+
expect(firstRow).toHaveTextContent('72');
|
|
122
|
+
expect(firstRow).toHaveTextContent('--');
|
|
123
|
+
expect(firstRow).toHaveTextContent('--');
|
|
109
124
|
const secondRow = screen.getAllByRole('row')[2];
|
|
110
|
-
expect(secondRow).toHaveTextContent('
|
|
111
|
-
expect(secondRow).toHaveTextContent('
|
|
112
|
-
expect(secondRow).toHaveTextContent('
|
|
113
|
-
expect(secondRow).toHaveTextContent('
|
|
125
|
+
expect(secondRow).toHaveTextContent('Jan');
|
|
126
|
+
expect(secondRow).toHaveTextContent('180');
|
|
127
|
+
expect(secondRow).toHaveTextContent('70');
|
|
128
|
+
expect(secondRow).toHaveTextContent('Too strong');
|
|
129
|
+
expect(secondRow).toHaveTextContent('9001');
|
|
114
130
|
|
|
115
131
|
const user = userEvent.setup();
|
|
116
132
|
const chartViewButton = screen.getByLabelText('Chart view');
|
|
@@ -122,13 +138,14 @@ describe('ObsSwitchable', () => {
|
|
|
122
138
|
expect(tabs).toHaveTextContent('Weight');
|
|
123
139
|
expect(tabs).not.toHaveTextContent('Chief Complaint');
|
|
124
140
|
expect(tabs).toHaveTextContent('Power Level');
|
|
141
|
+
expect(tabs).not.toHaveTextContent('Mystery Concept');
|
|
125
142
|
|
|
126
143
|
expect(mockLineChart).toHaveBeenNthCalledWith(
|
|
127
144
|
1,
|
|
128
145
|
expect.objectContaining({
|
|
129
146
|
data: [
|
|
130
|
-
{ group: 'Tallitude', key: '
|
|
131
|
-
{ group: 'Tallitude', key: '01-
|
|
147
|
+
{ group: 'Tallitude', key: new Date('2021-02-01T00:00:00.000Z'), value: 182 },
|
|
148
|
+
{ group: 'Tallitude', key: new Date('2021-01-01T00:00:00.000Z'), value: 180 },
|
|
132
149
|
],
|
|
133
150
|
options: expect.any(Object),
|
|
134
151
|
}),
|
|
@@ -139,8 +156,8 @@ describe('ObsSwitchable', () => {
|
|
|
139
156
|
2,
|
|
140
157
|
expect.objectContaining({
|
|
141
158
|
data: [
|
|
142
|
-
{ group: 'Weight', key: '
|
|
143
|
-
{ group: 'Weight', key: '01-
|
|
159
|
+
{ group: 'Weight', key: new Date('2021-02-01T00:00:00.000Z'), value: 72 },
|
|
160
|
+
{ group: 'Weight', key: new Date('2021-01-01T00:00:00.000Z'), value: 70 },
|
|
144
161
|
],
|
|
145
162
|
options: expect.any(Object),
|
|
146
163
|
}),
|
|
@@ -158,19 +175,97 @@ describe('ObsSwitchable', () => {
|
|
|
158
175
|
expect(mockLineChart).toHaveBeenNthCalledWith(
|
|
159
176
|
3,
|
|
160
177
|
expect.objectContaining({
|
|
161
|
-
data: [{ group: 'Power Level', key: '01-
|
|
178
|
+
data: [{ group: 'Power Level', key: new Date('2021-01-01T00:00:00.000Z'), value: 9001 }],
|
|
162
179
|
options: expect.any(Object),
|
|
163
180
|
}),
|
|
164
181
|
{},
|
|
165
182
|
);
|
|
166
183
|
});
|
|
167
184
|
|
|
185
|
+
it('should sort by date and by obs correctly', async () => {
|
|
186
|
+
mockUseObs.mockReturnValue({
|
|
187
|
+
data: { observations: mockObsData as Array<ObsResult>, concepts: mockConceptData, encounters: mockEncounters },
|
|
188
|
+
error: null,
|
|
189
|
+
isLoading: false,
|
|
190
|
+
isValidating: false,
|
|
191
|
+
mutate: jest.fn(),
|
|
192
|
+
});
|
|
193
|
+
mockUseConfig.mockReturnValue({
|
|
194
|
+
...(getDefaultsFromConfigSchema(configSchemaSwitchable) as Object),
|
|
195
|
+
title: 'My Stats',
|
|
196
|
+
data: [
|
|
197
|
+
{
|
|
198
|
+
concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
199
|
+
label: 'Tallitude',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
concept: '2154AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
203
|
+
},
|
|
204
|
+
{ concept: '164162AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' },
|
|
205
|
+
{ concept: '164163AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' },
|
|
206
|
+
],
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
render(<ObsSwitchable patientUuid="123" />);
|
|
210
|
+
|
|
211
|
+
const user = userEvent.setup();
|
|
212
|
+
|
|
213
|
+
const firstRowInitial = screen.getAllByRole('row')[1];
|
|
214
|
+
expect(firstRowInitial).toHaveTextContent('01 — Feb — 2021');
|
|
215
|
+
const secondRowInitial = screen.getAllByRole('row')[2];
|
|
216
|
+
expect(secondRowInitial).toHaveTextContent('01 — Jan — 2021');
|
|
217
|
+
|
|
218
|
+
const dateHeader = screen.getByText('Date and time');
|
|
219
|
+
await user.click(dateHeader);
|
|
220
|
+
|
|
221
|
+
const firstRow = screen.getAllByRole('row')[1];
|
|
222
|
+
expect(firstRow).toHaveTextContent('01 — Feb — 2021');
|
|
223
|
+
const secondRow = screen.getAllByRole('row')[2];
|
|
224
|
+
expect(secondRow).toHaveTextContent('01 — Jan — 2021');
|
|
225
|
+
|
|
226
|
+
await user.click(dateHeader);
|
|
227
|
+
const firstRow2 = screen.getAllByRole('row')[1];
|
|
228
|
+
expect(firstRow2).toHaveTextContent('01 — Jan — 2021');
|
|
229
|
+
const secondRow2 = screen.getAllByRole('row')[2];
|
|
230
|
+
expect(secondRow2).toHaveTextContent('01 — Feb — 2021');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('supports table sorting oldest to newest', async () => {
|
|
234
|
+
mockUseObs.mockReturnValue({
|
|
235
|
+
data: { observations: mockObsData as Array<ObsResult>, concepts: mockConceptData, encounters: mockEncounters },
|
|
236
|
+
error: null,
|
|
237
|
+
isLoading: false,
|
|
238
|
+
isValidating: false,
|
|
239
|
+
mutate: jest.fn(),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
mockUseConfig.mockReturnValue({
|
|
243
|
+
...(getDefaultsFromConfigSchema(configSchemaSwitchable) as Object),
|
|
244
|
+
title: 'My Stats',
|
|
245
|
+
data: [{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }, { concept: '2154AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }],
|
|
246
|
+
tableSortOldestFirst: true,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
render(<ObsSwitchable patientUuid="123" />);
|
|
250
|
+
|
|
251
|
+
const user = userEvent.setup();
|
|
252
|
+
|
|
253
|
+
const dateHeader = screen.getByText('Date and time');
|
|
254
|
+
await user.click(dateHeader);
|
|
255
|
+
|
|
256
|
+
const firstRow = screen.getAllByRole('row')[1];
|
|
257
|
+
expect(firstRow).toHaveTextContent('01 — Jan — 2021');
|
|
258
|
+
const secondRow = screen.getAllByRole('row')[2];
|
|
259
|
+
expect(secondRow).toHaveTextContent('01 — Feb — 2021');
|
|
260
|
+
});
|
|
261
|
+
|
|
168
262
|
it('should support showing graph tab by default', async () => {
|
|
169
263
|
mockUseObs.mockReturnValue({
|
|
170
|
-
data: mockObsData as Array<ObsResult>,
|
|
264
|
+
data: { observations: mockObsData as Array<ObsResult>, concepts: mockConceptData, encounters: mockEncounters },
|
|
171
265
|
error: null,
|
|
172
266
|
isLoading: false,
|
|
173
267
|
isValidating: false,
|
|
268
|
+
mutate: jest.fn(),
|
|
174
269
|
});
|
|
175
270
|
mockUseConfig.mockReturnValue({
|
|
176
271
|
...(getDefaultsFromConfigSchema(configSchemaSwitchable) as Object),
|
|
@@ -189,8 +284,8 @@ describe('ObsSwitchable', () => {
|
|
|
189
284
|
1,
|
|
190
285
|
expect.objectContaining({
|
|
191
286
|
data: [
|
|
192
|
-
{ group: 'Height', key: '
|
|
193
|
-
{ group: 'Height', key: '01-
|
|
287
|
+
{ group: 'Height', key: new Date('2021-02-01T00:00:00.000Z'), value: 182 },
|
|
288
|
+
{ group: 'Height', key: new Date('2021-01-01T00:00:00.000Z'), value: 180 },
|
|
194
289
|
],
|
|
195
290
|
options: expect.any(Object),
|
|
196
291
|
}),
|
|
@@ -200,10 +295,11 @@ describe('ObsSwitchable', () => {
|
|
|
200
295
|
|
|
201
296
|
it('should support grouping into multiline graphs', async () => {
|
|
202
297
|
mockUseObs.mockReturnValue({
|
|
203
|
-
data: mockObsData as Array<ObsResult>,
|
|
298
|
+
data: { observations: mockObsData as Array<ObsResult>, concepts: mockConceptData, encounters: mockEncounters },
|
|
204
299
|
error: null,
|
|
205
300
|
isLoading: false,
|
|
206
301
|
isValidating: false,
|
|
302
|
+
mutate: jest.fn(),
|
|
207
303
|
});
|
|
208
304
|
mockUseConfig.mockReturnValue({
|
|
209
305
|
...(getDefaultsFromConfigSchema(configSchemaSwitchable) as Object),
|
|
@@ -225,7 +321,7 @@ describe('ObsSwitchable', () => {
|
|
|
225
321
|
expect(mockLineChart).toHaveBeenNthCalledWith(
|
|
226
322
|
1,
|
|
227
323
|
expect.objectContaining({
|
|
228
|
-
data: [{ group: 'Power Level', key: '01-
|
|
324
|
+
data: [{ group: 'Power Level', key: new Date('2021-01-01T00:00:00.000Z'), value: 9001 }],
|
|
229
325
|
options: expect.any(Object),
|
|
230
326
|
}),
|
|
231
327
|
{},
|
|
@@ -235,10 +331,10 @@ describe('ObsSwitchable', () => {
|
|
|
235
331
|
2,
|
|
236
332
|
expect.objectContaining({
|
|
237
333
|
data: [
|
|
238
|
-
{ group: 'Height', key: '
|
|
239
|
-
{ group: 'Height', key: '01-
|
|
240
|
-
{ group: 'Weight', key: '
|
|
241
|
-
{ group: 'Weight', key: '01-
|
|
334
|
+
{ group: 'Height', key: new Date('2021-02-01T00:00:00.000Z'), value: 182 },
|
|
335
|
+
{ group: 'Height', key: new Date('2021-01-01T00:00:00.000Z'), value: 180 },
|
|
336
|
+
{ group: 'Weight', key: new Date('2021-02-01T00:00:00.000Z'), value: 72 },
|
|
337
|
+
{ group: 'Weight', key: new Date('2021-01-01T00:00:00.000Z'), value: 70 },
|
|
242
338
|
],
|
|
243
339
|
options: expect.any(Object),
|
|
244
340
|
}),
|
|
@@ -248,10 +344,17 @@ describe('ObsSwitchable', () => {
|
|
|
248
344
|
|
|
249
345
|
it('should hide the graph tab selection if there is only one graph', async () => {
|
|
250
346
|
mockUseObs.mockReturnValue({
|
|
251
|
-
data:
|
|
347
|
+
data: {
|
|
348
|
+
observations: mockObsData.filter(
|
|
349
|
+
(o) => o.conceptUuid === '164163AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
350
|
+
) as Array<ObsResult>,
|
|
351
|
+
concepts: mockConceptData,
|
|
352
|
+
encounters: mockEncounters,
|
|
353
|
+
},
|
|
252
354
|
error: null,
|
|
253
355
|
isLoading: false,
|
|
254
356
|
isValidating: false,
|
|
357
|
+
mutate: jest.fn(),
|
|
255
358
|
});
|
|
256
359
|
mockUseConfig.mockReturnValue({
|
|
257
360
|
...(getDefaultsFromConfigSchema(configSchemaSwitchable) as Object),
|
|
@@ -266,7 +369,7 @@ describe('ObsSwitchable', () => {
|
|
|
266
369
|
|
|
267
370
|
expect(mockLineChart).toHaveBeenCalledWith(
|
|
268
371
|
expect.objectContaining({
|
|
269
|
-
data: [{ group: 'Power Level', key: '01-
|
|
372
|
+
data: [{ group: 'Power Level', key: new Date('2021-01-01T00:00:00.000Z'), value: 9001 }],
|
|
270
373
|
options: expect.any(Object),
|
|
271
374
|
}),
|
|
272
375
|
{},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo, useState, useCallback } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
DataTable,
|
|
4
4
|
Table,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
TableHead,
|
|
9
9
|
TableHeader,
|
|
10
10
|
TableRow,
|
|
11
|
+
type DataTableSortState,
|
|
11
12
|
} from '@carbon/react';
|
|
12
13
|
import { usePagination, useConfig, formatDatetime, formatDate, formatTime } from '@openmrs/esm-framework';
|
|
13
14
|
import { PatientChartPagination } from '@openmrs/esm-patient-common-lib';
|
|
@@ -20,33 +21,77 @@ interface ObsTableProps {
|
|
|
20
21
|
patientUuid: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
interface Row {
|
|
25
|
+
id: string;
|
|
26
|
+
date: string;
|
|
27
|
+
rawDate: string;
|
|
28
|
+
encounter: string;
|
|
29
|
+
[conceptUuid: string]: string | number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Header {
|
|
33
|
+
key: string;
|
|
34
|
+
header: string;
|
|
35
|
+
sortFunc: (rowA: Row, rowB: Row) => number;
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
const ObsTable: React.FC<ObsTableProps> = ({ patientUuid }) => {
|
|
24
39
|
const { t } = useTranslation();
|
|
25
40
|
const config = useConfig<ConfigObjectSwitchable>();
|
|
26
|
-
const {
|
|
27
|
-
|
|
41
|
+
const {
|
|
42
|
+
data: { observations, concepts },
|
|
43
|
+
} = useObs(patientUuid);
|
|
44
|
+
|
|
45
|
+
const uniqueEncounterReferences = [...new Set(observations.map((o) => o.encounter.reference))].sort();
|
|
28
46
|
const obssGroupedByEncounters = uniqueEncounterReferences.map((reference) =>
|
|
29
|
-
|
|
47
|
+
observations.filter((o) => o.encounter.reference === reference),
|
|
30
48
|
);
|
|
31
49
|
|
|
32
|
-
const tableHeaders =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
const tableHeaders: Array<Header> = useMemo(() => {
|
|
51
|
+
const headers: Array<Header> = [
|
|
52
|
+
{
|
|
53
|
+
key: 'date',
|
|
54
|
+
header: t('dateAndTime', 'Date and time'),
|
|
55
|
+
sortFunc: (rowA: Row, rowB: Row) => new Date(rowB.rawDate).getTime() - new Date(rowA.rawDate).getTime(),
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
if (config.showEncounterType) {
|
|
59
|
+
headers.splice(1, 0, {
|
|
60
|
+
key: 'encounter',
|
|
61
|
+
header: t('encounterType', 'Encounter type'),
|
|
62
|
+
sortFunc: (rowA: Row, rowB: Row) => rowA.encounter.localeCompare(rowB.encounter) as 1 | -1,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
headers.push(
|
|
66
|
+
...config.data.map(({ concept, label }) => ({
|
|
67
|
+
key: concept,
|
|
68
|
+
header: label || concepts.find((c) => c.uuid == concept)?.display,
|
|
69
|
+
sortFunc: (rowA: Row, rowB: Row) => {
|
|
70
|
+
const a = rowA[concept];
|
|
71
|
+
const b = rowB[concept];
|
|
72
|
+
if (a === b) {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
if (a == null) {
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
if (b == null) {
|
|
79
|
+
return -1;
|
|
80
|
+
}
|
|
81
|
+
return a < b ? 1 : -1;
|
|
82
|
+
},
|
|
83
|
+
})),
|
|
84
|
+
);
|
|
85
|
+
return headers;
|
|
86
|
+
}, [t, config.data, config.showEncounterType, concepts]);
|
|
43
87
|
|
|
44
|
-
const tableRows = React.useMemo(
|
|
88
|
+
const tableRows: Array<Row> = React.useMemo(
|
|
45
89
|
() =>
|
|
46
90
|
obssGroupedByEncounters?.map((obss, index) => {
|
|
47
91
|
const rowData = {
|
|
48
92
|
id: `${index}`,
|
|
49
93
|
date: formatDatetime(new Date(obss[0].effectiveDateTime), { mode: 'wide' }),
|
|
94
|
+
rawDate: obss[0].effectiveDateTime,
|
|
50
95
|
encounter: obss[0].encounter.name,
|
|
51
96
|
};
|
|
52
97
|
|
|
@@ -56,7 +101,7 @@ const ObsTable: React.FC<ObsTableProps> = ({ patientUuid }) => {
|
|
|
56
101
|
rowData[obs.conceptUuid] = obs.valueString;
|
|
57
102
|
break;
|
|
58
103
|
|
|
59
|
-
case '
|
|
104
|
+
case 'Numeric': {
|
|
60
105
|
const decimalPlaces: number | undefined = config.data.find(
|
|
61
106
|
(ele: any) => ele.concept === obs.conceptUuid,
|
|
62
107
|
)?.decimalPlaces;
|
|
@@ -98,11 +143,50 @@ const ObsTable: React.FC<ObsTableProps> = ({ patientUuid }) => {
|
|
|
98
143
|
[config.data, config?.dateFormat, obssGroupedByEncounters],
|
|
99
144
|
);
|
|
100
145
|
|
|
101
|
-
const {
|
|
146
|
+
const [sortParams, setSortParams] = useState<{ key: string; sortDirection: 'ASC' | 'DESC' | 'NONE' }>({
|
|
147
|
+
key: 'date',
|
|
148
|
+
sortDirection: config.tableSortOldestFirst ? 'ASC' : 'DESC',
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const handleSorting = useCallback(
|
|
152
|
+
(cellA: any, cellB: any, { key, sortDirection }: { key: string; sortDirection: DataTableSortState }) => {
|
|
153
|
+
// Use setTimeout to avoid setState during render
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
if (sortDirection === 'NONE') {
|
|
156
|
+
setSortParams({ key: '', sortDirection });
|
|
157
|
+
} else {
|
|
158
|
+
setSortParams({ key, sortDirection });
|
|
159
|
+
}
|
|
160
|
+
}, 0);
|
|
161
|
+
return 0;
|
|
162
|
+
},
|
|
163
|
+
[],
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const sortedData: Array<any> = useMemo(() => {
|
|
167
|
+
if (sortParams.sortDirection === 'NONE') {
|
|
168
|
+
return tableRows;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const header = tableHeaders.find((header) => header.key === sortParams.key);
|
|
172
|
+
|
|
173
|
+
if (!header) {
|
|
174
|
+
return tableRows;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const sortedRows = tableRows.slice().sort((rowA, rowB) => {
|
|
178
|
+
const sortingNum = header.sortFunc(rowA, rowB);
|
|
179
|
+
return sortParams.sortDirection === 'DESC' ? sortingNum : -sortingNum;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return sortedRows;
|
|
183
|
+
}, [tableHeaders, tableRows, sortParams]);
|
|
184
|
+
|
|
185
|
+
const { results, goTo, currentPage } = usePagination(sortedData, config.table.pageSize);
|
|
102
186
|
|
|
103
187
|
return (
|
|
104
188
|
<div>
|
|
105
|
-
<DataTable rows={results} headers={tableHeaders} isSortable size="sm" useZebraStyles>
|
|
189
|
+
<DataTable rows={results} headers={tableHeaders} isSortable sortRow={handleSorting} size="sm" useZebraStyles>
|
|
106
190
|
{({ rows, headers, getHeaderProps, getTableProps }) => (
|
|
107
191
|
<TableContainer>
|
|
108
192
|
<Table {...getTableProps()} className={styles.customRow}>
|
|
@@ -135,7 +219,7 @@ const ObsTable: React.FC<ObsTableProps> = ({ patientUuid }) => {
|
|
|
135
219
|
</DataTable>
|
|
136
220
|
<PatientChartPagination
|
|
137
221
|
pageNumber={currentPage}
|
|
138
|
-
totalItems={
|
|
222
|
+
totalItems={sortedData.length}
|
|
139
223
|
currentItems={results.length}
|
|
140
224
|
pageSize={config.table.pageSize}
|
|
141
225
|
onPageNumberChange={({ page }) => goTo(page)}
|