@openmrs/esm-patient-chart-app 11.3.1-pre.9294 → 11.3.1-pre.9296

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 (63) hide show
  1. package/.turbo/turbo-build.log +9 -9
  2. package/dist/276.js +1 -1
  3. package/dist/276.js.map +1 -1
  4. package/dist/2761.js +1 -1
  5. package/dist/2761.js.map +1 -1
  6. package/dist/2859.js +1 -1
  7. package/dist/2859.js.map +1 -1
  8. package/dist/3119.js +1 -1
  9. package/dist/3119.js.map +1 -1
  10. package/dist/4300.js +1 -1
  11. package/dist/4727.js +1 -1
  12. package/dist/4727.js.map +1 -1
  13. package/dist/5048.js +1 -1
  14. package/dist/5048.js.map +1 -1
  15. package/dist/6568.js +1 -0
  16. package/dist/6568.js.map +1 -0
  17. package/dist/8260.js +1 -0
  18. package/dist/8260.js.map +1 -0
  19. package/dist/8454.js +1 -1
  20. package/dist/8454.js.map +1 -1
  21. package/dist/9329.js +1 -0
  22. package/dist/9329.js.map +1 -0
  23. package/dist/main.js +1 -1
  24. package/dist/main.js.map +1 -1
  25. package/dist/openmrs-esm-patient-chart-app.js +1 -1
  26. package/dist/openmrs-esm-patient-chart-app.js.buildmanifest.json +100 -100
  27. package/dist/openmrs-esm-patient-chart-app.js.map +1 -1
  28. package/dist/routes.json +1 -1
  29. package/package.json +2 -2
  30. package/src/actions-buttons/delete-visit.component.tsx +8 -3
  31. package/src/actions-buttons/stop-visit.component.tsx +1 -1
  32. package/src/clinical-views/encounter-list/{encounter-list-tabs.component.tsx → encounter-list-tabs.extension.tsx} +10 -6
  33. package/src/index.ts +4 -4
  34. package/src/mark-patient-deceased/mark-patient-deceased-form.test.tsx +2 -0
  35. package/src/patient-banner-tags/{visit-attribute-tags.component.tsx → visit-attribute-tags.extension.tsx} +6 -4
  36. package/src/patient-chart/patient-chart.component.tsx +39 -65
  37. package/src/patient-chart/patient-chart.resources.ts +108 -0
  38. package/src/visit/hooks/useDeleteVisit.test.tsx +39 -42
  39. package/src/visit/hooks/useDeleteVisit.tsx +33 -17
  40. package/src/visit/visit-form/visit-form.test.tsx +3 -9
  41. package/src/visit/visit-form/visit-form.workspace.tsx +17 -7
  42. package/src/visit/visit-prompt/delete-visit-dialog.component.tsx +10 -4
  43. package/src/visit/visit-prompt/delete-visit-dialog.test.tsx +20 -2
  44. package/src/visit/visit-prompt/end-visit-dialog.component.tsx +7 -1
  45. package/src/visit/visit-prompt/end-visit-dialog.test.tsx +19 -0
  46. package/src/visit/visits-widget/active-visit-buttons/active-visit-buttons.tsx +7 -6
  47. package/src/visit/visits-widget/{current-visit-summary.component.tsx → current-visit-summary.extension.tsx} +13 -20
  48. package/src/visit/visits-widget/current-visit-summary.test.tsx +45 -25
  49. package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.component.tsx +4 -3
  50. package/src/visit/visits-widget/past-visits-components/encounters-table/encounters-table.resource.ts +0 -1
  51. package/src/visit/visits-widget/visit-context/retrospective-data-date-time-picker/retrospective-date-time-picker.component.tsx +6 -8
  52. package/src/visit/visits-widget/visit-context/{visit-context-header.component.tsx → visit-context-header.extension.tsx} +17 -15
  53. package/src/visit/visits-widget/visit-context/visit-context-header.test.tsx +35 -29
  54. package/src/visit/visits-widget/visit-context/visit-context-switcher.modal.tsx +13 -11
  55. package/src/visit/visits-widget/visit-context/visit-context-switcher.test.tsx +50 -30
  56. package/src/visit/visits-widget/visit.resource.tsx +1 -1
  57. package/translations/en.json +1 -3
  58. package/dist/2442.js +0 -1
  59. package/dist/2442.js.map +0 -1
  60. package/dist/3042.js +0 -1
  61. package/dist/3042.js.map +0 -1
  62. package/dist/4713.js +0 -1
  63. package/dist/4713.js.map +0 -1
