@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2668 → 5.4.2-pre.2676
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 +5 -5
- package/dist/127.js +1 -1
- package/dist/195.js +1 -0
- package/dist/195.js.map +1 -0
- package/dist/295.js +1 -1
- package/dist/295.js.map +1 -1
- package/dist/40.js +1 -1
- package/dist/916.js +1 -1
- package/dist/kenyaemr-esm-patient-clinical-view-app.js +2 -2
- package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +37 -37
- package/dist/main.js +3 -3
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/case-management/encounters/case-encounter-overview.component.tsx +3 -2
- package/src/case-management/encounters/case-encounter-table.resource.ts +47 -1
- package/src/case-management/encounters/patient-case-form.scss +36 -0
- package/src/case-management/encounters/patient-case.workspace.tsx +182 -0
- package/src/case-management/encounters/patient-has-active-case.component.tsx.tsx +34 -0
- package/src/config-schema.ts +8 -0
- package/src/in-patient/admission-request.component.tsx +117 -0
- package/src/in-patient/admission-request.test.tsx +450 -0
- package/src/in-patient/in-patient.component.tsx +3 -68
- package/src/in-patient/in-patient.resource.tsx +32 -2
- package/src/index.ts +2 -0
- package/src/routes.json +6 -0
- package/src/types/index.ts +28 -1
- package/translations/am.json +6 -4
- package/translations/en.json +6 -4
- package/translations/sw.json +6 -4
- package/dist/941.js +0 -1
- package/dist/941.js.map +0 -1
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { launchWorkspace, formatDatetime, useLayoutType, isDesktop } from '@openmrs/esm-framework';
|
|
5
|
+
|
|
6
|
+
import AdmissionRequest from './admission-request.component';
|
|
7
|
+
import { useAdmissionRequest } from './in-patient.resource';
|
|
8
|
+
import { type AdmissionRequest as AdmissionRequestType } from '../types';
|
|
9
|
+
|
|
10
|
+
const mockUseAdmissionRequest = useAdmissionRequest as jest.MockedFunction<typeof useAdmissionRequest>;
|
|
11
|
+
const mockLaunchWorkspace = launchWorkspace as jest.MockedFunction<typeof launchWorkspace>;
|
|
12
|
+
const mockFormatDatetime = formatDatetime as jest.MockedFunction<typeof formatDatetime>;
|
|
13
|
+
const mockUseLayoutType = useLayoutType as jest.MockedFunction<typeof useLayoutType>;
|
|
14
|
+
const mockIsDesktop = isDesktop as jest.MockedFunction<typeof isDesktop>;
|
|
15
|
+
|
|
16
|
+
jest.mock('./in-patient.resource', () => ({
|
|
17
|
+
useAdmissionRequest: jest.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
21
|
+
...jest.requireActual('@openmrs/esm-framework'),
|
|
22
|
+
useConfig: jest.fn().mockReturnValue({
|
|
23
|
+
formsList: {
|
|
24
|
+
admissionRequestFormUuid: 'test-admission-form-uuid',
|
|
25
|
+
},
|
|
26
|
+
}),
|
|
27
|
+
formatDatetime: jest.fn().mockReturnValue('2023-10-16 10:30 AM'),
|
|
28
|
+
useLayoutType: jest.fn().mockReturnValue('desktop'),
|
|
29
|
+
isDesktop: jest.fn().mockReturnValue(true),
|
|
30
|
+
launchWorkspace: jest.fn(),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const mockMutate = jest.fn();
|
|
34
|
+
const patientUuid = 'test-patient-uuid';
|
|
35
|
+
|
|
36
|
+
const mockAdmissionRequestData: AdmissionRequestType[] = [
|
|
37
|
+
{
|
|
38
|
+
patient: {
|
|
39
|
+
uuid: 'test-patient-uuid',
|
|
40
|
+
person: {
|
|
41
|
+
uuid: 'test-person-uuid',
|
|
42
|
+
age: 35,
|
|
43
|
+
dead: false,
|
|
44
|
+
display: 'John Doe',
|
|
45
|
+
causeOfDeath: '',
|
|
46
|
+
gender: 'M',
|
|
47
|
+
deathDate: '',
|
|
48
|
+
attributes: [],
|
|
49
|
+
},
|
|
50
|
+
identifiers: [{ uuid: 'test-identifier-uuid' }],
|
|
51
|
+
},
|
|
52
|
+
dispositionLocation: {
|
|
53
|
+
uuid: 'test-location-uuid',
|
|
54
|
+
display: 'Ward A',
|
|
55
|
+
},
|
|
56
|
+
dispositionType: 'ADMIT',
|
|
57
|
+
disposition: {
|
|
58
|
+
uuid: 'test-disposition-uuid',
|
|
59
|
+
display: 'Admit to hospital',
|
|
60
|
+
answers: [],
|
|
61
|
+
},
|
|
62
|
+
dispositionEncounter: {
|
|
63
|
+
uuid: 'test-encounter-uuid',
|
|
64
|
+
encounterDatetime: '2023-10-16T10:30:00.000+0000',
|
|
65
|
+
patient: { uuid: 'test-patient-uuid' },
|
|
66
|
+
location: { uuid: 'test-location-uuid' },
|
|
67
|
+
form: {
|
|
68
|
+
name: 'Admission Form',
|
|
69
|
+
uuid: 'test-form-uuid',
|
|
70
|
+
version: '1.0',
|
|
71
|
+
published: true,
|
|
72
|
+
retired: false,
|
|
73
|
+
resources: [],
|
|
74
|
+
encounterType: {
|
|
75
|
+
uuid: 'test-encounter-type-uuid',
|
|
76
|
+
name: 'Admission',
|
|
77
|
+
viewPrivilege: null,
|
|
78
|
+
editPrivilege: null,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
encounterType: { uuid: 'test-encounter-type-uuid', name: 'Admission', display: 'Admission' },
|
|
82
|
+
obs: [],
|
|
83
|
+
orders: [],
|
|
84
|
+
voided: false,
|
|
85
|
+
auditInfo: {
|
|
86
|
+
creator: { uuid: 'test-creator-uuid', display: 'Admin' },
|
|
87
|
+
dateCreated: '2023-10-16T10:30:00.000+0000',
|
|
88
|
+
changedBy: null,
|
|
89
|
+
dateChanged: null,
|
|
90
|
+
},
|
|
91
|
+
visit: {
|
|
92
|
+
uuid: 'test-visit-uuid',
|
|
93
|
+
startDatetime: '2023-10-16T10:00:00.000+0000',
|
|
94
|
+
encounters: [],
|
|
95
|
+
},
|
|
96
|
+
encounterProviders: [],
|
|
97
|
+
diagnoses: [],
|
|
98
|
+
resourceVersion: '1.9',
|
|
99
|
+
},
|
|
100
|
+
dispositionObsGroup: {
|
|
101
|
+
uuid: 'test-obs-group-uuid',
|
|
102
|
+
concept: { uuid: 'test-concept-uuid' },
|
|
103
|
+
value: null,
|
|
104
|
+
obsDatetime: '2023-10-16T10:30:00.000+0000',
|
|
105
|
+
},
|
|
106
|
+
visit: {
|
|
107
|
+
uuid: 'test-visit-uuid',
|
|
108
|
+
startDatetime: '2023-10-16T10:00:00.000+0000',
|
|
109
|
+
encounters: [],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
patient: {
|
|
114
|
+
uuid: 'test-patient-uuid-2',
|
|
115
|
+
person: {
|
|
116
|
+
uuid: 'test-person-uuid-2',
|
|
117
|
+
age: 42,
|
|
118
|
+
dead: false,
|
|
119
|
+
display: 'Jane Smith',
|
|
120
|
+
causeOfDeath: '',
|
|
121
|
+
gender: 'F',
|
|
122
|
+
deathDate: '',
|
|
123
|
+
attributes: [],
|
|
124
|
+
},
|
|
125
|
+
identifiers: [{ uuid: 'test-identifier-uuid-2' }],
|
|
126
|
+
},
|
|
127
|
+
dispositionLocation: {
|
|
128
|
+
uuid: 'test-location-uuid-2',
|
|
129
|
+
display: 'Ward B',
|
|
130
|
+
},
|
|
131
|
+
dispositionType: 'ADMIT',
|
|
132
|
+
disposition: {
|
|
133
|
+
uuid: 'test-disposition-uuid-2',
|
|
134
|
+
display: 'Admit to ICU',
|
|
135
|
+
answers: [],
|
|
136
|
+
},
|
|
137
|
+
dispositionEncounter: {
|
|
138
|
+
uuid: 'test-encounter-uuid-2',
|
|
139
|
+
encounterDatetime: '2023-10-15T14:30:00.000+0000',
|
|
140
|
+
patient: { uuid: 'test-patient-uuid-2' },
|
|
141
|
+
location: { uuid: 'test-location-uuid-2' },
|
|
142
|
+
form: {
|
|
143
|
+
name: 'Admission Form',
|
|
144
|
+
uuid: 'test-form-uuid',
|
|
145
|
+
version: '1.0',
|
|
146
|
+
published: true,
|
|
147
|
+
retired: false,
|
|
148
|
+
resources: [],
|
|
149
|
+
encounterType: {
|
|
150
|
+
uuid: 'test-encounter-type-uuid',
|
|
151
|
+
name: 'Admission',
|
|
152
|
+
viewPrivilege: null,
|
|
153
|
+
editPrivilege: null,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
encounterType: { uuid: 'test-encounter-type-uuid', name: 'Admission', display: 'Admission' },
|
|
157
|
+
obs: [],
|
|
158
|
+
orders: [],
|
|
159
|
+
voided: false,
|
|
160
|
+
auditInfo: {
|
|
161
|
+
creator: { uuid: 'test-creator-uuid', display: 'Admin' },
|
|
162
|
+
dateCreated: '2023-10-15T14:30:00.000+0000',
|
|
163
|
+
changedBy: null,
|
|
164
|
+
dateChanged: null,
|
|
165
|
+
},
|
|
166
|
+
visit: {
|
|
167
|
+
uuid: 'test-visit-uuid-2',
|
|
168
|
+
startDatetime: '2023-10-15T14:00:00.000+0000',
|
|
169
|
+
encounters: [],
|
|
170
|
+
},
|
|
171
|
+
encounterProviders: [],
|
|
172
|
+
diagnoses: [],
|
|
173
|
+
resourceVersion: '1.9',
|
|
174
|
+
},
|
|
175
|
+
dispositionObsGroup: {
|
|
176
|
+
uuid: 'test-obs-group-uuid-2',
|
|
177
|
+
concept: { uuid: 'test-concept-uuid' },
|
|
178
|
+
value: null,
|
|
179
|
+
obsDatetime: '2023-10-15T14:30:00.000+0000',
|
|
180
|
+
},
|
|
181
|
+
visit: {
|
|
182
|
+
uuid: 'test-visit-uuid-2',
|
|
183
|
+
startDatetime: '2023-10-15T14:00:00.000+0000',
|
|
184
|
+
encounters: [],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
describe('AdmissionRequest', () => {
|
|
190
|
+
beforeEach(() => {
|
|
191
|
+
jest.clearAllMocks();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('should render loading skeleton when data is loading', () => {
|
|
195
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
196
|
+
admissionRequest: [],
|
|
197
|
+
isLoading: true,
|
|
198
|
+
error: undefined,
|
|
199
|
+
mutate: mockMutate,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
203
|
+
|
|
204
|
+
const loadingSkeleton = screen.getByRole('table');
|
|
205
|
+
expect(loadingSkeleton).toBeInTheDocument();
|
|
206
|
+
expect(loadingSkeleton).toHaveClass('cds--skeleton cds--data-table');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('should render error state when there is an error', () => {
|
|
210
|
+
const errorMessage = 'Failed to fetch admission requests';
|
|
211
|
+
const error = new Error(errorMessage);
|
|
212
|
+
|
|
213
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
214
|
+
admissionRequest: [],
|
|
215
|
+
isLoading: false,
|
|
216
|
+
error: error,
|
|
217
|
+
mutate: mockMutate,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
221
|
+
|
|
222
|
+
expect(screen.getByText(/Admission Request/i)).toBeInTheDocument();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('should render empty state when there are no admission requests', () => {
|
|
226
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
227
|
+
admissionRequest: [],
|
|
228
|
+
isLoading: false,
|
|
229
|
+
error: undefined,
|
|
230
|
+
mutate: mockMutate,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
234
|
+
|
|
235
|
+
// Check for the header title
|
|
236
|
+
expect(screen.getByText(/Admission Request/i)).toBeInTheDocument();
|
|
237
|
+
// Check for the empty state button
|
|
238
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('should launch admission request form when clicking add button in empty state', async () => {
|
|
242
|
+
const user = userEvent.setup();
|
|
243
|
+
|
|
244
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
245
|
+
admissionRequest: [],
|
|
246
|
+
isLoading: false,
|
|
247
|
+
error: undefined,
|
|
248
|
+
mutate: mockMutate,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
252
|
+
|
|
253
|
+
const addButton = screen.getByRole('button');
|
|
254
|
+
await user.click(addButton);
|
|
255
|
+
|
|
256
|
+
expect(mockLaunchWorkspace).toHaveBeenCalledWith('patient-form-entry-workspace', {
|
|
257
|
+
workspaceTitle: 'Admission Request',
|
|
258
|
+
mutateForm: mockMutate,
|
|
259
|
+
formInfo: {
|
|
260
|
+
formUuid: 'test-admission-form-uuid',
|
|
261
|
+
encounterUuid: '',
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('should render admission requests table with data', () => {
|
|
267
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
268
|
+
admissionRequest: mockAdmissionRequestData,
|
|
269
|
+
isLoading: false,
|
|
270
|
+
error: undefined,
|
|
271
|
+
mutate: mockMutate,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
275
|
+
|
|
276
|
+
// Check table headers
|
|
277
|
+
expect(screen.getByText('Request Datetime')).toBeInTheDocument();
|
|
278
|
+
expect(screen.getByText('Admission Location')).toBeInTheDocument();
|
|
279
|
+
expect(screen.getByText('Admission Type')).toBeInTheDocument();
|
|
280
|
+
expect(screen.getByText('Disposition')).toBeInTheDocument();
|
|
281
|
+
|
|
282
|
+
// Check table data
|
|
283
|
+
expect(screen.getByText('Ward A')).toBeInTheDocument();
|
|
284
|
+
expect(screen.getByText('Ward B')).toBeInTheDocument();
|
|
285
|
+
expect(screen.getByText('Admit to hospital')).toBeInTheDocument();
|
|
286
|
+
expect(screen.getByText('Admit to ICU')).toBeInTheDocument();
|
|
287
|
+
expect(screen.getAllByText('ADMIT')).toHaveLength(2);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should render Add button in header when data is available', () => {
|
|
291
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
292
|
+
admissionRequest: mockAdmissionRequestData,
|
|
293
|
+
isLoading: false,
|
|
294
|
+
error: undefined,
|
|
295
|
+
mutate: mockMutate,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
299
|
+
|
|
300
|
+
const addButton = screen.getByRole('button', { name: /Add/i });
|
|
301
|
+
expect(addButton).toBeInTheDocument();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should launch admission request form when clicking Add button', async () => {
|
|
305
|
+
const user = userEvent.setup();
|
|
306
|
+
|
|
307
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
308
|
+
admissionRequest: mockAdmissionRequestData,
|
|
309
|
+
isLoading: false,
|
|
310
|
+
error: undefined,
|
|
311
|
+
mutate: mockMutate,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
315
|
+
|
|
316
|
+
const addButton = screen.getByRole('button', { name: /Add/i });
|
|
317
|
+
await user.click(addButton);
|
|
318
|
+
|
|
319
|
+
expect(mockLaunchWorkspace).toHaveBeenCalledWith('patient-form-entry-workspace', {
|
|
320
|
+
workspaceTitle: 'Admission Request',
|
|
321
|
+
mutateForm: mockMutate,
|
|
322
|
+
formInfo: {
|
|
323
|
+
formUuid: 'test-admission-form-uuid',
|
|
324
|
+
encounterUuid: '',
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test('should format dates correctly in the table', () => {
|
|
330
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
331
|
+
admissionRequest: mockAdmissionRequestData,
|
|
332
|
+
isLoading: false,
|
|
333
|
+
error: undefined,
|
|
334
|
+
mutate: mockMutate,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
338
|
+
|
|
339
|
+
expect(mockFormatDatetime).toHaveBeenCalled();
|
|
340
|
+
expect(screen.getAllByText('2023-10-16 10:30 AM')).toHaveLength(2);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('should render table with correct number of rows', () => {
|
|
344
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
345
|
+
admissionRequest: mockAdmissionRequestData,
|
|
346
|
+
isLoading: false,
|
|
347
|
+
error: undefined,
|
|
348
|
+
mutate: mockMutate,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
352
|
+
|
|
353
|
+
const tableRows = screen.getAllByRole('row');
|
|
354
|
+
// 1 header row + 2 data rows
|
|
355
|
+
expect(tableRows).toHaveLength(3);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test('should handle missing disposition location gracefully', () => {
|
|
359
|
+
const dataWithMissingLocation = [
|
|
360
|
+
{
|
|
361
|
+
...mockAdmissionRequestData[0],
|
|
362
|
+
dispositionLocation: undefined,
|
|
363
|
+
},
|
|
364
|
+
];
|
|
365
|
+
|
|
366
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
367
|
+
admissionRequest: dataWithMissingLocation,
|
|
368
|
+
isLoading: false,
|
|
369
|
+
error: undefined,
|
|
370
|
+
mutate: mockMutate,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
374
|
+
|
|
375
|
+
// Should still render table
|
|
376
|
+
expect(screen.getByText('Request Datetime')).toBeInTheDocument();
|
|
377
|
+
expect(screen.getByText('Admission Location')).toBeInTheDocument();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('should transform admission request data correctly', () => {
|
|
381
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
382
|
+
admissionRequest: mockAdmissionRequestData,
|
|
383
|
+
isLoading: false,
|
|
384
|
+
error: undefined,
|
|
385
|
+
mutate: mockMutate,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
389
|
+
|
|
390
|
+
// Verify that all expected data is displayed
|
|
391
|
+
const expectedLocations = ['Ward A', 'Ward B'];
|
|
392
|
+
const expectedDispositions = ['Admit to hospital', 'Admit to ICU'];
|
|
393
|
+
|
|
394
|
+
expectedLocations.forEach((location) => {
|
|
395
|
+
expect(screen.getByText(location)).toBeInTheDocument();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
expectedDispositions.forEach((disposition) => {
|
|
399
|
+
expect(screen.getByText(disposition)).toBeInTheDocument();
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test('should call useAdmissionRequest with correct patient UUID', () => {
|
|
404
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
405
|
+
admissionRequest: [],
|
|
406
|
+
isLoading: false,
|
|
407
|
+
error: undefined,
|
|
408
|
+
mutate: mockMutate,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
412
|
+
|
|
413
|
+
expect(mockUseAdmissionRequest).toHaveBeenCalledWith(patientUuid);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test('should render table headers in correct order', () => {
|
|
417
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
418
|
+
admissionRequest: mockAdmissionRequestData,
|
|
419
|
+
isLoading: false,
|
|
420
|
+
error: undefined,
|
|
421
|
+
mutate: mockMutate,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
425
|
+
|
|
426
|
+
const headers = screen.getAllByRole('columnheader');
|
|
427
|
+
expect(headers[0]).toHaveTextContent('Request Datetime');
|
|
428
|
+
expect(headers[1]).toHaveTextContent('Admission Location');
|
|
429
|
+
expect(headers[2]).toHaveTextContent('Admission Type');
|
|
430
|
+
expect(headers[3]).toHaveTextContent('Disposition');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test('should use tablet size for non-desktop layouts', () => {
|
|
434
|
+
mockIsDesktop.mockReturnValue(false);
|
|
435
|
+
mockUseLayoutType.mockReturnValue('tablet' as any);
|
|
436
|
+
|
|
437
|
+
mockUseAdmissionRequest.mockReturnValue({
|
|
438
|
+
admissionRequest: mockAdmissionRequestData,
|
|
439
|
+
isLoading: false,
|
|
440
|
+
error: undefined,
|
|
441
|
+
mutate: mockMutate,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
render(<AdmissionRequest patientUuid={patientUuid} />);
|
|
445
|
+
|
|
446
|
+
// Verify the table is still rendered correctly
|
|
447
|
+
const table = screen.getByRole('table');
|
|
448
|
+
expect(table).toBeInTheDocument();
|
|
449
|
+
});
|
|
450
|
+
});
|
|
@@ -1,78 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
3
|
-
import { ComboButton, MenuItem, DataTableSkeleton } from '@carbon/react';
|
|
4
|
-
import { CardHeader, EmptyState, useVisitOrOfflineVisit } from '@openmrs/esm-patient-common-lib';
|
|
5
|
-
import { useConfig, useVisit, evaluateAsBoolean, launchWorkspace } from '@openmrs/esm-framework';
|
|
6
|
-
import dayjs from 'dayjs';
|
|
7
|
-
import { BedManagementConfig } from '../config-schema';
|
|
8
|
-
import { usePatientEncounters } from './in-patient.resource';
|
|
9
|
-
import InPatientTable from './in-patient-table/in-patient-table.component';
|
|
2
|
+
import AdmissionRequest from './admission-request.component';
|
|
10
3
|
|
|
11
4
|
type InPatientProps = {
|
|
12
5
|
patientUuid: string;
|
|
13
6
|
patient: fhir.Patient;
|
|
14
7
|
};
|
|
15
8
|
|
|
16
|
-
const InPatient: React.FC<InPatientProps> = ({ patientUuid
|
|
17
|
-
|
|
18
|
-
const { inpatientVisitUuid } = useConfig<BedManagementConfig>();
|
|
19
|
-
const { currentVisit } = useVisitOrOfflineVisit(patientUuid);
|
|
20
|
-
const { inPatientForms } = useConfig<BedManagementConfig>();
|
|
21
|
-
const { encounters, isLoading, mutate } = usePatientEncounters(patientUuid);
|
|
22
|
-
|
|
23
|
-
const hasInPatientVisit = currentVisit?.visitType.uuid === inpatientVisitUuid;
|
|
24
|
-
const filteredForms = inPatientForms.filter((form) => {
|
|
25
|
-
if (!form.hideExpression) {
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
const age = dayjs().diff(dayjs(patient.birthDate), 'year');
|
|
29
|
-
const ageInDays = dayjs().diff(dayjs(patient.birthDate), 'day');
|
|
30
|
-
const ageInMonths = dayjs().diff(dayjs(patient.birthDate), 'month');
|
|
31
|
-
const gender = patient.gender;
|
|
32
|
-
const hide = form.hideExpression
|
|
33
|
-
? evaluateAsBoolean(form.hideExpression, { age, gender, ageInDays, ageInMonths })
|
|
34
|
-
: false;
|
|
35
|
-
return hide;
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const handleLaunchForm = (form: { label: string; uuid: string }) => {
|
|
39
|
-
launchWorkspace('patient-form-entry-workspace', {
|
|
40
|
-
workspaceTitle: form.label,
|
|
41
|
-
mutateForm: () => mutate(),
|
|
42
|
-
formInfo: {
|
|
43
|
-
encounterUuid: '',
|
|
44
|
-
formUuid: form.uuid,
|
|
45
|
-
additionalProps: {},
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
if (!hasInPatientVisit) {
|
|
51
|
-
return (
|
|
52
|
-
<EmptyState
|
|
53
|
-
displayText={t('inPatientVisitMessage', 'in-patient encounter found for current {{visitType}} visit', {
|
|
54
|
-
visitType: currentVisit?.visitType.display,
|
|
55
|
-
})}
|
|
56
|
-
headerTitle={t('inPatientView', 'In Patient View')}
|
|
57
|
-
/>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<div>
|
|
63
|
-
<CardHeader title={t('inPatient', 'In Patient')}>
|
|
64
|
-
<ComboButton size="sm" label={t('inPatientForms', 'In-Patient Forms')}>
|
|
65
|
-
{filteredForms.map((form) => (
|
|
66
|
-
<MenuItem key={form.uuid} onClick={() => handleLaunchForm(form)} label={form.label} />
|
|
67
|
-
))}
|
|
68
|
-
</ComboButton>
|
|
69
|
-
</CardHeader>
|
|
70
|
-
<div>
|
|
71
|
-
{isLoading && <DataTableSkeleton aria-label="encounters" />}
|
|
72
|
-
{!isLoading && <InPatientTable tableRows={encounters} currentVisit={currentVisit} />}
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
);
|
|
9
|
+
const InPatient: React.FC<InPatientProps> = ({ patientUuid }) => {
|
|
10
|
+
return <AdmissionRequest patientUuid={patientUuid} />;
|
|
76
11
|
};
|
|
77
12
|
|
|
78
13
|
export default InPatient;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { openmrsFetch, useConfig } from '@openmrs/esm-framework';
|
|
1
|
+
import { type Encounter, openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
|
|
2
2
|
import useSWR from 'swr';
|
|
3
|
-
import { Encounter } from './encounter-observations/visit.resource';
|
|
4
3
|
import { BedManagementConfig } from '../config-schema';
|
|
4
|
+
import { AdmissionRequest } from '../types';
|
|
5
5
|
|
|
6
6
|
export const usePatientEncounters = (patientUuid: string) => {
|
|
7
7
|
const { inPatientForms } = useConfig<BedManagementConfig>();
|
|
@@ -24,3 +24,33 @@ export const usePatientEncounters = (patientUuid: string) => {
|
|
|
24
24
|
mutate,
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
|
+
|
|
28
|
+
const defaultRep =
|
|
29
|
+
'custom:(' +
|
|
30
|
+
'dispositionLocation,' +
|
|
31
|
+
'dispositionType,' +
|
|
32
|
+
'disposition,' +
|
|
33
|
+
'dispositionEncounter:full,' +
|
|
34
|
+
'patient:(uuid,identifiers,voided,' +
|
|
35
|
+
'person:(uuid,display,gender,age,birthdate,birthtime,preferredName,preferredAddress,dead,deathDate)),' +
|
|
36
|
+
'dispositionObsGroup,' +
|
|
37
|
+
'visit)';
|
|
38
|
+
|
|
39
|
+
export const useAdmissionRequest = (patientUuid: string) => {
|
|
40
|
+
const patientUuids = [patientUuid];
|
|
41
|
+
const searchParams = new URLSearchParams();
|
|
42
|
+
searchParams.set('dispositionType', 'ADMIT');
|
|
43
|
+
searchParams.set('patients', patientUuids.join(','));
|
|
44
|
+
searchParams.set('v', defaultRep);
|
|
45
|
+
|
|
46
|
+
const url = `${restBaseUrl}/emrapi/inpatient/request?${searchParams.toString()}`;
|
|
47
|
+
|
|
48
|
+
const { data, isLoading, error, mutate } = useSWR<{ data: { results: Array<AdmissionRequest> } }>(url, openmrsFetch);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
admissionRequest: data?.data?.results ?? [],
|
|
52
|
+
isLoading: isLoading,
|
|
53
|
+
error: error,
|
|
54
|
+
mutate: mutate,
|
|
55
|
+
};
|
|
56
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { configSchema } from './config-schema';
|
|
|
47
47
|
import SpecialClinicDashboard from './special-clinics/special-clinic.component';
|
|
48
48
|
import { createDashboardLink as createDashboardLink2 } from './dashboard/createDashboardLink';
|
|
49
49
|
import { createLeftPanelLink } from './left-panel-link.component';
|
|
50
|
+
import PatientCaseForm from './case-management/encounters/patient-case.workspace';
|
|
50
51
|
|
|
51
52
|
const moduleName = '@kenyaemr/esm-patient-clinical-view-app';
|
|
52
53
|
|
|
@@ -104,6 +105,7 @@ export const partograph = getSyncLifecycle(Partography, options);
|
|
|
104
105
|
export const caseManagementDashboardLink = getSyncLifecycle(createLeftPanelLink(caseManagementDashboardMeta), options);
|
|
105
106
|
export const wrapComponent = getSyncLifecycle(WrapComponent, options);
|
|
106
107
|
export const caseManagementForm = getSyncLifecycle(CaseManagementForm, options);
|
|
108
|
+
export const addPatientCaseForm = getSyncLifecycle(PatientCaseForm, options);
|
|
107
109
|
export const caseEncounterDashboardLink = getSyncLifecycle(
|
|
108
110
|
createDashboardLink({ ...caseEncounterDashboardMeta, icon: 'omrs-icon-add' }),
|
|
109
111
|
options,
|
package/src/routes.json
CHANGED
|
@@ -239,6 +239,12 @@
|
|
|
239
239
|
"title": "Case Management Form",
|
|
240
240
|
"type": "form"
|
|
241
241
|
},
|
|
242
|
+
{
|
|
243
|
+
"name": "add-patient-case-form",
|
|
244
|
+
"component": "addPatientCaseForm",
|
|
245
|
+
"title": "Add patient case Form",
|
|
246
|
+
"type": "form"
|
|
247
|
+
},
|
|
242
248
|
{
|
|
243
249
|
"name": "family-relationship-form",
|
|
244
250
|
"component": "familyRelationshipForm",
|
package/src/types/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { OpenmrsResource } from '@openmrs/esm-framework';
|
|
1
|
+
import { AuditInfo, EncounterType, type Obs, type OpenmrsResource } from '@openmrs/esm-framework';
|
|
2
|
+
import { type Form } from '@openmrs/esm-patient-common-lib';
|
|
2
3
|
export interface OpenmrsEncounter extends OpenmrsResource {
|
|
3
4
|
encounterDatetime: string;
|
|
4
5
|
encounterType: {
|
|
@@ -262,3 +263,29 @@ export interface ContactAttributeData {
|
|
|
262
263
|
livingWithClient?: string;
|
|
263
264
|
ipvOutCome?: string;
|
|
264
265
|
}
|
|
266
|
+
|
|
267
|
+
interface DispositionEncounter extends OpenmrsResource {
|
|
268
|
+
encounterDatetime: string;
|
|
269
|
+
patient: OpenmrsResource;
|
|
270
|
+
location: OpenmrsResource;
|
|
271
|
+
form: Form;
|
|
272
|
+
encounterType: EncounterType;
|
|
273
|
+
obs: Obs[];
|
|
274
|
+
orders: any[];
|
|
275
|
+
voided: boolean;
|
|
276
|
+
auditInfo: AuditInfo;
|
|
277
|
+
visit: Visit;
|
|
278
|
+
encounterProviders: any[];
|
|
279
|
+
diagnoses: any[];
|
|
280
|
+
resourceVersion: string;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface AdmissionRequest {
|
|
284
|
+
dispositionLocation: OpenmrsResource;
|
|
285
|
+
dispositionType: string;
|
|
286
|
+
disposition: Concept;
|
|
287
|
+
dispositionEncounter: DispositionEncounter;
|
|
288
|
+
patient: Patient;
|
|
289
|
+
dispositionObsGroup: Obs;
|
|
290
|
+
visit: Visit;
|
|
291
|
+
}
|
package/translations/am.json
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"addPNSContact": "የPNS እውቂያ ጨምር",
|
|
11
11
|
"addRelationship": "ግንኙነት ጨምር",
|
|
12
12
|
"admissionDate": "የመግቢያ ቀን",
|
|
13
|
+
"admissionLocation": "መግቢያ ቦታ",
|
|
14
|
+
"admissionRequest": "የመግቢያ ጥያቄ",
|
|
15
|
+
"admissionRequests": "የመግቢያ ጥያቄዎች",
|
|
16
|
+
"admissionType": "የመግቢያ ዓይነት",
|
|
13
17
|
"admissionWard": "የመግቢያ Ward",
|
|
14
18
|
"admittingDoctor": "የሚያስገባ ሀኪም",
|
|
15
19
|
"age": "ዕድሜ",
|
|
@@ -59,6 +63,7 @@
|
|
|
59
63
|
"dischargeDate": "የመልቀቂያ ቀን",
|
|
60
64
|
"dischargingDoctor": "የሚለቅ ሀኪም",
|
|
61
65
|
"discontinuationCases": "የማቋረጥ ጉዳዮች",
|
|
66
|
+
"disposition": "Disposition",
|
|
62
67
|
"edit": "አስተካክል",
|
|
63
68
|
"editContactList": "የእውቂያ ዝርዝር አስተካክል",
|
|
64
69
|
"editEncounter": "አስተካክል",
|
|
@@ -103,11 +108,7 @@
|
|
|
103
108
|
"htsResult": "የመጨረሻ ውጤት",
|
|
104
109
|
"htsScreening": "HTS ምርመራ",
|
|
105
110
|
"htsTestType": "የምርመራ አይነት",
|
|
106
|
-
"inPatient": "የውስጥ ታካሚ",
|
|
107
|
-
"inPatientForms": "የውስጥ ታካሚ ቅጾች",
|
|
108
111
|
"inPatientSummary": "የውስጥ ታካሚ ማጠቃለያ",
|
|
109
|
-
"inPatientView": "የውስጥ ታካሚ እይታ",
|
|
110
|
-
"inPatientVisitMessage": "ለአሁኑ {{visitType}} ጉብኝት የውስጥ ታካሚ ግኝት ተገኝቷል",
|
|
111
112
|
"ipvOutcome": "IPV ውጤት",
|
|
112
113
|
"ipvOutCome": "IPV ውጤት",
|
|
113
114
|
"ipvQuestions": "IPV ጥያቄዎች",
|
|
@@ -167,6 +168,7 @@
|
|
|
167
168
|
"remarks": "ማስታወሻዎች",
|
|
168
169
|
"reportingMonth": "ወር",
|
|
169
170
|
"reportingYear": "ዓመት",
|
|
171
|
+
"requestDatetime": "Request Datetime",
|
|
170
172
|
"save": "አስቀምጥ",
|
|
171
173
|
"savedRelationship": "ግንኙነት በተሳካ ሁኔታ ተቋርጧል",
|
|
172
174
|
"selectClinic": "ክሊኒክ ምረጥ",
|