@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.
- package/.turbo/turbo-build.log +9 -9
- 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/3119.js +1 -1
- package/dist/3119.js.map +1 -1
- package/dist/4300.js +1 -1
- package/dist/4727.js +1 -1
- package/dist/4727.js.map +1 -1
- package/dist/5048.js +1 -1
- package/dist/5048.js.map +1 -1
- package/dist/6568.js +1 -0
- package/dist/6568.js.map +1 -0
- package/dist/8260.js +1 -0
- package/dist/8260.js.map +1 -0
- package/dist/8454.js +1 -1
- package/dist/8454.js.map +1 -1
- package/dist/9329.js +1 -0
- package/dist/9329.js.map +1 -0
- 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 +100 -100
- 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/en.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,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 {
|
|
32
|
+
const { activeVisit } = useVisit(patientUuid);
|
|
31
33
|
const { visitAttributeTypes } = useConfig<ChartConfig>();
|
|
32
34
|
|
|
33
35
|
return (
|
|
34
36
|
<>
|
|
35
|
-
{
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
<div
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
84
|
-
|
|
54
|
+
</div>
|
|
55
|
+
</>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
85
58
|
</main>
|
|
86
59
|
<WorkspaceContainer
|
|
87
|
-
|
|
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,
|
|
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({
|