@@ -1,7 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Tag } from '@carbon/react';
3
- import { formatDate, useConfig } from '@openmrs/esm-framework';
4
- import { useVisitOrOfflineVisit } from '@openmrs/esm-patient-common-lib';
3
+ import { formatDate, useConfig, useVisit } from '@openmrs/esm-framework';
5
4
  import { type ChartConfig } from '../config-schema';
6
5
 
7
6
  interface VisitAttributeTagsProps {
@@ -26,13 +25,16 @@ const getAttributeValue = (attributeType, value) => {
26
25
  }
27
26
  };
28
27
 
28
+ /**
29
+ * This extension slots to the patient-banner-tags-slot by default.
30
+ */
29
31
  const VisitAttributeTags: React.FC<VisitAttributeTagsProps> = ({ patientUuid }) => {
30
- const { currentVisit } = useVisitOrOfflineVisit(patientUuid);
32
+ const { activeVisit } = useVisit(patientUuid);
31
33
  const { visitAttributeTypes } = useConfig<ChartConfig>();
32
34
 
33
35
  return (
34
36
  <>
35
- {currentVisit?.attributes
37
+ {activeVisit?.attributes
36
38
  ?.filter(
37
39
  (attribute) =>
38
40
  visitAttributeTypes.find(({ uuid }) => attribute?.attributeType?.uuid === uuid)?.displayInThePatientBanner,
@@ -1,46 +1,21 @@
1
- import {
2
- ExtensionSlot,
3
- WorkspaceContainer,
4
- setCurrentVisit,
5
- usePatient,
6
- useWorkspaces,
7
- useLeftNav,
8
- } from '@openmrs/esm-framework';
9
- import { getPatientChartStore } from '@openmrs/esm-patient-common-lib';
1
+ import React, { useMemo, useState } from 'react';
10
2
  import classNames from 'classnames';
11
- import React, { useEffect, useMemo, useState } from 'react';
12
3
  import { useParams } from 'react-router-dom';
4
+ import { ExtensionSlot, WorkspaceContainer, useWorkspaces, useLeftNav } from '@openmrs/esm-framework';
13
5
  import { spaBasePath } from '../constants';
14
- import Loader from '../loader/loader.component';
6
+ import { usePatientChartPatientAndVisit } from './patient-chart.resources';
7
+ import { type LayoutMode } from './chart-review/dashboard-view.component';
15
8
  import ChartReview from '../patient-chart/chart-review/chart-review.component';
9
+ import Loader from '../loader/loader.component';
16
10
  import SideMenuPanel from '../side-nav/side-menu.component';
17
- import { type LayoutMode } from './chart-review/dashboard-view.component';
18
11
  import styles from './patient-chart.scss';
19
12
 
20
13
  const PatientChart: React.FC = () => {
21
14
  const { patientUuid, view: encodedView } = useParams();
22
- const view = decodeURIComponent(encodedView);
23
- const { isLoading: isLoadingPatient, patient } = usePatient(patientUuid);
24
- const state = useMemo(() => ({ patient, patientUuid }), [patient, patientUuid]);
25
15
  const { workspaceWindowState, active } = useWorkspaces();
26
16
  const [layoutMode, setLayoutMode] = useState<LayoutMode>();
27
- // Keep state updated with the current patient. Anything used outside the patient
28
- // chart (e.g., the current visit is used by the Active Visit Tag used in the
29
- // patient search) must be updated in the callback, which is called when the patient
30
- // chart unmounts.
31
- useEffect(() => {
32
- setCurrentVisit(patientUuid, null);
33
- return () => {
34
- setCurrentVisit(null, null);
35
- };
36
- }, [patientUuid]);
37
-
38
- useEffect(() => {
39
- getPatientChartStore().setState({ ...state });
40
- return () => {
41
- getPatientChartStore().setState({});
42
- };
43
- }, [state]);
17
+ const state = usePatientChartPatientAndVisit(patientUuid);
18
+ const view = decodeURIComponent(encodedView);
44
19
 
45
20
  const leftNavBasePath = useMemo(() => spaBasePath.replace(':patientUuid', patientUuid), [patientUuid]);
46
21
 
@@ -50,43 +25,42 @@ const PatientChart: React.FC = () => {
50
25
  <>
51
26
  <SideMenuPanel />
52
27
  <main className={classNames('omrs-main-content', styles.chartContainer)}>
53
- <>
54
- <div
55
- className={classNames(
56
- styles.innerChartContainer,
57
- workspaceWindowState === 'normal' && active ? styles.closeWorkspace : styles.activeWorkspace,
58
- )}
59
- >
60
- {isLoadingPatient ? (
61
- <Loader />
62
- ) : (
63
- <>
64
- <aside>
65
- <ExtensionSlot name="patient-header-slot" state={state} />
66
- <ExtensionSlot name="patient-highlights-bar-slot" state={state} />
67
- <ExtensionSlot name="patient-info-slot" state={state} />
68
- </aside>
69
- <div className={styles.grid}>
70
- <div
71
- className={classNames(styles.chartReview, { [styles.widthContained]: layoutMode == 'contained' })}
72
- >
73
- <ChartReview
74
- patient={state.patient}
75
- patientUuid={state.patientUuid}
76
- view={view}
77
- setDashboardLayoutMode={setLayoutMode}
78
- />
79
- </div>
28
+ <div
29
+ className={classNames(
30
+ styles.innerChartContainer,
31
+ workspaceWindowState === 'normal' && active ? styles.closeWorkspace : styles.activeWorkspace,
32
+ )}
33
+ >
34
+ {state.isLoadingPatient ? (
35
+ <Loader />
36
+ ) : (
37
+ <>
38
+ <aside>
39
+ <ExtensionSlot name="patient-header-slot" state={state} />
40
+ <ExtensionSlot name="patient-highlights-bar-slot" state={state} />
41
+ <ExtensionSlot name="patient-info-slot" state={state} />
42
+ </aside>
43
+ <div className={styles.grid}>
44
+ <div
45
+ className={classNames(styles.chartReview, { [styles.widthContained]: layoutMode === 'contained' })}
46
+ >
47
+ <ChartReview
48
+ patient={state.patient}
49
+ patientUuid={state.patientUuid}
50
+ view={view}
51
+ setDashboardLayoutMode={setLayoutMode}
52
+ />
80
53
  </div>
81
- </>
82
- )}
83
- </div>
84
- </>
54
+ </div>
55
+ </>
56
+ )}
57
+ </div>
85
58
  </main>
86
59
  <WorkspaceContainer
87
- showSiderailAndBottomNav
88
- contextKey={`patient/${patientUuid}`}
60
+ actionMenuProps={state}
89
61
  additionalWorkspaceProps={state}
62
+ contextKey={`patient/${patientUuid}`}
63
+ showSiderailAndBottomNav
90
64
  />
91
65
  </>
92
66
  );
@@ -0,0 +1,108 @@
1
+ import { useEffect, useMemo } from 'react';
2
+ import useSWR from 'swr';
3
+ import { openmrsFetch, restBaseUrl, usePatient, useVisit, type Visit } from '@openmrs/esm-framework';
4
+ import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
5
+
6
+ const defaultVisitCustomRepresentation =
7
+ 'custom:(uuid,display,voided,indication,startDatetime,stopDatetime,' +
8
+ 'encounters:(uuid,display,encounterDatetime,' +
9
+ 'form:(uuid,name),location:ref,' +
10
+ 'encounterType:ref,' +
11
+ 'encounterProviders:(uuid,display,' +
12
+ 'provider:(uuid,display))),' +
13
+ 'patient:(uuid,display),' +
14
+ 'visitType:(uuid,name,display),' +
15
+ 'attributes:(uuid,display,attributeType:(name,datatypeClassname,uuid),value),' +
16
+ 'location:(uuid,name,display))';
17
+
18
+ export function useVisitByUuid(visitUuid: string | null, representation: string = defaultVisitCustomRepresentation) {
19
+ const url = `${restBaseUrl}/visit/${visitUuid}?v=${representation}`;
20
+ const { data, ...rest } = useSWR<{ data: Visit }>(visitUuid ? url : null, openmrsFetch);
21
+ return { visit: data?.data, ...rest };
22
+ }
23
+
24
+ /**
25
+ * This hook manages fetching of the patient and the visitContext
26
+ * when entering the patient chart, and the associated updated to patient chart store.
27
+ *
28
+ * The patient chart store sets the patient when we enter the patient chart
29
+ * and unsets the patient when we leave. (This gives extensions and workspaces a way
30
+ * to check whether they are rendered within the patient chart app.)
31
+ * Note: We do not unset visitContext when leaving the chart, so it persists across
32
+ * in‑app navigation. On a full page reload, visitContext is rehydrated by refetching
33
+ * (via useVisit/useVisitByUuId) rather than restored from storage.
34
+ * When we enter the chart, we want to update the visit context as follows:
35
+ * does the stored visitContext exist and belong to the patient?
36
+ * 1. If so, the visitContext should be valid but possibly stale; fetch the visit again
37
+ * and update the context
38
+ * 2. If not, fetch the active visit of the patient, If it exists, set it as the
39
+ * visitContext; otherwise, clear it.
40
+ * @param patientUuid
41
+ * @returns
42
+ */
43
+ export function usePatientChartPatientAndVisit(patientUuid: string) {
44
+ const { isLoading: isLoadingPatient, patient } = usePatient(patientUuid);
45
+ const {
46
+ patientUuid: storePatientUuid,
47
+ setPatient,
48
+ visitContext,
49
+ mutateVisitContext,
50
+ setVisitContext,
51
+ } = usePatientChartStore(patientUuid);
52
+
53
+ const isVisitContextValid = visitContext && visitContext.patient?.uuid === patientUuid;
54
+ const {
55
+ visit: newVisitContext,
56
+ mutate: newMutateVisitContext,
57
+ isValidating: isValidatingVisitContext,
58
+ } = useVisitByUuid(isVisitContextValid ? visitContext.uuid : null);
59
+ const {
60
+ activeVisit,
61
+ isValidating: isValidatingActiveVisit,
62
+ mutate: mutateActiveVisit,
63
+ } = useVisit(isVisitContextValid ? null : patientUuid);
64
+
65
+ useEffect(() => {
66
+ if (!isValidatingVisitContext && !isValidatingActiveVisit && storePatientUuid) {
67
+ if (activeVisit) {
68
+ setVisitContext(activeVisit, mutateActiveVisit);
69
+ } else if (newVisitContext) {
70
+ setVisitContext(newVisitContext, newMutateVisitContext);
71
+ } else {
72
+ setVisitContext(null, null);
73
+ }
74
+ }
75
+ }, [
76
+ newVisitContext,
77
+ isValidatingVisitContext,
78
+ newMutateVisitContext,
79
+ setVisitContext,
80
+ activeVisit,
81
+ isValidatingActiveVisit,
82
+ storePatientUuid,
83
+ mutateActiveVisit,
84
+ ]);
85
+
86
+ useEffect(() => {
87
+ if (!isLoadingPatient) {
88
+ setPatient(patient);
89
+ }
90
+
91
+ return () => {
92
+ setPatient(null);
93
+ };
94
+ }, [patient, setPatient, isLoadingPatient]);
95
+
96
+ const state = useMemo(
97
+ () => ({
98
+ patientUuid,
99
+ patient: patient ?? {},
100
+ visitContext,
101
+ mutateVisitContext,
102
+ isLoadingPatient,
103
+ }),
104
+ [patient, patientUuid, visitContext, mutateVisitContext, isLoadingPatient],
105
+ );
106
+
107
+ return state;
108
+ }
@@ -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({