@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.
Files changed (139) hide show
  1. package/.turbo/turbo-build.log +9 -9
  2. package/dist/1119.js +1 -1
  3. package/dist/1197.js +1 -1
  4. package/dist/2146.js +1 -1
  5. package/dist/2690.js +1 -1
  6. package/dist/276.js +1 -1
  7. package/dist/276.js.map +1 -1
  8. package/dist/2761.js +1 -1
  9. package/dist/2761.js.map +1 -1
  10. package/dist/2859.js +1 -1
  11. package/dist/2859.js.map +1 -1
  12. package/dist/3099.js +1 -1
  13. package/dist/3119.js +1 -1
  14. package/dist/3119.js.map +1 -1
  15. package/dist/3584.js +1 -1
  16. package/dist/4055.js +1 -1
  17. package/dist/4132.js +1 -1
  18. package/dist/4300.js +1 -1
  19. package/dist/4335.js +1 -1
  20. package/dist/4618.js +1 -1
  21. package/dist/4652.js +1 -1
  22. package/dist/4727.js +1 -1
  23. package/dist/4727.js.map +1 -1
  24. package/dist/4944.js +1 -1
  25. package/dist/5048.js +1 -1
  26. package/dist/5048.js.map +1 -1
  27. package/dist/5173.js +1 -1
  28. package/dist/5241.js +1 -1
  29. package/dist/5442.js +1 -1
  30. package/dist/5661.js +1 -1
  31. package/dist/6022.js +1 -1
  32. package/dist/6468.js +1 -1
  33. package/dist/6568.js +1 -0
  34. package/dist/6568.js.map +1 -0
  35. package/dist/6679.js +1 -1
  36. package/dist/6840.js +1 -1
  37. package/dist/6859.js +1 -1
  38. package/dist/7097.js +1 -1
  39. package/dist/7159.js +1 -1
  40. package/dist/723.js +1 -1
  41. package/dist/7617.js +1 -1
  42. package/dist/795.js +1 -1
  43. package/dist/8163.js +1 -1
  44. package/dist/8260.js +1 -0
  45. package/dist/8260.js.map +1 -0
  46. package/dist/8349.js +1 -1
  47. package/dist/8454.js +1 -1
  48. package/dist/8454.js.map +1 -1
  49. package/dist/8618.js +1 -1
  50. package/dist/890.js +1 -1
  51. package/dist/9214.js +1 -1
  52. package/dist/9329.js +1 -0
  53. package/dist/9329.js.map +1 -0
  54. package/dist/9538.js +1 -1
  55. package/dist/9569.js +1 -1
  56. package/dist/986.js +1 -1
  57. package/dist/9879.js +1 -1
  58. package/dist/9895.js +1 -1
  59. package/dist/9900.js +1 -1
  60. package/dist/9913.js +1 -1
  61. package/dist/main.js +1 -1
  62. package/dist/main.js.map +1 -1
  63. package/dist/openmrs-esm-patient-chart-app.js +1 -1
  64. package/dist/openmrs-esm-patient-chart-app.js.buildmanifest.json +214 -214
  65. package/dist/openmrs-esm-patient-chart-app.js.map +1 -1
  66. package/dist/routes.json +1 -1
  67. package/package.json +2 -2
  68. package/src/actions-buttons/delete-visit.component.tsx +8 -3
  69. package/src/actions-buttons/stop-visit.component.tsx +1 -1
  70. package/src/clinical-views/encounter-list/{encounter-list-tabs.component.tsx → encounter-list-tabs.extension.tsx} +10 -6
  71. package/src/index.ts +4 -4
  72. package/src/mark-patient-deceased/mark-patient-deceased-form.test.tsx +2 -0
  73. package/src/patient-banner-tags/{visit-attribute-tags.component.tsx → visit-attribute-tags.extension.tsx} +6 -4
  74. package/src/patient-chart/patient-chart.component.tsx +39 -65
  75. package/src/patient-chart/patient-chart.resources.ts +108 -0
  76. package/src/visit/hooks/useDeleteVisit.test.tsx +39 -42
  77. package/src/visit/hooks/useDeleteVisit.tsx +33 -17
  78. package/src/visit/visit-form/visit-form.test.tsx +3 -9
  79. package/src/visit/visit-form/visit-form.workspace.tsx +17 -7
  80. package/src/visit/visit-prompt/delete-visit-dialog.component.tsx +10 -4
  81. package/src/visit/visit-prompt/delete-visit-dialog.test.tsx +20 -2
  82. package/src/visit/visit-prompt/end-visit-dialog.component.tsx +7 -1
  83. package/src/visit/visit-prompt/end-visit-dialog.test.tsx +19 -0
  84. package/src/visit/visits-widget/active-visit-buttons/active-visit-buttons.tsx +7 -6
  85. package/src/visit/visits-widget/{current-visit-summary.component.tsx → current-visit-summary.extension.tsx} +13 -20
  86. package/src/visit/visits-widget/current-visit-summary.test.tsx +45 -25
  87. package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.component.tsx +4 -3
  88. package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.resource.ts +0 -1
  89. package/src/visit/visits-widget/visit-context/retrospective-data-date-time-picker/retrospective-date-time-picker.component.tsx +6 -8
  90. package/src/visit/visits-widget/visit-context/{visit-context-header.component.tsx → visit-context-header.extension.tsx} +17 -15
  91. package/src/visit/visits-widget/visit-context/visit-context-header.test.tsx +35 -29
  92. package/src/visit/visits-widget/visit-context/visit-context-switcher.modal.tsx +13 -11
  93. package/src/visit/visits-widget/visit-context/visit-context-switcher.test.tsx +50 -30
  94. package/src/visit/visits-widget/visit.resource.tsx +1 -1
  95. package/translations/am.json +1 -3
  96. package/translations/ar.json +1 -3
  97. package/translations/ar_SY.json +1 -3
  98. package/translations/bn.json +1 -3
  99. package/translations/de.json +1 -3
  100. package/translations/en.json +1 -3
  101. package/translations/en_US.json +1 -3
  102. package/translations/es.json +1 -3
  103. package/translations/es_MX.json +1 -3
  104. package/translations/fr.json +1 -3
  105. package/translations/he.json +1 -3
  106. package/translations/hi.json +1 -3
  107. package/translations/hi_IN.json +1 -3
  108. package/translations/id.json +1 -3
  109. package/translations/it.json +1 -3
  110. package/translations/ka.json +1 -3
  111. package/translations/km.json +1 -3
  112. package/translations/ku.json +1 -3
  113. package/translations/ky.json +1 -3
  114. package/translations/lg.json +1 -3
  115. package/translations/ne.json +1 -3
  116. package/translations/pl.json +1 -3
  117. package/translations/pt.json +1 -3
  118. package/translations/pt_BR.json +1 -3
  119. package/translations/qu.json +1 -3
  120. package/translations/ro_RO.json +1 -3
  121. package/translations/ru_RU.json +1 -3
  122. package/translations/si.json +1 -3
  123. package/translations/sw.json +1 -3
  124. package/translations/sw_KE.json +1 -3
  125. package/translations/tr.json +1 -3
  126. package/translations/tr_TR.json +1 -3
  127. package/translations/uk.json +1 -3
  128. package/translations/uz.json +1 -3
  129. package/translations/uz@Latn.json +1 -3
  130. package/translations/uz_UZ.json +1 -3
  131. package/translations/vi.json +1 -3
  132. package/translations/zh.json +1 -3
  133. package/translations/zh_CN.json +1 -3
  134. package/dist/2442.js +0 -1
  135. package/dist/2442.js.map +0 -1
  136. package/dist/3042.js +0 -1
  137. package/dist/3042.js.map +0 -1
  138. package/dist/4713.js +0 -1
  139. 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, useVisit, type Visit, type VisitType } from '@openmrs/esm-framework';
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 mockUseVisit = jest.mocked(useVisit);
30
+ const mockUsePatientChartStore = jest.mocked(usePatientChartStore);
30
31
  const mockInvalidateVisitAndEncounterData = jest.mocked(invalidateVisitAndEncounterData);
