@openmrs/esm-patient-chart-app 11.3.1-pre.9294 → 11.3.1-pre.9304
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 +9 -9
- package/dist/1119.js +1 -1
- package/dist/1197.js +1 -1
- package/dist/2146.js +1 -1
- package/dist/2690.js +1 -1
- package/dist/276.js +1 -1
- package/dist/276.js.map +1 -1
- package/dist/2761.js +1 -1
- package/dist/2761.js.map +1 -1
- package/dist/2859.js +1 -1
- package/dist/2859.js.map +1 -1
- package/dist/3099.js +1 -1
- package/dist/3119.js +1 -1
- package/dist/3119.js.map +1 -1
- 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/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4727.js +1 -1
- package/dist/4727.js.map +1 -1
- package/dist/4944.js +1 -1
- package/dist/5048.js +1 -1
- package/dist/5048.js.map +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/6022.js +1 -1
- package/dist/6468.js +1 -1
- package/dist/6568.js +1 -0
- package/dist/6568.js.map +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/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8260.js +1 -0
- package/dist/8260.js.map +1 -0
- package/dist/8349.js +1 -1
- package/dist/8454.js +1 -1
- package/dist/8454.js.map +1 -1
- package/dist/8618.js +1 -1
- package/dist/890.js +1 -1
- package/dist/9214.js +1 -1
- package/dist/9329.js +1 -0
- package/dist/9329.js.map +1 -0
- 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-patient-chart-app.js +1 -1
- package/dist/openmrs-esm-patient-chart-app.js.buildmanifest.json +214 -214
- package/dist/openmrs-esm-patient-chart-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -2
- package/src/actions-buttons/delete-visit.component.tsx +8 -3
- package/src/actions-buttons/stop-visit.component.tsx +1 -1
- package/src/clinical-views/encounter-list/{encounter-list-tabs.component.tsx → encounter-list-tabs.extension.tsx} +10 -6
- package/src/index.ts +4 -4
- package/src/mark-patient-deceased/mark-patient-deceased-form.test.tsx +2 -0
- package/src/patient-banner-tags/{visit-attribute-tags.component.tsx → visit-attribute-tags.extension.tsx} +6 -4
- package/src/patient-chart/patient-chart.component.tsx +39 -65
- package/src/patient-chart/patient-chart.resources.ts +108 -0
- package/src/visit/hooks/useDeleteVisit.test.tsx +39 -42
- package/src/visit/hooks/useDeleteVisit.tsx +33 -17
- package/src/visit/visit-form/visit-form.test.tsx +3 -9
- package/src/visit/visit-form/visit-form.workspace.tsx +17 -7
- package/src/visit/visit-prompt/delete-visit-dialog.component.tsx +10 -4
- package/src/visit/visit-prompt/delete-visit-dialog.test.tsx +20 -2
- package/src/visit/visit-prompt/end-visit-dialog.component.tsx +7 -1
- package/src/visit/visit-prompt/end-visit-dialog.test.tsx +19 -0
- package/src/visit/visits-widget/active-visit-buttons/active-visit-buttons.tsx +7 -6
- package/src/visit/visits-widget/{current-visit-summary.component.tsx → current-visit-summary.extension.tsx} +13 -20
- package/src/visit/visits-widget/current-visit-summary.test.tsx +45 -25
- package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.component.tsx +4 -3
- package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.resource.ts +0 -1
- package/src/visit/visits-widget/visit-context/retrospective-data-date-time-picker/retrospective-date-time-picker.component.tsx +6 -8
- package/src/visit/visits-widget/visit-context/{visit-context-header.component.tsx → visit-context-header.extension.tsx} +17 -15
- package/src/visit/visits-widget/visit-context/visit-context-header.test.tsx +35 -29
- package/src/visit/visits-widget/visit-context/visit-context-switcher.modal.tsx +13 -11
- package/src/visit/visits-widget/visit-context/visit-context-switcher.test.tsx +50 -30
- package/src/visit/visits-widget/visit.resource.tsx +1 -1
- package/translations/am.json +1 -3
- package/translations/ar.json +1 -3
- package/translations/ar_SY.json +1 -3
- package/translations/bn.json +1 -3
- package/translations/de.json +1 -3
- package/translations/en.json +1 -3
- package/translations/en_US.json +1 -3
- package/translations/es.json +1 -3
- package/translations/es_MX.json +1 -3
- package/translations/fr.json +1 -3
- package/translations/he.json +1 -3
- package/translations/hi.json +1 -3
- package/translations/hi_IN.json +1 -3
- package/translations/id.json +1 -3
- package/translations/it.json +1 -3
- package/translations/ka.json +1 -3
- package/translations/km.json +1 -3
- package/translations/ku.json +1 -3
- package/translations/ky.json +1 -3
- package/translations/lg.json +1 -3
- package/translations/ne.json +1 -3
- package/translations/pl.json +1 -3
- package/translations/pt.json +1 -3
- package/translations/pt_BR.json +1 -3
- package/translations/qu.json +1 -3
- package/translations/ro_RO.json +1 -3
- package/translations/ru_RU.json +1 -3
- package/translations/si.json +1 -3
- package/translations/sw.json +1 -3
- package/translations/sw_KE.json +1 -3
- package/translations/tr.json +1 -3
- package/translations/tr_TR.json +1 -3
- package/translations/uk.json +1 -3
- package/translations/uz.json +1 -3
- package/translations/uz@Latn.json +1 -3
- package/translations/uz_UZ.json +1 -3
- package/translations/vi.json +1 -3
- package/translations/zh.json +1 -3
- package/translations/zh_CN.json +1 -3
- package/dist/2442.js +0 -1
- package/dist/2442.js.map +0 -1
- package/dist/3042.js +0 -1
- package/dist/3042.js.map +0 -1
- package/dist/4713.js +0 -1
- package/dist/4713.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useSWRConfig } from 'swr';
|
|
2
2
|
import { renderHook, act } from '@testing-library/react';
|
|
3
|
-
import { showSnackbar,
|
|
4
|
-
import { invalidateVisitAndEncounterData } from '@openmrs/esm-patient-common-lib';
|
|
3
|
+
import { showSnackbar, type Visit } from '@openmrs/esm-framework';
|
|
4
|
+
import { invalidateVisitAndEncounterData, usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
5
5
|
import { useDeleteVisit } from './useDeleteVisit';
|
|
6
6
|
import { deleteVisit, restoreVisit } from '../visits-widget/visit.resource';
|
|
7
7
|
|
|
@@ -20,17 +20,19 @@ jest.mock('../visits-widget/visit.resource', () => {
|
|
|
20
20
|
|
|
21
21
|
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
22
22
|
invalidateVisitAndEncounterData: jest.fn(),
|
|
23
|
+
usePatientChartStore: jest.fn(),
|
|
23
24
|
}));
|
|
24
25
|
|
|
25
26
|
const mockDeleteVisit = jest.mocked(deleteVisit);
|
|
26
27
|
const mockRestoreVisit = jest.mocked(restoreVisit);
|
|
27
28
|
const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
28
29
|
const mockUseSWRConfig = jest.mocked(useSWRConfig);
|
|
29
|
-
const
|
|
30
|
+
const mockUsePatientChartStore = jest.mocked(usePatientChartStore);
|
|
30
31
|
const mockInvalidateVisitAndEncounterData = jest.mocked(invalidateVisitAndEncounterData);
|
|
31
32
|
|
|
32
|
-
const
|
|
33
|
+
const mockMutateActiveVisit = jest.fn();
|
|
33
34
|
const mockGlobalMutate = jest.fn();
|
|
35
|
+
const mockSetVisitContext = jest.fn();
|
|
34
36
|
|
|
35
37
|
const mockVisitType = {
|
|
36
38
|
uuid: 'visit-type-123',
|
|
@@ -54,16 +56,13 @@ const mockVisit = {
|
|
|
54
56
|
|
|
55
57
|
describe('useDeleteVisit', () => {
|
|
56
58
|
beforeEach(() => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
error: null,
|
|
65
|
-
isLoading: false,
|
|
66
|
-
isValidating: false,
|
|
59
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
60
|
+
patientUuid: 'patient-123',
|
|
61
|
+
patient: null,
|
|
62
|
+
visitContext: null,
|
|
63
|
+
mutateVisitContext: jest.fn(),
|
|
64
|
+
setPatient: jest.fn(),
|
|
65
|
+
setVisitContext: mockSetVisitContext,
|
|
67
66
|
});
|
|
68
67
|
|
|
69
68
|
mockUseSWRConfig.mockReturnValue({
|
|
@@ -76,14 +75,14 @@ describe('useDeleteVisit', () => {
|
|
|
76
75
|
mockDeleteVisit.mockResolvedValue({ data: {} } as any);
|
|
77
76
|
const onVisitDelete = jest.fn();
|
|
78
77
|
|
|
79
|
-
const { result } = renderHook(() => useDeleteVisit(mockVisit, onVisitDelete));
|
|
78
|
+
const { result } = renderHook(() => useDeleteVisit(mockVisit, mockMutateActiveVisit, onVisitDelete));
|
|
80
79
|
|
|
81
80
|
await act(async () => {
|
|
82
81
|
result.current.initiateDeletingVisit();
|
|
83
82
|
});
|
|
84
83
|
|
|
85
84
|
expect(mockDeleteVisit).toHaveBeenCalledWith('visit-123');
|
|
86
|
-
expect(
|
|
85
|
+
expect(mockMutateActiveVisit).toHaveBeenCalledTimes(1);
|
|
87
86
|
expect(mockInvalidateVisitAndEncounterData).toHaveBeenCalledWith(mockGlobalMutate, 'patient-123');
|
|
88
87
|
|
|
89
88
|
expect(mockShowSnackbar).toHaveBeenCalledWith(
|
|
@@ -103,13 +102,13 @@ describe('useDeleteVisit', () => {
|
|
|
103
102
|
mockDeleteVisit.mockRejectedValue(new Error('Delete failed'));
|
|
104
103
|
const onVisitDelete = jest.fn();
|
|
105
104
|
|
|
106
|
-
const { result } = renderHook(() => useDeleteVisit(mockVisit, onVisitDelete));
|
|
105
|
+
const { result } = renderHook(() => useDeleteVisit(mockVisit, mockMutateActiveVisit, onVisitDelete));
|
|
107
106
|
|
|
108
107
|
await act(async () => {
|
|
109
108
|
result.current.initiateDeletingVisit();
|
|
110
109
|
});
|
|
111
110
|
|
|
112
|
-
expect(
|
|
111
|
+
expect(mockMutateActiveVisit).toHaveBeenCalledTimes(1);
|
|
113
112
|
expect(mockInvalidateVisitAndEncounterData).toHaveBeenCalledWith(mockGlobalMutate, 'patient-123');
|
|
114
113
|
|
|
115
114
|
expect(mockShowSnackbar).toHaveBeenCalledWith({
|
|
@@ -125,7 +124,7 @@ describe('useDeleteVisit', () => {
|
|
|
125
124
|
mockRestoreVisit.mockResolvedValue({ data: {} } as any);
|
|
126
125
|
const onVisitRestore = jest.fn();
|
|
127
126
|
|
|
128
|
-
const { result } = renderHook(() => useDeleteVisit(mockVisit, undefined, onVisitRestore));
|
|
127
|
+
const { result } = renderHook(() => useDeleteVisit(mockVisit, mockMutateActiveVisit, undefined, onVisitRestore));
|
|
129
128
|
|
|
130
129
|
// First delete to get the restore function
|
|
131
130
|
mockDeleteVisit.mockResolvedValue({ data: {} } as any);
|
|
@@ -138,14 +137,13 @@ describe('useDeleteVisit', () => {
|
|
|
138
137
|
|
|
139
138
|
// Clear mocks to test restore independently
|
|
140
139
|
jest.clearAllMocks();
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
isValidating: false,
|
|
140
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
141
|
+
patientUuid: 'patient-123',
|
|
142
|
+
patient: null,
|
|
143
|
+
visitContext: null,
|
|
144
|
+
mutateVisitContext: jest.fn(),
|
|
145
|
+
setPatient: jest.fn(),
|
|
146
|
+
setVisitContext: mockSetVisitContext,
|
|
149
147
|
});
|
|
150
148
|
mockUseSWRConfig.mockReturnValue({
|
|
151
149
|
mutate: mockGlobalMutate,
|
|
@@ -158,7 +156,7 @@ describe('useDeleteVisit', () => {
|
|
|
158
156
|
});
|
|
159
157
|
|
|
160
158
|
expect(mockRestoreVisit).toHaveBeenCalledWith('visit-123');
|
|
161
|
-
expect(
|
|
159
|
+
expect(mockMutateActiveVisit).toHaveBeenCalledTimes(1);
|
|
162
160
|
expect(mockInvalidateVisitAndEncounterData).toHaveBeenCalledWith(mockGlobalMutate, 'patient-123');
|
|
163
161
|
|
|
164
162
|
expect(mockShowSnackbar).toHaveBeenCalledWith({
|
|
@@ -174,7 +172,7 @@ describe('useDeleteVisit', () => {
|
|
|
174
172
|
mockRestoreVisit.mockRejectedValue(new Error('Restore failed'));
|
|
175
173
|
const onVisitRestore = jest.fn();
|
|
176
174
|
|
|
177
|
-
const { result } = renderHook(() => useDeleteVisit(mockVisit, undefined, onVisitRestore));
|
|
175
|
+
const { result } = renderHook(() => useDeleteVisit(mockVisit, mockMutateActiveVisit, undefined, onVisitRestore));
|
|
178
176
|
|
|
179
177
|
// First delete to get the restore function
|
|
180
178
|
mockDeleteVisit.mockResolvedValue({ data: {} } as any);
|
|
@@ -186,14 +184,13 @@ describe('useDeleteVisit', () => {
|
|
|
186
184
|
|
|
187
185
|
// Clear mocks to test restore independently
|
|
188
186
|
jest.clearAllMocks();
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
isValidating: false,
|
|
187
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
188
|
+
patientUuid: 'patient-123',
|
|
189
|
+
patient: null,
|
|
190
|
+
visitContext: null,
|
|
191
|
+
mutateVisitContext: jest.fn(),
|
|
192
|
+
setPatient: jest.fn(),
|
|
193
|
+
setVisitContext: mockSetVisitContext,
|
|
197
194
|
});
|
|
198
195
|
mockUseSWRConfig.mockReturnValue({
|
|
199
196
|
mutate: mockGlobalMutate,
|
|
@@ -205,7 +202,7 @@ describe('useDeleteVisit', () => {
|
|
|
205
202
|
restoreFunction();
|
|
206
203
|
});
|
|
207
204
|
|
|
208
|
-
expect(
|
|
205
|
+
expect(mockMutateActiveVisit).toHaveBeenCalledTimes(1);
|
|
209
206
|
expect(mockInvalidateVisitAndEncounterData).toHaveBeenCalledWith(mockGlobalMutate, 'patient-123');
|
|
210
207
|
|
|
211
208
|
expect(mockShowSnackbar).toHaveBeenCalledWith({
|
|
@@ -220,7 +217,7 @@ describe('useDeleteVisit', () => {
|
|
|
220
217
|
it('should manage loading state correctly', async () => {
|
|
221
218
|
mockDeleteVisit.mockResolvedValue({ data: {} } as any);
|
|
222
219
|
|
|
223
|
-
const { result } = renderHook(() => useDeleteVisit(mockVisit));
|
|
220
|
+
const { result } = renderHook(() => useDeleteVisit(mockVisit, mockMutateActiveVisit, () => {}));
|
|
224
221
|
|
|
225
222
|
// Initially not deleting
|
|
226
223
|
expect(result.current.isDeletingVisit).toBe(false);
|
|
@@ -238,7 +235,7 @@ describe('useDeleteVisit', () => {
|
|
|
238
235
|
const visitWithoutPatient = { ...mockVisit, patient: null };
|
|
239
236
|
|
|
240
237
|
expect(() => {
|
|
241
|
-
renderHook(() => useDeleteVisit(visitWithoutPatient));
|
|
238
|
+
renderHook(() => useDeleteVisit(visitWithoutPatient, mockMutateActiveVisit, () => {}));
|
|
242
239
|
}).not.toThrow();
|
|
243
240
|
});
|
|
244
241
|
|
|
@@ -246,7 +243,7 @@ describe('useDeleteVisit', () => {
|
|
|
246
243
|
const visitWithEmptyPatient = { ...mockVisit, patient: { uuid: '' } };
|
|
247
244
|
|
|
248
245
|
expect(() => {
|
|
249
|
-
renderHook(() => useDeleteVisit(visitWithEmptyPatient));
|
|
246
|
+
renderHook(() => useDeleteVisit(visitWithEmptyPatient, mockMutateActiveVisit, () => {}));
|
|
250
247
|
}).not.toThrow();
|
|
251
248
|
});
|
|
252
249
|
|
|
@@ -257,7 +254,7 @@ describe('useDeleteVisit', () => {
|
|
|
257
254
|
};
|
|
258
255
|
|
|
259
256
|
mockDeleteVisit.mockResolvedValue({ data: {} } as any);
|
|
260
|
-
const { result } = renderHook(() => useDeleteVisit(visitWithoutDisplay));
|
|
257
|
+
const { result } = renderHook(() => useDeleteVisit(visitWithoutDisplay, mockMutateActiveVisit, () => {}));
|
|
261
258
|
|
|
262
259
|
await act(async () => {
|
|
263
260
|
result.current.initiateDeletingVisit();
|
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { useSWRConfig } from 'swr';
|
|
4
|
-
import { type Visit, showSnackbar
|
|
5
|
-
import {
|
|
4
|
+
import { type Visit, showSnackbar } from '@openmrs/esm-framework';
|
|
5
|
+
import {
|
|
6
|
+
invalidateVisitAndEncounterData,
|
|
7
|
+
invalidateVisitByUuid,
|
|
8
|
+
usePatientChartStore,
|
|
9
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
6
10
|
import { deleteVisit, restoreVisit } from '../visits-widget/visit.resource';
|
|
7
11
|
|
|
8
|
-
export function useDeleteVisit(
|
|
12
|
+
export function useDeleteVisit(
|
|
13
|
+
activeVisit: Visit,
|
|
14
|
+
mutateActiveVisit: () => void,
|
|
15
|
+
onVisitDelete = () => {},
|
|
16
|
+
onVisitRestore = () => {},
|
|
17
|
+
) {
|
|
9
18
|
const { t } = useTranslation();
|
|
10
19
|
const { mutate: globalMutate } = useSWRConfig();
|
|
11
|
-
const { mutate: mutateCurrentVisit } = useVisit(visit?.patient?.uuid || '');
|
|
12
20
|
const [isDeletingVisit, setIsDeletingVisit] = useState(false);
|
|
13
|
-
const patientUuid =
|
|
21
|
+
const patientUuid = activeVisit?.patient?.uuid || '';
|
|
22
|
+
const { visitContext, setVisitContext } = usePatientChartStore(patientUuid);
|
|
14
23
|
|
|
15
24
|
const restoreDeletedVisit = () => {
|
|
16
|
-
restoreVisit(
|
|
17
|
-
.then(() => {
|
|
25
|
+
restoreVisit(activeVisit?.uuid)
|
|
26
|
+
.then(({ data: updatedVisit }) => {
|
|
18
27
|
// Update current visit data for critical components (useVisit hook)
|
|
19
|
-
|
|
28
|
+
mutateActiveVisit();
|
|
29
|
+
if (!updatedVisit.stopDatetime) {
|
|
30
|
+
const mutateSavedOrUpdatedVisit = () => invalidateVisitByUuid(globalMutate, updatedVisit.uuid);
|
|
31
|
+
setVisitContext(updatedVisit, mutateSavedOrUpdatedVisit);
|
|
32
|
+
}
|
|
20
33
|
|
|
21
34
|
// Use targeted SWR invalidation instead of global mutateVisit
|
|
22
35
|
invalidateVisitAndEncounterData(globalMutate, patientUuid);
|
|
@@ -24,7 +37,7 @@ export function useDeleteVisit(visit: Visit, onVisitDelete = () => {}, onVisitRe
|
|
|
24
37
|
showSnackbar({
|
|
25
38
|
title: t('visitRestored', 'Visit restored'),
|
|
26
39
|
subtitle: t('visitRestoredSuccessfully', '{{visit}} restored successfully', {
|
|
27
|
-
visit:
|
|
40
|
+
visit: activeVisit?.visitType?.display ?? t('visit', 'Visit'),
|
|
28
41
|
}),
|
|
29
42
|
kind: 'success',
|
|
30
43
|
});
|
|
@@ -32,12 +45,12 @@ export function useDeleteVisit(visit: Visit, onVisitDelete = () => {}, onVisitRe
|
|
|
32
45
|
})
|
|
33
46
|
.catch(() => {
|
|
34
47
|
// On error, revalidate to get correct state
|
|
35
|
-
|
|
48
|
+
mutateActiveVisit();
|
|
36
49
|
invalidateVisitAndEncounterData(globalMutate, patientUuid);
|
|
37
50
|
showSnackbar({
|
|
38
51
|
title: t('visitNotRestored', "Visit couldn't be restored"),
|
|
39
52
|
subtitle: t('errorWhenRestoringVisit', 'Error occurred when restoring {{visit}}', {
|
|
40
|
-
visit:
|
|
53
|
+
visit: activeVisit?.visitType?.display ?? t('visit', 'Visit'),
|
|
41
54
|
}),
|
|
42
55
|
kind: 'error',
|
|
43
56
|
});
|
|
@@ -47,20 +60,23 @@ export function useDeleteVisit(visit: Visit, onVisitDelete = () => {}, onVisitRe
|
|
|
47
60
|
const initiateDeletingVisit = () => {
|
|
48
61
|
setIsDeletingVisit(true);
|
|
49
62
|
|
|
50
|
-
deleteVisit(
|
|
63
|
+
deleteVisit(activeVisit?.uuid)
|
|
51
64
|
.then(() => {
|
|
52
|
-
// Update
|
|
53
|
-
|
|
65
|
+
// Update active visit data
|
|
66
|
+
mutateActiveVisit();
|
|
67
|
+
if (activeVisit.uuid === visitContext?.uuid) {
|
|
68
|
+
setVisitContext(null, null);
|
|
69
|
+
}
|
|
54
70
|
|
|
55
71
|
// Use targeted SWR invalidation instead of global mutateVisit
|
|
56
72
|
invalidateVisitAndEncounterData(globalMutate, patientUuid);
|
|
57
73
|
|
|
58
74
|
showSnackbar({
|
|
59
75
|
title: t('visitDeleted', '{{visit}} deleted', {
|
|
60
|
-
visit:
|
|
76
|
+
visit: activeVisit?.visitType?.display ?? t('visit', 'Visit'),
|
|
61
77
|
}),
|
|
62
78
|
subtitle: t('visitDeletedSuccessfully', '{{visit}} deleted successfully', {
|
|
63
|
-
visit:
|
|
79
|
+
visit: activeVisit?.visitType?.display ?? t('visit', 'Visit'),
|
|
64
80
|
}),
|
|
65
81
|
kind: 'success',
|
|
66
82
|
actionButtonLabel: t('undo', 'Undo'),
|
|
@@ -70,7 +86,7 @@ export function useDeleteVisit(visit: Visit, onVisitDelete = () => {}, onVisitRe
|
|
|
70
86
|
})
|
|
71
87
|
.catch(() => {
|
|
72
88
|
// On error, revalidate to get correct state
|
|
73
|
-
|
|
89
|
+
mutateActiveVisit();
|
|
74
90
|
invalidateVisitAndEncounterData(globalMutate, patientUuid);
|
|
75
91
|
|
|
76
92
|
showSnackbar({
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
useConfig,
|
|
12
12
|
useEmrConfiguration,
|
|
13
13
|
useLocations,
|
|
14
|
-
useVisitContextStore,
|
|
15
14
|
useVisitTypes,
|
|
16
15
|
type Visit,
|
|
17
16
|
} from '@openmrs/esm-framework';
|
|
@@ -59,6 +58,7 @@ const visitAttributes = {
|
|
|
59
58
|
const mockCloseWorkspace = jest.fn();
|
|
60
59
|
const mockPromptBeforeClosing = jest.fn();
|
|
61
60
|
const mockSetTitle = jest.fn();
|
|
61
|
+
const mockMutateVisitContext = jest.fn();
|
|
62
62
|
|
|
63
63
|
const testProps = {
|
|
64
64
|
openedFrom: 'test',
|
|
@@ -68,6 +68,8 @@ const testProps = {
|
|
|
68
68
|
closeWorkspaceWithSavedChanges: mockCloseWorkspace,
|
|
69
69
|
promptBeforeClosing: mockPromptBeforeClosing,
|
|
70
70
|
setTitle: mockSetTitle,
|
|
71
|
+
visitContext: null,
|
|
72
|
+
mutateVisitContext: mockMutateVisitContext,
|
|
71
73
|
};
|
|
72
74
|
|
|
73
75
|
const mockSaveVisit = jest.mocked(saveVisit);
|
|
@@ -181,14 +183,6 @@ mockSaveVisit.mockResolvedValue({
|
|
|
181
183
|
},
|
|
182
184
|
} as unknown as FetchResponse<Visit>);
|
|
183
185
|
|
|
184
|
-
jest.mocked(useVisitContextStore).mockReturnValue({
|
|
185
|
-
manuallySetVisitUuid: null,
|
|
186
|
-
patientUuid: null,
|
|
187
|
-
setVisitContext: jest.fn(),
|
|
188
|
-
mutateVisitCallbacks: {},
|
|
189
|
-
mutateVisit: jest.fn(),
|
|
190
|
-
});
|
|
191
|
-
|
|
192
186
|
describe('Visit form', () => {
|
|
193
187
|
beforeEach(() => {
|
|
194
188
|
mockUseConfig.mockReturnValue({
|
|
@@ -36,8 +36,10 @@ import {
|
|
|
36
36
|
} from '@openmrs/esm-framework';
|
|
37
37
|
import {
|
|
38
38
|
createOfflineVisitForPatient,
|
|
39
|
+
invalidateVisitByUuid,
|
|
39
40
|
invalidateVisitAndEncounterData,
|
|
40
41
|
useActivePatientEnrollment,
|
|
42
|
+
usePatientChartStore,
|
|
41
43
|
type DefaultPatientWorkspaceProps,
|
|
42
44
|
} from '@openmrs/esm-patient-common-lib';
|
|
43
45
|
import { MemoizedRecommendedVisitType } from './recommended-visit-type.component';
|
|
@@ -120,9 +122,10 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
120
122
|
);
|
|
121
123
|
const visitHeaderSlotState = useMemo(() => ({ patientUuid }), [patientUuid]);
|
|
122
124
|
const { activePatientEnrollment, isLoading } = useActivePatientEnrollment(patientUuid);
|
|
123
|
-
const { mutate:
|
|
125
|
+
const { mutate: mutateActiveVisit } = useVisit(patientUuid);
|
|
124
126
|
const { mutate: globalMutate } = useSWRConfig();
|
|
125
127
|
const allVisitTypes = useConditionalVisitTypes();
|
|
128
|
+
const { setVisitContext } = usePatientChartStore(patientUuid);
|
|
126
129
|
|
|
127
130
|
const [errorFetchingResources, setErrorFetchingResources] = useState<{
|
|
128
131
|
blockSavingForm: boolean;
|
|
@@ -327,12 +330,15 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
327
330
|
// 1. Current visit data (for critical components like visit summary, action buttons)
|
|
328
331
|
// 2. Visit history table (for the paginated visit list)
|
|
329
332
|
|
|
330
|
-
// Update
|
|
331
|
-
|
|
333
|
+
// Update patient's visit data for critical components
|
|
334
|
+
const mutateSavedOrUpdatedVisit = () => invalidateVisitByUuid(globalMutate, visit.uuid);
|
|
335
|
+
mutateActiveVisit();
|
|
336
|
+
setVisitContext?.(visit, mutateSavedOrUpdatedVisit);
|
|
337
|
+
visitToEdit && mutateSavedOrUpdatedVisit();
|
|
332
338
|
|
|
333
339
|
// Use targeted SWR invalidation instead of global mutateVisit
|
|
334
340
|
// This will invalidate visit history and encounter tables for this patient
|
|
335
|
-
// (
|
|
341
|
+
// (if visitContext is updated, it should have been invalidated with mutateSavedOrUpdatedVisit)
|
|
336
342
|
invalidateVisitAndEncounterData(globalMutate, patientUuid);
|
|
337
343
|
|
|
338
344
|
// handleVisitAttributes already has code to show error snackbar when attribute fails to update
|
|
@@ -371,9 +377,12 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
371
377
|
config.offlineVisitTypeUuid,
|
|
372
378
|
payload.startDatetime,
|
|
373
379
|
).then(
|
|
374
|
-
() => {
|
|
380
|
+
(visit) => {
|
|
375
381
|
// Use same targeted approach for offline visits for consistency
|
|
376
|
-
|
|
382
|
+
const mutateSavedOrUpdatedVisit = () => invalidateVisitByUuid(globalMutate, visit.uuid);
|
|
383
|
+
mutateActiveVisit();
|
|
384
|
+
setVisitContext?.(visit, mutateSavedOrUpdatedVisit);
|
|
385
|
+
visitToEdit && mutateSavedOrUpdatedVisit();
|
|
377
386
|
|
|
378
387
|
// Also invalidate visit history and encounter tables
|
|
379
388
|
invalidateVisitAndEncounterData(globalMutate, patientUuid);
|
|
@@ -407,12 +416,13 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
407
416
|
globalMutate,
|
|
408
417
|
handleVisitAttributes,
|
|
409
418
|
isOnline,
|
|
410
|
-
|
|
419
|
+
setVisitContext,
|
|
411
420
|
patientUuid,
|
|
412
421
|
t,
|
|
413
422
|
visitFormCallbacks,
|
|
414
423
|
visitToEdit,
|
|
415
424
|
isValidVisitAttributesArray,
|
|
425
|
+
mutateActiveVisit,
|
|
416
426
|
],
|
|
417
427
|
);
|
|
418
428
|
|
|
@@ -8,12 +8,18 @@ import styles from './start-visit-dialog.scss';
|
|
|
8
8
|
interface DeleteVisitDialogProps {
|
|
9
9
|
closeModal: () => void;
|
|
10
10
|
patientUuid: string;
|
|
11
|
-
|
|
11
|
+
activeVisit: Visit;
|
|
12
|
+
mutateActiveVisit: () => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
const DeleteVisitDialog: React.FC<DeleteVisitDialogProps> = ({
|
|
15
|
+
const DeleteVisitDialog: React.FC<DeleteVisitDialogProps> = ({
|
|
16
|
+
closeModal,
|
|
17
|
+
mutateActiveVisit,
|
|
18
|
+
patientUuid,
|
|
19
|
+
activeVisit,
|
|
20
|
+
}) => {
|
|
15
21
|
const { t } = useTranslation();
|
|
16
|
-
const { isDeletingVisit, initiateDeletingVisit } = useDeleteVisit(
|
|
22
|
+
const { isDeletingVisit, initiateDeletingVisit } = useDeleteVisit(activeVisit, mutateActiveVisit, closeModal);
|
|
17
23
|
|
|
18
24
|
return (
|
|
19
25
|
<div>
|
|
@@ -24,7 +30,7 @@ const DeleteVisitDialog: React.FC<DeleteVisitDialogProps> = ({ closeModal, patie
|
|
|
24
30
|
<ModalBody>
|
|
25
31
|
<p className={styles.body}>
|
|
26
32
|
{t('confirmDeleteVisitText', 'Deleting this {{visit}} will delete its associated encounters.', {
|
|
27
|
-
visit:
|
|
33
|
+
visit: activeVisit?.visitType?.display ?? t('visit', 'Visit'),
|
|
28
34
|
})}
|
|
29
35
|
</p>
|
|
30
36
|
</ModalBody>
|
|
@@ -9,6 +9,7 @@ import DeleteVisitDialog from './delete-visit-dialog.component';
|
|
|
9
9
|
const mockCloseModal = jest.fn();
|
|
10
10
|
const mockOpenmrsFetch = jest.mocked(openmrsFetch);
|
|
11
11
|
const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
12
|
+
const mockActiveVisit = jest.fn();
|
|
12
13
|
|
|
13
14
|
describe('Delete visit', () => {
|
|
14
15
|
it('voids the visit and voids its associated encounters', async () => {
|
|
@@ -21,7 +22,14 @@ describe('Delete visit', () => {
|
|
|
21
22
|
|
|
22
23
|
mockOpenmrsFetch.mockResolvedValue(response as FetchResponse);
|
|
23
24
|
|
|
24
|
-
render(
|
|
25
|
+
render(
|
|
26
|
+
<DeleteVisitDialog
|
|
27
|
+
activeVisit={mockCurrentVisit}
|
|
28
|
+
mutateActiveVisit={mockActiveVisit}
|
|
29
|
+
closeModal={mockCloseModal}
|
|
30
|
+
patientUuid={mockPatient.id}
|
|
31
|
+
/>,
|
|
32
|
+
);
|
|
25
33
|
|
|
26
34
|
const cancelButton = screen.getByRole('button', { name: /^cancel$/i });
|
|
27
35
|
const deleteVisitButton = screen.getByRole('button', { name: /delete visit$/i });
|
|
@@ -46,6 +54,7 @@ describe('Delete visit', () => {
|
|
|
46
54
|
subtitle: 'Facility Visit deleted successfully',
|
|
47
55
|
}),
|
|
48
56
|
);
|
|
57
|
+
expect(mockActiveVisit).toHaveBeenCalledTimes(1);
|
|
49
58
|
});
|
|
50
59
|
|
|
51
60
|
it('displays an error notification if there was problem with deleting a visit', async () => {
|
|
@@ -53,7 +62,14 @@ describe('Delete visit', () => {
|
|
|
53
62
|
|
|
54
63
|
mockOpenmrsFetch.mockRejectedValueOnce({ message: 'Internal server error', status: 500 });
|
|
55
64
|
|
|
56
|
-
render(
|
|
65
|
+
render(
|
|
66
|
+
<DeleteVisitDialog
|
|
67
|
+
activeVisit={mockCurrentVisit}
|
|
68
|
+
mutateActiveVisit={mockActiveVisit}
|
|
69
|
+
closeModal={mockCloseModal}
|
|
70
|
+
patientUuid={mockPatient.id}
|
|
71
|
+
/>,
|
|
72
|
+
);
|
|
57
73
|
|
|
58
74
|
const cancelButton = screen.getByRole('button', { name: /^cancel$/i });
|
|
59
75
|
const deleteVisitButton = screen.getByRole('button', { name: /delete visit$/i });
|
|
@@ -74,5 +90,7 @@ describe('Delete visit', () => {
|
|
|
74
90
|
kind: 'error',
|
|
75
91
|
title: 'Error deleting visit',
|
|
76
92
|
});
|
|
93
|
+
|
|
94
|
+
expect(mockActiveVisit).toHaveBeenCalledTimes(1);
|
|
77
95
|
});
|
|
78
96
|
});
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
4
4
|
import { showSnackbar, updateVisit, useVisit } from '@openmrs/esm-framework';
|
|
5
|
+
import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
5
6
|
import styles from './end-visit-dialog.scss';
|
|
6
7
|
|
|
7
8
|
interface EndVisitDialogProps {
|
|
@@ -13,10 +14,13 @@ interface EndVisitDialogProps {
|
|
|
13
14
|
* This modal shows up when user clicks on the "End visit" button in the action menu within the
|
|
14
15
|
* patient banner. It should only show when the patient has an active visit. See stop-visit.component.tsx
|
|
15
16
|
* for the button.
|
|
17
|
+
*
|
|
18
|
+
* This dialog uses the patient chart store and SHOULD only be mounted within the patient chart
|
|
16
19
|
*/
|
|
17
20
|
const EndVisitDialog: React.FC<EndVisitDialogProps> = ({ patientUuid, closeModal }) => {
|
|
18
21
|
const { t } = useTranslation();
|
|
19
22
|
const { activeVisit, mutate } = useVisit(patientUuid);
|
|
23
|
+
const { visitContext, setVisitContext } = usePatientChartStore(patientUuid);
|
|
20
24
|
|
|
21
25
|
const handleEndVisit = () => {
|
|
22
26
|
if (activeVisit) {
|
|
@@ -31,7 +35,9 @@ const EndVisitDialog: React.FC<EndVisitDialogProps> = ({ patientUuid, closeModal
|
|
|
31
35
|
mutate();
|
|
32
36
|
window.dispatchEvent(new CustomEvent('queue-entry-updated'));
|
|
33
37
|
closeModal();
|
|
34
|
-
|
|
38
|
+
if (visitContext?.uuid === activeVisit.uuid) {
|
|
39
|
+
setVisitContext(null, null);
|
|
40
|
+
}
|
|
35
41
|
showSnackbar({
|
|
36
42
|
isLowContrast: true,
|
|
37
43
|
kind: 'success',
|
|
@@ -4,6 +4,7 @@ import { screen, render } from '@testing-library/react';
|
|
|
4
4
|
import { showSnackbar, updateVisit, useVisit, type Visit, type FetchResponse } from '@openmrs/esm-framework';
|
|
5
5
|
import { mockCurrentVisit } from '__mocks__';
|
|
6
6
|
import EndVisitDialog from './end-visit-dialog.component';
|
|
7
|
+
import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
7
8
|
|
|
8
9
|
const endVisitPayload = {
|
|
9
10
|
stopDatetime: expect.any(Date),
|
|
@@ -15,6 +16,22 @@ const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
|
15
16
|
const mockUseVisit = jest.mocked(useVisit);
|
|
16
17
|
const mockUpdateVisit = jest.mocked(updateVisit);
|
|
17
18
|
|
|
19
|
+
const mockUsePatientChartStore = jest.mocked(usePatientChartStore);
|
|
20
|
+
const mockSetVisitContext = jest.fn();
|
|
21
|
+
|
|
22
|
+
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
23
|
+
usePatientChartStore: jest.fn(),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
27
|
+
patientUuid: 'patient-123',
|
|
28
|
+
patient: null,
|
|
29
|
+
visitContext: mockCurrentVisit,
|
|
30
|
+
mutateVisitContext: jest.fn(),
|
|
31
|
+
setPatient: jest.fn(),
|
|
32
|
+
setVisitContext: mockSetVisitContext,
|
|
33
|
+
});
|
|
34
|
+
|
|
18
35
|
describe('End visit dialog', () => {
|
|
19
36
|
beforeEach(() => {
|
|
20
37
|
mockUseVisit.mockReturnValue({
|
|
@@ -66,6 +83,8 @@ describe('End visit dialog', () => {
|
|
|
66
83
|
kind: 'success',
|
|
67
84
|
title: 'Visit ended',
|
|
68
85
|
});
|
|
86
|
+
|
|
87
|
+
expect(mockSetVisitContext).toHaveBeenCalledTimes(1);
|
|
69
88
|
});
|
|
70
89
|
|
|
71
90
|
test('displays an error snackbar if there was a problem ending a visit', async () => {
|
|
@@ -20,13 +20,14 @@ const ActiveVisitActions: React.FC<ActiveVisitActionsInterface> = ({ visit, pati
|
|
|
20
20
|
const isTablet = layout === 'tablet';
|
|
21
21
|
const isMobile = layout === 'phone';
|
|
22
22
|
|
|
23
|
-
const launchAllergiesFormWorkspace = useLaunchWorkspaceRequiringVisit('patient-allergy-form-workspace');
|
|
24
|
-
const launchAppointmentsFormWorkspace = useLaunchWorkspaceRequiringVisit('appointments-form-workspace');
|
|
25
|
-
const launchClinicalFormsWorkspace = useLaunchWorkspaceRequiringVisit('clinical-forms-workspace');
|
|
26
|
-
const launchConditionsFormWorkspace = useLaunchWorkspaceRequiringVisit('conditions-form-workspace');
|
|
27
|
-
const launchOrderBasketFormWorkspace = useLaunchWorkspaceRequiringVisit('order-basket');
|
|
28
|
-
const launchVisitNotesFormWorkspace = useLaunchWorkspaceRequiringVisit('visit-notes-form-workspace');
|
|
23
|
+
const launchAllergiesFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'patient-allergy-form-workspace');
|
|
24
|
+
const launchAppointmentsFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'appointments-form-workspace');
|
|
25
|
+
const launchClinicalFormsWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'clinical-forms-workspace');
|
|
26
|
+
const launchConditionsFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'conditions-form-workspace');
|
|
27
|
+
const launchOrderBasketFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'order-basket');
|
|
28
|
+
const launchVisitNotesFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'visit-notes-form-workspace');
|
|
29
29
|
const launchVitalsAndBiometricsFormWorkspace = useLaunchWorkspaceRequiringVisit(
|
|
30
|
+
patientUuid,
|
|
30
31
|
'patient-vitals-biometrics-form-workspace',
|
|
31
32
|
);
|
|
32
33
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { CardHeader, EmptyState } from '@openmrs/esm-patient-common-lib';
|
|
3
|
+
import { launchWorkspace } from '@openmrs/esm-framework';
|
|
4
|
+
import { CardHeader, EmptyState, usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
6
5
|
import VisitSummary from './past-visits-components/visit-summary.component';
|
|
7
6
|
import styles from './current-visit-summary.scss';
|
|
8
7
|
|
|
@@ -10,29 +9,23 @@ interface CurrentVisitSummaryProps {
|
|
|
10
9
|
patientUuid: string;
|
|
11
10
|
}
|
|
12
11
|
|
|
12
|
+
/**
|
|
13
|
+
* This extension is not used in the refapp.
|
|
14
|
+
* This extension uses the patient chart store and SHOULD only be mounted within the patient chart
|
|
15
|
+
*/
|
|
13
16
|
const CurrentVisitSummary: React.FC<CurrentVisitSummaryProps> = ({ patientUuid }) => {
|
|
14
17
|
const { t } = useTranslation();
|
|
15
|
-
const {
|
|
18
|
+
const { patientUuid: storePatientUuid, visitContext } = usePatientChartStore(patientUuid);
|
|
16
19
|
|
|
17
|
-
if (
|
|
18
|
-
return
|
|
19
|
-
<InlineLoading
|
|
20
|
-
status="active"
|
|
21
|
-
iconDescription={t('loading', 'Loading')}
|
|
22
|
-
description={t('loadingVisit', 'Loading current visit...')}
|
|
23
|
-
/>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!!error) {
|
|
28
|
-
return <ErrorState headerTitle={t('failedToLoadCurrentVisit', 'Failed loading current visit')} error={error} />;
|
|
20
|
+
if (patientUuid !== storePatientUuid) {
|
|
21
|
+
return null;
|
|
29
22
|
}
|
|
30
23
|
|
|
31
|
-
if (!
|
|
24
|
+
if (!visitContext) {
|
|
32
25
|
return (
|
|
33
26
|
<EmptyState
|
|
34
27
|
headerTitle={t('currentVisit', 'Current visit')}
|
|
35
|
-
displayText={t('
|
|
28
|
+
displayText={t('activeVisits__lower', 'active visits')}
|
|
36
29
|
launchForm={() =>
|
|
37
30
|
launchWorkspace('start-visit-workspace-form', { openedFrom: 'patient-chart-current-visit-summary' })
|
|
38
31
|
}
|
|
@@ -43,10 +36,10 @@ const CurrentVisitSummary: React.FC<CurrentVisitSummaryProps> = ({ patientUuid }
|
|
|
43
36
|
return (
|
|
44
37
|
<div className={styles.container}>
|
|
45
38
|
<CardHeader title={t('currentVisit', 'Current visit')}>
|
|
46
|
-
<span
|
|
39
|
+
<span />
|
|
47
40
|
</CardHeader>
|
|
48
41
|
<div className={styles.visitSummaryCard}>
|
|
49
|
-
<VisitSummary visit={
|
|
42
|
+
<VisitSummary visit={visitContext} patientUuid={patientUuid} />
|
|
50
43
|
</div>
|
|
51
44
|
</div>
|
|
52
45
|
);
|