@kenyaemr/esm-active-visits-app 8.1.1-pre.124 → 8.1.2-pre.152
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 +17 -17
- package/dist/130.js +1 -1
- package/dist/130.js.map +1 -1
- package/dist/136.js +2 -0
- package/dist/136.js.map +1 -0
- package/dist/236.js +1 -0
- package/dist/240.js +1 -0
- package/dist/261.js +1 -0
- package/dist/271.js +1 -1
- package/dist/272.js +1 -0
- package/dist/319.js +1 -1
- package/dist/336.js +1 -0
- package/dist/378.js +1 -0
- package/dist/460.js +1 -1
- package/dist/539.js +1 -0
- package/dist/566.js +1 -0
- package/dist/6.js +1 -1
- package/dist/6.js.map +1 -1
- package/dist/652.js +1 -0
- package/dist/673.js +1 -0
- package/dist/705.js +1 -0
- package/dist/711.js +1 -0
- package/dist/725.js +1 -1
- package/dist/727.js +1 -0
- package/dist/737.js +1 -0
- package/dist/744.js +1 -0
- package/dist/899.js +1 -0
- package/dist/967.js +1 -1
- package/dist/kenyaemr-esm-active-visits-app.js +1 -1
- package/dist/kenyaemr-esm-active-visits-app.js.buildmanifest.json +389 -37
- package/dist/kenyaemr-esm-active-visits-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package-lock.json +6209 -0
- package/package.json +5 -5
- package/src/active-visits-widget/active-visits.component.tsx +156 -187
- package/src/active-visits-widget/active-visits.resource.tsx +202 -28
- package/src/active-visits-widget/active-visits.scss +18 -0
- package/src/active-visits-widget/active-visits.test.tsx +120 -83
- package/src/config-schema.ts +11 -1
- package/src/types/index.ts +156 -1
- package/src/visits-summary/visit-detail.component.tsx +3 -2
- package/src/visits-summary/visit-detail.test.tsx +27 -14
- package/src/visits-summary/visit.resource.ts +1 -135
- package/src/visits-summary/visits-components/encounter-list.component.tsx +9 -9
- package/src/visits-summary/visits-components/encounter-observations.component.tsx +1 -1
- package/src/visits-summary/visits-components/encounter-observations.test.tsx +1 -1
- package/src/visits-summary/visits-components/medications-summary.component.tsx +1 -1
- package/src/visits-summary/visits-components/notes-summary.component.tsx +1 -1
- package/src/visits-summary/visits-components/tests-summary.component.tsx +1 -1
- package/src/visits-summary/visits-components/visit-summary.component.tsx +4 -4
- package/translations/ar.json +6 -6
- package/translations/de.json +37 -0
- package/translations/es.json +11 -11
- package/translations/hi.json +37 -0
- package/translations/hi_IN.json +37 -0
- package/translations/id.json +37 -0
- package/translations/it.json +37 -0
- package/translations/ne.json +37 -0
- package/translations/pt.json +37 -0
- package/translations/pt_BR.json +37 -0
- package/translations/qu.json +37 -0
- package/translations/si.json +37 -0
- package/translations/sw.json +37 -0
- package/translations/sw_KE.json +37 -0
- package/translations/tr.json +37 -0
- package/translations/tr_TR.json +37 -0
- package/translations/uk.json +37 -0
- package/translations/vi.json +37 -0
- package/translations/zh.json +1 -1
- package/dist/586.js +0 -2
- package/dist/586.js.map +0 -1
- /package/dist/{586.js.LICENSE.txt → 136.js.LICENSE.txt} +0 -0
|
@@ -1,39 +1,24 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import useSWRInfinite from 'swr/infinite';
|
|
3
3
|
import dayjs from 'dayjs';
|
|
4
4
|
import isToday from 'dayjs/plugin/isToday';
|
|
5
5
|
import last from 'lodash-es/last';
|
|
6
6
|
import {
|
|
7
|
-
openmrsFetch,
|
|
8
|
-
type Visit,
|
|
9
|
-
useSession,
|
|
10
7
|
type FetchResponse,
|
|
11
8
|
formatDatetime,
|
|
9
|
+
openmrsFetch,
|
|
10
|
+
type OpenmrsResource,
|
|
12
11
|
parseDate,
|
|
13
|
-
useConfig,
|
|
14
12
|
restBaseUrl,
|
|
13
|
+
useConfig,
|
|
14
|
+
useSession,
|
|
15
|
+
type Visit,
|
|
15
16
|
} from '@openmrs/esm-framework';
|
|
16
|
-
|
|
17
|
+
import useSWR from 'swr';
|
|
18
|
+
import { type ActiveVisit, type VisitResponse } from '../types';
|
|
19
|
+
import { useTranslation } from 'react-i18next';
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
age: string;
|
|
20
|
-
id: string;
|
|
21
|
-
idNumber: string;
|
|
22
|
-
gender: string;
|
|
23
|
-
location: string;
|
|
24
|
-
name: string;
|
|
25
|
-
patientUuid: string;
|
|
26
|
-
visitStartTime: string;
|
|
27
|
-
visitType: string;
|
|
28
|
-
visitUuid: string;
|
|
29
|
-
[identifier: string]: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface VisitResponse {
|
|
33
|
-
results: Array<Visit>;
|
|
34
|
-
links: Array<{ rel: 'prev' | 'next' }>;
|
|
35
|
-
totalCount: number;
|
|
36
|
-
}
|
|
21
|
+
dayjs.extend(isToday);
|
|
37
22
|
|
|
38
23
|
export function useActiveVisits() {
|
|
39
24
|
const session = useSession();
|
|
@@ -41,8 +26,10 @@ export function useActiveVisits() {
|
|
|
41
26
|
const sessionLocation = session?.sessionLocation?.uuid;
|
|
42
27
|
|
|
43
28
|
const customRepresentation =
|
|
44
|
-
'custom:(uuid,patient:(uuid,identifiers:(identifier,uuid,identifierType:(name,uuid)),
|
|
45
|
-
'
|
|
29
|
+
'custom:(uuid,patient:(uuid,identifiers:(identifier,uuid,identifierType:(name,uuid)),' +
|
|
30
|
+
'person:(age,display,gender,uuid,attributes:(value,attributeType:(uuid,display)))),' +
|
|
31
|
+
'visitType:(uuid,name,display),location:(uuid,name,display),startDatetime,stopDatetime,' +
|
|
32
|
+
'encounters:(encounterDatetime,obs:(uuid,concept:(uuid,display),value)))';
|
|
46
33
|
|
|
47
34
|
const getUrl = (pageIndex, previousPageData: FetchResponse<VisitResponse>) => {
|
|
48
35
|
if (pageIndex && !previousPageData?.data?.links?.some((link) => link.rel === 'next')) {
|
|
@@ -76,7 +63,7 @@ export function useActiveVisits() {
|
|
|
76
63
|
if (data && data?.[pageNumber - 1]?.data?.links?.some((link) => link.rel === 'next')) {
|
|
77
64
|
setSize((currentSize) => currentSize + 1);
|
|
78
65
|
}
|
|
79
|
-
}, [data, pageNumber]);
|
|
66
|
+
}, [data, pageNumber, setSize]);
|
|
80
67
|
|
|
81
68
|
const mapVisitProperties = (visit: Visit): ActiveVisit => {
|
|
82
69
|
// create base object
|
|
@@ -126,6 +113,23 @@ export function useActiveVisits() {
|
|
|
126
113
|
activeVisits[header?.key] = personAttributes?.value ?? '--';
|
|
127
114
|
});
|
|
128
115
|
|
|
116
|
+
// Add flattened observations
|
|
117
|
+
const allObs = visit.encounters.reduce((accumulator, encounter) => {
|
|
118
|
+
return [...accumulator, ...(encounter.obs || [])];
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
activeVisits.observations = allObs.reduce((map, obs) => {
|
|
122
|
+
const key = obs.concept.uuid;
|
|
123
|
+
if (!map[key]) {
|
|
124
|
+
map[key] = [];
|
|
125
|
+
}
|
|
126
|
+
map[key].push({
|
|
127
|
+
value: obs.value,
|
|
128
|
+
uuid: obs.uuid,
|
|
129
|
+
});
|
|
130
|
+
return map;
|
|
131
|
+
}, {});
|
|
132
|
+
|
|
129
133
|
return activeVisits;
|
|
130
134
|
};
|
|
131
135
|
|
|
@@ -142,6 +146,176 @@ export function useActiveVisits() {
|
|
|
142
146
|
};
|
|
143
147
|
}
|
|
144
148
|
|
|
149
|
+
export function useObsConcepts(uuids: Array<string>): {
|
|
150
|
+
obsConcepts: Array<OpenmrsResource> | undefined;
|
|
151
|
+
isLoadingObsConcepts: boolean;
|
|
152
|
+
} {
|
|
153
|
+
const fetchConcept = async (uuid: string): Promise<OpenmrsResource | null> => {
|
|
154
|
+
try {
|
|
155
|
+
const response = await openmrsFetch(`${restBaseUrl}/concept/${uuid}?v=custom:(uuid,display)`);
|
|
156
|
+
return response?.data;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`Error fetching concept for UUID: ${uuid}`, error);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const { data, isLoading, error } = useSWR(uuids.length > 0 ? ['obs-concepts', uuids] : null, async () => {
|
|
164
|
+
const results = await Promise.all(uuids.map(fetchConcept));
|
|
165
|
+
return results.filter((concept) => concept !== null);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return useMemo(
|
|
169
|
+
() => ({
|
|
170
|
+
obsConcepts: data ?? [],
|
|
171
|
+
isLoadingObsConcepts: isLoading,
|
|
172
|
+
}),
|
|
173
|
+
[data, isLoading],
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function useActiveVisitsSorting(tableRows: Array<any>) {
|
|
178
|
+
const [sortParams, setSortParams] = useState<{
|
|
179
|
+
key: string;
|
|
180
|
+
sortDirection: 'ASC' | 'DESC' | 'NONE';
|
|
181
|
+
}>({ key: 'visitStartTime', sortDirection: 'DESC' });
|
|
182
|
+
|
|
183
|
+
const sortRow = (cellA, cellB, { key, sortDirection }) => {
|
|
184
|
+
setSortParams({ key, sortDirection });
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const getSortValue = (item: any, key: string) => {
|
|
188
|
+
// For observation columns
|
|
189
|
+
if (key.startsWith('obs-')) {
|
|
190
|
+
const conceptUuid = key.replace('obs-', '');
|
|
191
|
+
const obsValue = item?.observations?.[conceptUuid]?.[0]?.value;
|
|
192
|
+
|
|
193
|
+
if (!obsValue) return null;
|
|
194
|
+
if (typeof obsValue === 'object' && obsValue.display) {
|
|
195
|
+
return obsValue.display.toLowerCase();
|
|
196
|
+
}
|
|
197
|
+
return obsValue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const value = item[key];
|
|
201
|
+
if (value == null) return null;
|
|
202
|
+
|
|
203
|
+
if (key === 'visitStartTime') {
|
|
204
|
+
return new Date(value).getTime();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (key === 'age' && !isNaN(value)) {
|
|
208
|
+
return Number(value);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return String(value).toLowerCase();
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const sortedRows = useMemo(() => {
|
|
215
|
+
if (sortParams.sortDirection === 'NONE') {
|
|
216
|
+
return tableRows;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return [...tableRows].sort((a, b) => {
|
|
220
|
+
const valueA = getSortValue(a, sortParams.key);
|
|
221
|
+
const valueB = getSortValue(b, sortParams.key);
|
|
222
|
+
|
|
223
|
+
if (valueA === null && valueB === null) return 0;
|
|
224
|
+
if (valueA === null) return 1;
|
|
225
|
+
if (valueB === null) return -1;
|
|
226
|
+
|
|
227
|
+
if (typeof valueA === 'number' && typeof valueB === 'number') {
|
|
228
|
+
return sortParams.sortDirection === 'DESC' ? valueB - valueA : valueA - valueB;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const compareResult = String(valueA).localeCompare(String(valueB), undefined, {
|
|
232
|
+
numeric: true,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return sortParams.sortDirection === 'DESC' ? -compareResult : compareResult;
|
|
236
|
+
});
|
|
237
|
+
}, [sortParams, tableRows]);
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
sortedRows,
|
|
241
|
+
sortRow,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function useTableHeaders(obsConcepts: OpenmrsResource[]) {
|
|
246
|
+
const { t } = useTranslation();
|
|
247
|
+
const config = useConfig();
|
|
248
|
+
return useMemo(() => {
|
|
249
|
+
let headersIndex = 0;
|
|
250
|
+
|
|
251
|
+
const headers = [
|
|
252
|
+
{
|
|
253
|
+
id: headersIndex++,
|
|
254
|
+
header: t('visitStartTime', 'Visit Time'),
|
|
255
|
+
key: 'visitStartTime',
|
|
256
|
+
},
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
config?.activeVisits?.identifiers?.forEach((identifier) => {
|
|
260
|
+
headers.push({
|
|
261
|
+
id: headersIndex++,
|
|
262
|
+
header: t(identifier?.header?.key, identifier?.header?.default),
|
|
263
|
+
key: identifier?.header?.key,
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (!config?.activeVisits?.identifiers) {
|
|
268
|
+
headers.push({
|
|
269
|
+
id: headersIndex++,
|
|
270
|
+
header: t('idNumber', 'ID Number'),
|
|
271
|
+
key: 'idNumber',
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
config?.activeVisits?.attributes?.forEach((attribute) => {
|
|
276
|
+
headers.push({
|
|
277
|
+
id: headersIndex++,
|
|
278
|
+
header: t(attribute?.header?.key, attribute?.header?.default),
|
|
279
|
+
key: attribute?.header?.key,
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Add headers for obs concepts
|
|
284
|
+
obsConcepts?.forEach((concept) => {
|
|
285
|
+
headers.push({
|
|
286
|
+
id: headersIndex++,
|
|
287
|
+
header: concept.display,
|
|
288
|
+
key: `obs-${concept.uuid}`,
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
headers.push(
|
|
293
|
+
{
|
|
294
|
+
id: headersIndex++,
|
|
295
|
+
header: t('name', 'Name'),
|
|
296
|
+
key: 'name',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
id: headersIndex++,
|
|
300
|
+
header: t('gender', 'Gender'),
|
|
301
|
+
key: 'gender',
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: headersIndex++,
|
|
305
|
+
header: t('age', 'Age'),
|
|
306
|
+
key: 'age',
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
id: headersIndex++,
|
|
310
|
+
header: t('visitType', 'Visit Type'),
|
|
311
|
+
key: 'visitType',
|
|
312
|
+
},
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return headers;
|
|
316
|
+
}, [t, config, obsConcepts]);
|
|
317
|
+
}
|
|
318
|
+
|
|
145
319
|
export const getOriginFromPathName = (pathname = '') => {
|
|
146
320
|
const from = pathname.split('/');
|
|
147
321
|
return last(from);
|
|
@@ -24,6 +24,14 @@
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
.activeVisitsTable {
|
|
27
|
+
width: 100%;
|
|
28
|
+
|
|
29
|
+
th,
|
|
30
|
+
td {
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
text-align: left;
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
tbody tr[data-parent-row] {
|
|
28
36
|
// Don't show a bottom border on the last row so we don't end up with a double border from the activeVisitContainer
|
|
29
37
|
&:nth-last-of-type(2) > td {
|
|
@@ -154,12 +162,14 @@ html[dir='rtl'] {
|
|
|
154
162
|
.activeVisitsDetailHeaderContainer {
|
|
155
163
|
padding: layout.$spacing-04 layout.$spacing-05 layout.$spacing-04 0;
|
|
156
164
|
}
|
|
165
|
+
|
|
157
166
|
.desktopHeading,
|
|
158
167
|
.tabletHeading {
|
|
159
168
|
h4 {
|
|
160
169
|
text-align: right;
|
|
161
170
|
}
|
|
162
171
|
}
|
|
172
|
+
|
|
163
173
|
div[role='search'] {
|
|
164
174
|
& :first-child {
|
|
165
175
|
svg {
|
|
@@ -167,21 +177,29 @@ html[dir='rtl'] {
|
|
|
167
177
|
right: layout.$spacing-03;
|
|
168
178
|
}
|
|
169
179
|
}
|
|
180
|
+
|
|
170
181
|
& :last-child {
|
|
171
182
|
right: unset;
|
|
172
183
|
left: 0;
|
|
173
184
|
}
|
|
174
185
|
}
|
|
186
|
+
|
|
175
187
|
.tableContainer {
|
|
188
|
+
overflow-x: auto;
|
|
189
|
+
text-wrap: nowrap;
|
|
190
|
+
|
|
176
191
|
th > div {
|
|
177
192
|
text-align: right;
|
|
178
193
|
}
|
|
194
|
+
|
|
179
195
|
td {
|
|
180
196
|
text-align: right;
|
|
197
|
+
|
|
181
198
|
.serviceColor {
|
|
182
199
|
margin-right: 0;
|
|
183
200
|
margin-left: layout.$spacing-03;
|
|
184
201
|
}
|
|
202
|
+
|
|
185
203
|
button {
|
|
186
204
|
text-align: right;
|
|
187
205
|
}
|
|
@@ -1,41 +1,72 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import { render, screen } from '@testing-library/react';
|
|
4
|
-
import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { useActiveVisits } from './active-visits.resource';
|
|
4
|
+
import { getDefaultsFromConfigSchema, type OpenmrsResource, useConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import { mockSession } from '__mocks__';
|
|
6
|
+
import { type ActiveVisitsConfigSchema, configSchema } from '../config-schema';
|
|
7
|
+
import { useActiveVisits, useObsConcepts } from './active-visits.resource';
|
|
8
8
|
import ActiveVisitsTable from './active-visits.component';
|
|
9
|
+
import { type ActiveVisit, type Observation } from '../types';
|
|
9
10
|
|
|
10
11
|
const mockUseActiveVisits = jest.mocked(useActiveVisits);
|
|
11
|
-
const
|
|
12
|
+
const mockUseObsConcepts = jest.mocked(useObsConcepts);
|
|
13
|
+
const mockIsDesktop = jest.mocked(useObsConcepts);
|
|
14
|
+
const mockUseConfig = jest.mocked(useConfig<ActiveVisitsConfigSchema>);
|
|
12
15
|
|
|
13
16
|
jest.mock('./active-visits.resource', () => ({
|
|
14
17
|
...jest.requireActual('./active-visits.resource'),
|
|
15
18
|
useActiveVisits: jest.fn(),
|
|
19
|
+
useObsConcepts: jest.fn(),
|
|
16
20
|
}));
|
|
17
21
|
|
|
22
|
+
const mockObsConcepts: Array<OpenmrsResource> = [
|
|
23
|
+
{ uuid: '160225AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Sickle cell screening test' },
|
|
24
|
+
{ uuid: '5484AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Nutritional support' },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const mockConfig: ActiveVisitsConfigSchema = {
|
|
28
|
+
activeVisits: {
|
|
29
|
+
...getDefaultsFromConfigSchema(configSchema).activeVisits,
|
|
30
|
+
obs: ['160225AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', '5484AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const mockActiveVisits: ActiveVisit[] = [
|
|
35
|
+
{
|
|
36
|
+
age: '20',
|
|
37
|
+
gender: 'male',
|
|
38
|
+
id: '1',
|
|
39
|
+
idNumber: '000001A',
|
|
40
|
+
location: mockSession.data.sessionLocation.uuid,
|
|
41
|
+
name: 'John Doe',
|
|
42
|
+
patientUuid: 'uuid1',
|
|
43
|
+
visitStartTime: '',
|
|
44
|
+
visitType: 'Checkup',
|
|
45
|
+
visitUuid: 'visit-uuid-1',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
age: '25',
|
|
49
|
+
gender: 'female',
|
|
50
|
+
id: '2',
|
|
51
|
+
idNumber: '000001B',
|
|
52
|
+
location: mockSession.data.sessionLocation.uuid,
|
|
53
|
+
name: 'Some One',
|
|
54
|
+
patientUuid: 'uuid2',
|
|
55
|
+
visitStartTime: '',
|
|
56
|
+
visitType: 'Checkup',
|
|
57
|
+
visitUuid: 'visit-uuid-2',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
18
61
|
describe('ActiveVisitsTable', () => {
|
|
19
62
|
beforeEach(() => {
|
|
20
|
-
mockUseConfig.mockReturnValue(
|
|
21
|
-
|
|
63
|
+
mockUseConfig.mockReturnValue(mockConfig);
|
|
64
|
+
mockUseObsConcepts.mockReturnValue({
|
|
65
|
+
obsConcepts: mockObsConcepts,
|
|
66
|
+
isLoadingObsConcepts: false,
|
|
22
67
|
});
|
|
23
|
-
|
|
24
68
|
mockUseActiveVisits.mockReturnValue({
|
|
25
|
-
activeVisits:
|
|
26
|
-
{
|
|
27
|
-
age: '20',
|
|
28
|
-
gender: 'male',
|
|
29
|
-
id: '1',
|
|
30
|
-
idNumber: mockPatient.uuid,
|
|
31
|
-
location: mockSession.data.sessionLocation.uuid,
|
|
32
|
-
name: 'John Doe',
|
|
33
|
-
patientUuid: 'uuid1',
|
|
34
|
-
visitStartTime: '',
|
|
35
|
-
visitType: 'Checkup',
|
|
36
|
-
visitUuid: 'visit-uuid-1',
|
|
37
|
-
},
|
|
38
|
-
],
|
|
69
|
+
activeVisits: mockActiveVisits,
|
|
39
70
|
isLoading: false,
|
|
40
71
|
isValidating: false,
|
|
41
72
|
error: undefined,
|
|
@@ -43,51 +74,82 @@ describe('ActiveVisitsTable', () => {
|
|
|
43
74
|
});
|
|
44
75
|
});
|
|
45
76
|
|
|
46
|
-
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
jest.clearAllMocks();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('renders data table with standard and observation columns', () => {
|
|
82
|
+
mockUseActiveVisits.mockReturnValue({
|
|
83
|
+
activeVisits: mockActiveVisits.map((visit) => ({
|
|
84
|
+
...visit,
|
|
85
|
+
observations: {
|
|
86
|
+
'160225AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': [
|
|
87
|
+
{
|
|
88
|
+
value: { uuid: '1065AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Patient is sick' },
|
|
89
|
+
uuid: 'obs-uuid-1',
|
|
90
|
+
} as unknown as Observation,
|
|
91
|
+
],
|
|
92
|
+
'5484AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': [
|
|
93
|
+
{
|
|
94
|
+
value: 'Not done',
|
|
95
|
+
uuid: 'obs-uuid-2',
|
|
96
|
+
} as unknown as Observation,
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
})),
|
|
100
|
+
isLoading: false,
|
|
101
|
+
isValidating: false,
|
|
102
|
+
error: undefined,
|
|
103
|
+
totalResults: 0,
|
|
104
|
+
});
|
|
105
|
+
|
|
47
106
|
render(<ActiveVisitsTable />);
|
|
48
107
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
expectedColumnHeaders.forEach((header) => {
|
|
53
|
-
expect(screen.getByRole('columnheader', { name: new RegExp(header, 'i') })).toBeInTheDocument();
|
|
108
|
+
const standardColumnHeaders = [/Visit Time/, /ID Number/, /Name/, /Gender/, /Age/, /Visit Type/];
|
|
109
|
+
standardColumnHeaders.forEach((header) => {
|
|
110
|
+
expect(screen.getByRole('columnheader', { name: header })).toBeInTheDocument();
|
|
54
111
|
});
|
|
55
112
|
|
|
56
|
-
|
|
57
|
-
expect(
|
|
58
|
-
|
|
113
|
+
expect(screen.getByRole('columnheader', { name: /Sickle cell screening test/ })).toBeInTheDocument();
|
|
114
|
+
expect(screen.getByRole('columnheader', { name: /Nutritional support/ })).toBeInTheDocument();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('displays observation values correctly', () => {
|
|
118
|
+
mockUseActiveVisits.mockReturnValue({
|
|
119
|
+
activeVisits: mockActiveVisits.map((visit) => ({
|
|
120
|
+
...visit,
|
|
121
|
+
observations: {
|
|
122
|
+
'160225AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': [
|
|
123
|
+
{
|
|
124
|
+
value: { uuid: '1065AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Patient is sick' },
|
|
125
|
+
uuid: 'obs-uuid-1',
|
|
126
|
+
} as unknown as Observation,
|
|
127
|
+
],
|
|
128
|
+
'5484AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': [
|
|
129
|
+
{
|
|
130
|
+
value: 'Not done',
|
|
131
|
+
uuid: 'obs-uuid-2',
|
|
132
|
+
} as unknown as Observation,
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
})),
|
|
136
|
+
isLoading: false,
|
|
137
|
+
isValidating: false,
|
|
138
|
+
error: undefined,
|
|
139
|
+
totalResults: 0,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
render(<ActiveVisitsTable />);
|
|
143
|
+
|
|
144
|
+
expect(screen.getAllByRole('cell', { name: /Patient is sick/ }).length).toBe(2);
|
|
145
|
+
expect(screen.getAllByRole('cell', { name: /Not done/ }).length).toBe(2);
|
|
59
146
|
});
|
|
60
147
|
|
|
61
148
|
it('filters active visits based on search input', async () => {
|
|
62
149
|
const user = userEvent.setup();
|
|
63
150
|
|
|
64
151
|
mockUseActiveVisits.mockReturnValue({
|
|
65
|
-
activeVisits:
|
|
66
|
-
{
|
|
67
|
-
age: '20',
|
|
68
|
-
gender: 'male',
|
|
69
|
-
id: '1',
|
|
70
|
-
idNumber: '000001A',
|
|
71
|
-
location: mockSession.data.sessionLocation.uuid,
|
|
72
|
-
name: 'John Doe',
|
|
73
|
-
patientUuid: 'uuid1',
|
|
74
|
-
visitStartTime: '',
|
|
75
|
-
visitType: 'Checkup',
|
|
76
|
-
visitUuid: 'visit-uuid-1',
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
age: '25',
|
|
80
|
-
gender: 'female',
|
|
81
|
-
id: '2',
|
|
82
|
-
idNumber: '000001B',
|
|
83
|
-
location: mockSession.data.sessionLocation.uuid,
|
|
84
|
-
name: 'Some One',
|
|
85
|
-
patientUuid: 'uuid2',
|
|
86
|
-
visitStartTime: '',
|
|
87
|
-
visitType: 'Checkup',
|
|
88
|
-
visitUuid: 'visit-uuid-2',
|
|
89
|
-
},
|
|
90
|
-
],
|
|
152
|
+
activeVisits: mockActiveVisits,
|
|
91
153
|
isLoading: false,
|
|
92
154
|
isValidating: false,
|
|
93
155
|
error: undefined,
|
|
@@ -151,32 +213,7 @@ describe('ActiveVisitsTable', () => {
|
|
|
151
213
|
|
|
152
214
|
it('should display the pagination when pagination is true', () => {
|
|
153
215
|
mockUseActiveVisits.mockReturnValue({
|
|
154
|
-
activeVisits:
|
|
155
|
-
{
|
|
156
|
-
age: '20',
|
|
157
|
-
gender: 'male',
|
|
158
|
-
id: '1',
|
|
159
|
-
idNumber: '000001A',
|
|
160
|
-
location: mockSession.data.sessionLocation.uuid,
|
|
161
|
-
name: 'John Doe',
|
|
162
|
-
patientUuid: 'uuid1',
|
|
163
|
-
visitStartTime: '',
|
|
164
|
-
visitType: 'Checkup',
|
|
165
|
-
visitUuid: 'visit-uuid-1',
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
age: '25',
|
|
169
|
-
gender: 'female',
|
|
170
|
-
id: '2',
|
|
171
|
-
idNumber: '000001B',
|
|
172
|
-
location: mockSession.data.sessionLocation.uuid,
|
|
173
|
-
name: 'Some One',
|
|
174
|
-
patientUuid: 'uuid2',
|
|
175
|
-
visitStartTime: '',
|
|
176
|
-
visitType: 'Checkup',
|
|
177
|
-
visitUuid: 'visit-uuid-2',
|
|
178
|
-
},
|
|
179
|
-
],
|
|
216
|
+
activeVisits: mockActiveVisits,
|
|
180
217
|
isLoading: false,
|
|
181
218
|
isValidating: false,
|
|
182
219
|
error: undefined,
|
package/src/config-schema.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Type } from '@openmrs/esm-framework';
|
|
2
2
|
|
|
3
|
-
export interface
|
|
3
|
+
export interface ActiveVisitsConfigSchema {
|
|
4
4
|
activeVisits: {
|
|
5
5
|
pageSize: Number;
|
|
6
6
|
pageSizes: Array<Number>;
|
|
7
7
|
identifiers: Array<IdentifiersDefinition>;
|
|
8
|
+
obs: Array<string>;
|
|
8
9
|
};
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -53,5 +54,14 @@ export const configSchema = {
|
|
|
53
54
|
_description: 'Customizable page sizes that user can choose',
|
|
54
55
|
_default: [10, 20, 50],
|
|
55
56
|
},
|
|
57
|
+
obs: {
|
|
58
|
+
_type: Type.Array,
|
|
59
|
+
_description: 'Array of observation concept UUIDs to be displayed on the active visits table.',
|
|
60
|
+
_elements: {
|
|
61
|
+
_type: Type.UUID,
|
|
62
|
+
_description: 'UUID of an observation concept.',
|
|
63
|
+
},
|
|
64
|
+
_default: [],
|
|
65
|
+
},
|
|
56
66
|
},
|
|
57
67
|
};
|