31
32
 
32
- const mockMutateCurrentVisit = jest.fn();
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
- jest.clearAllMocks();
58
-
59
- mockUseVisit.mockReturnValue({
60
- mutate: mockMutateCurrentVisit,
61
- currentVisit: null,
62
- activeVisit: null,
63
- currentVisitIsRetrospective: false,
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(mockMutateCurrentVisit).toHaveBeenCalledTimes(1);
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(mockMutateCurrentVisit).toHaveBeenCalledTimes(1);
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
- mockUseVisit.mockReturnValue({
142
- mutate: mockMutateCurrentVisit,
143
- currentVisit: null,
144
- activeVisit: null,
145
- currentVisitIsRetrospective: false,
146
- error: null,
147
- isLoading: false,
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(mockMutateCurrentVisit).toHaveBeenCalledTimes(1);
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
- mockUseVisit.mockReturnValue({
190
- mutate: mockMutateCurrentVisit,
191
- currentVisit: null,
192
- activeVisit: null,
193
- currentVisitIsRetrospective: false,
194
- error: null,
195
- isLoading: false,
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(mockMutateCurrentVisit).toHaveBeenCalledTimes(1);
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, useVisit } from '@openmrs/esm-framework';
5
- import { invalidateVisitAndEncounterData } from '@openmrs/esm-patient-common-lib';
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(visit: Visit, onVisitDelete = () => {}, onVisitRestore = () => {}) {
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 = visit?.patient?.uuid || '';
21
+ const patientUuid = activeVisit?.patient?.uuid || '';
22
+ const { visitContext, setVisitContext } = usePatientChartStore(patientUuid);
14
23
 
15
24
  const restoreDeletedVisit = () => {
16
- restoreVisit(visit?.uuid)
17
- .then(() => {
25
+ restoreVisit(activeVisit?.uuid)
26
+ .then(({ data: updatedVisit }) => {
18
27
  // Update current visit data for critical components (useVisit hook)
19
- mutateCurrentVisit();
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: visit?.visitType?.display ?? t('visit', '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
- mutateCurrentVisit();
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: visit?.visitType?.display ?? t('visit', '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(visit?.uuid)
63
+ deleteVisit(activeVisit?.uuid)
51
64
  .then(() => {
52
- // Update current visit data for critical components (useVisit hook)
53
- mutateCurrentVisit();
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: visit?.visitType?.display ?? t('visit', 'Visit'),
76
+ visit: activeVisit?.visitType?.display ?? t('visit', 'Visit'),
61
77
  }),
62
78
  subtitle: t('visitDeletedSuccessfully', '{{visit}} deleted successfully', {
63
- visit: visit?.visitType?.display ?? t('visit', '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
- mutateCurrentVisit();
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: mutateCurrentVisit } = useVisit(patientUuid);
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 current visit data for critical components (useVisit hook)
331
- mutateCurrentVisit();
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
- // (current visit is already updated with mutateCurrentVisit)
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
- mutateCurrentVisit();
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
- mutateCurrentVisit,
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
- visit: Visit;
11
+ activeVisit: Visit;
12
+ mutateActiveVisit: () => void;
12
13
  }
13
14
 
14
- const DeleteVisitDialog: React.FC<DeleteVisitDialogProps> = ({ closeModal, patientUuid, visit }) => {
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(visit, closeModal);
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: visit?.visitType?.display ?? t('visit', '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(<DeleteVisitDialog visit={mockCurrentVisit} closeModal={mockCloseModal} patientUuid={mockPatient.id} />);
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(<DeleteVisitDialog visit={mockCurrentVisit} closeModal={mockCloseModal} patientUuid={mockPatient.id} />);
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 { InlineLoading } from '@carbon/react';
4
- import { ErrorState, launchWorkspace, useVisit } from '@openmrs/esm-framework';
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 { isLoading, currentVisit, error, isValidating } = useVisit(patientUuid);
18
+ const { patientUuid: storePatientUuid, visitContext } = usePatientChartStore(patientUuid);
16
19
 
17
- if (isLoading) {
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 (!currentVisit) {
24
+ if (!visitContext) {
32
25
  return (
33
26
  <EmptyState
34
27
  headerTitle={t('currentVisit', 'Current visit')}
35
- displayText={t('noActiveVisitMessage', 'active visit')}
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>{isValidating ? <InlineLoading /> : null}</span>
39
+ <span />
47
40
  </CardHeader>
48
41
  <div className={styles.visitSummaryCard}>
49
- <VisitSummary visit={currentVisit} patientUuid={patientUuid} />
42
+ <VisitSummary visit={visitContext} patientUuid={patientUuid} />
50
43
  </div>
51
44
  </div>
52
45
  );