@openmrs/esm-patient-chart-app 11.3.1-pre.9283 → 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
|
@@ -36,8 +36,10 @@ import {
|
|
|
36
36
|
} from '@openmrs/esm-framework';
|
|
37
37
|
import {
|
|
38
38
|
createOfflineVisitForPatient,
|
|
39
|
+
invalidateVisitByUuid,
|
|
39
40
|
invalidateVisitAndEncounterData,
|
|
40
41
|
useActivePatientEnrollment,
|
|
42
|
+
usePatientChartStore,
|
|
41
43
|
type DefaultPatientWorkspaceProps,
|
|
42
44
|
} from '@openmrs/esm-patient-common-lib';
|
|
43
45
|
import { MemoizedRecommendedVisitType } from './recommended-visit-type.component';
|
|
@@ -120,9 +122,10 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
120
122
|
);
|
|
121
123
|
const visitHeaderSlotState = useMemo(() => ({ patientUuid }), [patientUuid]);
|
|
122
124
|
const { activePatientEnrollment, isLoading } = useActivePatientEnrollment(patientUuid);
|
|
123
|
-
const { mutate:
|
|
125
|
+
const { mutate: mutateActiveVisit } = useVisit(patientUuid);
|
|
124
126
|
const { mutate: globalMutate } = useSWRConfig();
|
|
125
127
|
const allVisitTypes = useConditionalVisitTypes();
|
|
128
|
+
const { setVisitContext } = usePatientChartStore(patientUuid);
|
|
126
129
|
|
|
127
130
|
const [errorFetchingResources, setErrorFetchingResources] = useState<{
|
|
128
131
|
blockSavingForm: boolean;
|
|
@@ -327,12 +330,15 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
327
330
|
// 1. Current visit data (for critical components like visit summary, action buttons)
|
|
328
331
|
// 2. Visit history table (for the paginated visit list)
|
|
329
332
|
|
|
330
|
-
// Update
|
|
331
|
-
|
|
333
|
+
// Update patient's visit data for critical components
|
|
334
|
+
const mutateSavedOrUpdatedVisit = () => invalidateVisitByUuid(globalMutate, visit.uuid);
|
|
335
|
+
mutateActiveVisit();
|
|
336
|
+
setVisitContext?.(visit, mutateSavedOrUpdatedVisit);
|
|
337
|
+
visitToEdit && mutateSavedOrUpdatedVisit();
|
|
332
338
|
|
|
333
339
|
// Use targeted SWR invalidation instead of global mutateVisit
|
|
334
340
|
// This will invalidate visit history and encounter tables for this patient
|
|
335
|
-
// (
|
|
341
|
+
// (if visitContext is updated, it should have been invalidated with mutateSavedOrUpdatedVisit)
|
|
336
342
|
invalidateVisitAndEncounterData(globalMutate, patientUuid);
|
|
337
343
|
|
|
338
344
|
// handleVisitAttributes already has code to show error snackbar when attribute fails to update
|
|
@@ -371,9 +377,12 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
371
377
|
config.offlineVisitTypeUuid,
|
|
372
378
|
payload.startDatetime,
|
|
373
379
|
).then(
|
|
374
|
-
() => {
|
|
380
|
+
(visit) => {
|
|
375
381
|
// Use same targeted approach for offline visits for consistency
|
|
376
|
-
|
|
382
|
+
const mutateSavedOrUpdatedVisit = () => invalidateVisitByUuid(globalMutate, visit.uuid);
|
|
383
|
+
mutateActiveVisit();
|
|
384
|
+
setVisitContext?.(visit, mutateSavedOrUpdatedVisit);
|
|
385
|
+
visitToEdit && mutateSavedOrUpdatedVisit();
|
|
377
386
|
|
|
378
387
|
// Also invalidate visit history and encounter tables
|
|
379
388
|
invalidateVisitAndEncounterData(globalMutate, patientUuid);
|
|
@@ -407,12 +416,13 @@ const VisitForm: React.FC<VisitFormProps> = ({
|
|
|
407
416
|
globalMutate,
|
|
408
417
|
handleVisitAttributes,
|
|
409
418
|
isOnline,
|
|
410
|
-
|
|
419
|
+
setVisitContext,
|
|
411
420
|
patientUuid,
|
|
412
421
|
t,
|
|
413
422
|
visitFormCallbacks,
|
|
414
423
|
visitToEdit,
|
|
415
424
|
isValidVisitAttributesArray,
|
|
425
|
+
mutateActiveVisit,
|
|
416
426
|
],
|
|
417
427
|
);
|
|
418
428
|
|
|
@@ -8,12 +8,18 @@ import styles from './start-visit-dialog.scss';
|
|
|
8
8
|
interface DeleteVisitDialogProps {
|
|
9
9
|
closeModal: () => void;
|
|
10
10
|
patientUuid: string;
|
|
11
|
-
|
|
11
|
+
activeVisit: Visit;
|
|
12
|
+
mutateActiveVisit: () => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
const DeleteVisitDialog: React.FC<DeleteVisitDialogProps> = ({
|
|
15
|
+
const DeleteVisitDialog: React.FC<DeleteVisitDialogProps> = ({
|
|
16
|
+
closeModal,
|
|
17
|
+
mutateActiveVisit,
|
|
18
|
+
patientUuid,
|
|
19
|
+
activeVisit,
|
|
20
|
+
}) => {
|
|
15
21
|
const { t } = useTranslation();
|
|
16
|
-
const { isDeletingVisit, initiateDeletingVisit } = useDeleteVisit(
|
|
22
|
+
const { isDeletingVisit, initiateDeletingVisit } = useDeleteVisit(activeVisit, mutateActiveVisit, closeModal);
|
|
17
23
|
|
|
18
24
|
return (
|
|
19
25
|
<div>
|
|
@@ -24,7 +30,7 @@ const DeleteVisitDialog: React.FC<DeleteVisitDialogProps> = ({ closeModal, patie
|
|
|
24
30
|
<ModalBody>
|
|
25
31
|
<p className={styles.body}>
|
|
26
32
|
{t('confirmDeleteVisitText', 'Deleting this {{visit}} will delete its associated encounters.', {
|
|
27
|
-
visit:
|
|
33
|
+
visit: activeVisit?.visitType?.display ?? t('visit', 'Visit'),
|
|
28
34
|
})}
|
|
29
35
|
</p>
|
|
30
36
|
</ModalBody>
|
|
@@ -9,6 +9,7 @@ import DeleteVisitDialog from './delete-visit-dialog.component';
|
|
|
9
9
|
const mockCloseModal = jest.fn();
|
|
10
10
|
const mockOpenmrsFetch = jest.mocked(openmrsFetch);
|
|
11
11
|
const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
12
|
+
const mockActiveVisit = jest.fn();
|
|
12
13
|
|
|
13
14
|
describe('Delete visit', () => {
|
|
14
15
|
it('voids the visit and voids its associated encounters', async () => {
|
|
@@ -21,7 +22,14 @@ describe('Delete visit', () => {
|
|
|
21
22
|
|
|
22
23
|
mockOpenmrsFetch.mockResolvedValue(response as FetchResponse);
|
|
23
24
|
|
|
24
|
-
render(
|
|
25
|
+
render(
|
|
26
|
+
<DeleteVisitDialog
|
|
27
|
+
activeVisit={mockCurrentVisit}
|
|
28
|
+
mutateActiveVisit={mockActiveVisit}
|
|
29
|
+
closeModal={mockCloseModal}
|
|
30
|
+
patientUuid={mockPatient.id}
|
|
31
|
+
/>,
|
|
32
|
+
);
|
|
25
33
|
|
|
26
34
|
const cancelButton = screen.getByRole('button', { name: /^cancel$/i });
|
|
27
35
|
const deleteVisitButton = screen.getByRole('button', { name: /delete visit$/i });
|
|
@@ -46,6 +54,7 @@ describe('Delete visit', () => {
|
|
|
46
54
|
subtitle: 'Facility Visit deleted successfully',
|
|
47
55
|
}),
|
|
48
56
|
);
|
|
57
|
+
expect(mockActiveVisit).toHaveBeenCalledTimes(1);
|
|
49
58
|
});
|
|
50
59
|
|
|
51
60
|
it('displays an error notification if there was problem with deleting a visit', async () => {
|
|
@@ -53,7 +62,14 @@ describe('Delete visit', () => {
|
|
|
53
62
|
|
|
54
63
|
mockOpenmrsFetch.mockRejectedValueOnce({ message: 'Internal server error', status: 500 });
|
|
55
64
|
|
|
56
|
-
render(
|
|
65
|
+
render(
|
|
66
|
+
<DeleteVisitDialog
|
|
67
|
+
activeVisit={mockCurrentVisit}
|
|
68
|
+
mutateActiveVisit={mockActiveVisit}
|
|
69
|
+
closeModal={mockCloseModal}
|
|
70
|
+
patientUuid={mockPatient.id}
|
|
71
|
+
/>,
|
|
72
|
+
);
|
|
57
73
|
|
|
58
74
|
const cancelButton = screen.getByRole('button', { name: /^cancel$/i });
|
|
59
75
|
const deleteVisitButton = screen.getByRole('button', { name: /delete visit$/i });
|
|
@@ -74,5 +90,7 @@ describe('Delete visit', () => {
|
|
|
74
90
|
kind: 'error',
|
|
75
91
|
title: 'Error deleting visit',
|
|
76
92
|
});
|
|
93
|
+
|
|
94
|
+
expect(mockActiveVisit).toHaveBeenCalledTimes(1);
|
|
77
95
|
});
|
|
78
96
|
});
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
4
4
|
import { showSnackbar, updateVisit, useVisit } from '@openmrs/esm-framework';
|
|
5
|
+
import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
5
6
|
import styles from './end-visit-dialog.scss';
|
|
6
7
|
|
|
7
8
|
interface EndVisitDialogProps {
|
|
@@ -13,10 +14,13 @@ interface EndVisitDialogProps {
|
|
|
13
14
|
* This modal shows up when user clicks on the "End visit" button in the action menu within the
|
|
14
15
|
* patient banner. It should only show when the patient has an active visit. See stop-visit.component.tsx
|
|
15
16
|
* for the button.
|
|
17
|
+
*
|
|
18
|
+
* This dialog uses the patient chart store and SHOULD only be mounted within the patient chart
|
|
16
19
|
*/
|
|
17
20
|
const EndVisitDialog: React.FC<EndVisitDialogProps> = ({ patientUuid, closeModal }) => {
|
|
18
21
|
const { t } = useTranslation();
|
|
19
22
|
const { activeVisit, mutate } = useVisit(patientUuid);
|
|
23
|
+
const { visitContext, setVisitContext } = usePatientChartStore(patientUuid);
|
|
20
24
|
|
|
21
25
|
const handleEndVisit = () => {
|
|
22
26
|
if (activeVisit) {
|
|
@@ -31,7 +35,9 @@ const EndVisitDialog: React.FC<EndVisitDialogProps> = ({ patientUuid, closeModal
|
|
|
31
35
|
mutate();
|
|
32
36
|
window.dispatchEvent(new CustomEvent('queue-entry-updated'));
|
|
33
37
|
closeModal();
|
|
34
|
-
|
|
38
|
+
if (visitContext?.uuid === activeVisit.uuid) {
|
|
39
|
+
setVisitContext(null, null);
|
|
40
|
+
}
|
|
35
41
|
showSnackbar({
|
|
36
42
|
isLowContrast: true,
|
|
37
43
|
kind: 'success',
|
|
@@ -4,6 +4,7 @@ import { screen, render } from '@testing-library/react';
|
|
|
4
4
|
import { showSnackbar, updateVisit, useVisit, type Visit, type FetchResponse } from '@openmrs/esm-framework';
|
|
5
5
|
import { mockCurrentVisit } from '__mocks__';
|
|
6
6
|
import EndVisitDialog from './end-visit-dialog.component';
|
|
7
|
+
import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
7
8
|
|
|
8
9
|
const endVisitPayload = {
|
|
9
10
|
stopDatetime: expect.any(Date),
|
|
@@ -15,6 +16,22 @@ const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
|
15
16
|
const mockUseVisit = jest.mocked(useVisit);
|
|
16
17
|
const mockUpdateVisit = jest.mocked(updateVisit);
|
|
17
18
|
|
|
19
|
+
const mockUsePatientChartStore = jest.mocked(usePatientChartStore);
|
|
20
|
+
const mockSetVisitContext = jest.fn();
|
|
21
|
+
|
|
22
|
+
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
23
|
+
usePatientChartStore: jest.fn(),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
27
|
+
patientUuid: 'patient-123',
|
|
28
|
+
patient: null,
|
|
29
|
+
visitContext: mockCurrentVisit,
|
|
30
|
+
mutateVisitContext: jest.fn(),
|
|
31
|
+
setPatient: jest.fn(),
|
|
32
|
+
setVisitContext: mockSetVisitContext,
|
|
33
|
+
});
|
|
34
|
+
|
|
18
35
|
describe('End visit dialog', () => {
|
|
19
36
|
beforeEach(() => {
|
|
20
37
|
mockUseVisit.mockReturnValue({
|
|
@@ -66,6 +83,8 @@ describe('End visit dialog', () => {
|
|
|
66
83
|
kind: 'success',
|
|
67
84
|
title: 'Visit ended',
|
|
68
85
|
});
|
|
86
|
+
|
|
87
|
+
expect(mockSetVisitContext).toHaveBeenCalledTimes(1);
|
|
69
88
|
});
|
|
70
89
|
|
|
71
90
|
test('displays an error snackbar if there was a problem ending a visit', async () => {
|
|
@@ -20,13 +20,14 @@ const ActiveVisitActions: React.FC<ActiveVisitActionsInterface> = ({ visit, pati
|
|
|
20
20
|
const isTablet = layout === 'tablet';
|
|
21
21
|
const isMobile = layout === 'phone';
|
|
22
22
|
|
|
23
|
-
const launchAllergiesFormWorkspace = useLaunchWorkspaceRequiringVisit('patient-allergy-form-workspace');
|
|
24
|
-
const launchAppointmentsFormWorkspace = useLaunchWorkspaceRequiringVisit('appointments-form-workspace');
|
|
25
|
-
const launchClinicalFormsWorkspace = useLaunchWorkspaceRequiringVisit('clinical-forms-workspace');
|
|
26
|
-
const launchConditionsFormWorkspace = useLaunchWorkspaceRequiringVisit('conditions-form-workspace');
|
|
27
|
-
const launchOrderBasketFormWorkspace = useLaunchWorkspaceRequiringVisit('order-basket');
|
|
28
|
-
const launchVisitNotesFormWorkspace = useLaunchWorkspaceRequiringVisit('visit-notes-form-workspace');
|
|
23
|
+
const launchAllergiesFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'patient-allergy-form-workspace');
|
|
24
|
+
const launchAppointmentsFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'appointments-form-workspace');
|
|
25
|
+
const launchClinicalFormsWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'clinical-forms-workspace');
|
|
26
|
+
const launchConditionsFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'conditions-form-workspace');
|
|
27
|
+
const launchOrderBasketFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'order-basket');
|
|
28
|
+
const launchVisitNotesFormWorkspace = useLaunchWorkspaceRequiringVisit(patientUuid, 'visit-notes-form-workspace');
|
|
29
29
|
const launchVitalsAndBiometricsFormWorkspace = useLaunchWorkspaceRequiringVisit(
|
|
30
|
+
patientUuid,
|
|
30
31
|
'patient-vitals-biometrics-form-workspace',
|
|
31
32
|
);
|
|
32
33
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { CardHeader, EmptyState } from '@openmrs/esm-patient-common-lib';
|
|
3
|
+
import { launchWorkspace } from '@openmrs/esm-framework';
|
|
4
|
+
import { CardHeader, EmptyState, usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
6
5
|
import VisitSummary from './past-visits-components/visit-summary.component';
|
|
7
6
|
import styles from './current-visit-summary.scss';
|
|
8
7
|
|
|
@@ -10,29 +9,23 @@ interface CurrentVisitSummaryProps {
|
|
|
10
9
|
patientUuid: string;
|
|
11
10
|
}
|
|
12
11
|
|
|
12
|
+
/**
|
|
13
|
+
* This extension is not used in the refapp.
|
|
14
|
+
* This extension uses the patient chart store and SHOULD only be mounted within the patient chart
|
|
15
|
+
*/
|
|
13
16
|
const CurrentVisitSummary: React.FC<CurrentVisitSummaryProps> = ({ patientUuid }) => {
|
|
14
17
|
const { t } = useTranslation();
|
|
15
|
-
const {
|
|
18
|
+
const { patientUuid: storePatientUuid, visitContext } = usePatientChartStore(patientUuid);
|
|
16
19
|
|
|
17
|
-
if (
|
|
18
|
-
return
|
|
19
|
-
<InlineLoading
|
|
20
|
-
status="active"
|
|
21
|
-
iconDescription={t('loading', 'Loading')}
|
|
22
|
-
description={t('loadingVisit', 'Loading current visit...')}
|
|
23
|
-
/>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!!error) {
|
|
28
|
-
return <ErrorState headerTitle={t('failedToLoadCurrentVisit', 'Failed loading current visit')} error={error} />;
|
|
20
|
+
if (patientUuid !== storePatientUuid) {
|
|
21
|
+
return null;
|
|
29
22
|
}
|
|
30
23
|
|
|
31
|
-
if (!
|
|
24
|
+
if (!visitContext) {
|
|
32
25
|
return (
|
|
33
26
|
<EmptyState
|
|
34
27
|
headerTitle={t('currentVisit', 'Current visit')}
|
|
35
|
-
displayText={t('
|
|
28
|
+
displayText={t('activeVisits__lower', 'active visits')}
|
|
36
29
|
launchForm={() =>
|
|
37
30
|
launchWorkspace('start-visit-workspace-form', { openedFrom: 'patient-chart-current-visit-summary' })
|
|
38
31
|
}
|
|
@@ -43,10 +36,10 @@ const CurrentVisitSummary: React.FC<CurrentVisitSummaryProps> = ({ patientUuid }
|
|
|
43
36
|
return (
|
|
44
37
|
<div className={styles.container}>
|
|
45
38
|
<CardHeader title={t('currentVisit', 'Current visit')}>
|
|
46
|
-
<span
|
|
39
|
+
<span />
|
|
47
40
|
</CardHeader>
|
|
48
41
|
<div className={styles.visitSummaryCard}>
|
|
49
|
-
<VisitSummary visit={
|
|
42
|
+
<VisitSummary visit={visitContext} patientUuid={patientUuid} />
|
|
50
43
|
</div>
|
|
51
44
|
</div>
|
|
52
45
|
);
|
|
@@ -1,34 +1,52 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import {
|
|
4
|
-
import { waitForLoadingToFinish } from 'tools';
|
|
5
|
-
import
|
|
3
|
+
import { getConfig } from '@openmrs/esm-framework';
|
|
4
|
+
import { mockPatient, waitForLoadingToFinish } from 'tools';
|
|
5
|
+
import { usePatientChartStore } from '@openmrs/esm-patient-common-lib';
|
|
6
|
+
import CurrentVisitSummary from './current-visit-summary.extension';
|
|
6
7
|
|
|
7
8
|
const mockGetConfig = jest.mocked(getConfig);
|
|
8
|
-
const
|
|
9
|
+
const mockUsePatientChartStore = jest.mocked(usePatientChartStore);
|
|
10
|
+
|
|
11
|
+
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
12
|
+
...jest.requireActual('@openmrs/esm-patient-common-lib'),
|
|
13
|
+
usePatientChartStore: jest.fn(),
|
|
14
|
+
}));
|
|
9
15
|
|
|
10
16
|
describe('CurrentVisitSummary', () => {
|
|
11
17
|
test('renders an empty state when there is no active visit', () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
mutate: jest.fn(),
|
|
18
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
19
|
+
patientUuid: mockPatient.id,
|
|
20
|
+
patient: mockPatient,
|
|
21
|
+
visitContext: null,
|
|
22
|
+
mutateVisitContext: null,
|
|
23
|
+
setPatient: jest.fn(),
|
|
24
|
+
setVisitContext: jest.fn(),
|
|
20
25
|
});
|
|
21
|
-
|
|
22
|
-
render(<CurrentVisitSummary patientUuid="some-uuid" />);
|
|
26
|
+
render(<CurrentVisitSummary patientUuid={mockPatient.id} />);
|
|
23
27
|
expect(screen.getByText(/current visit/i)).toBeInTheDocument();
|
|
24
|
-
expect(screen.getByText('There are no active
|
|
28
|
+
expect(screen.getByText('There are no active visits to display for this patient')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('returns null when patientUuid does not match store patientUuid', () => {
|
|
32
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
33
|
+
patientUuid: 'different-patient-id',
|
|
34
|
+
patient: mockPatient,
|
|
35
|
+
visitContext: null,
|
|
36
|
+
mutateVisitContext: null,
|
|
37
|
+
setPatient: jest.fn(),
|
|
38
|
+
setVisitContext: jest.fn(),
|
|
39
|
+
});
|
|
40
|
+
render(<CurrentVisitSummary patientUuid={mockPatient.id} />);
|
|
41
|
+
expect(screen.queryByText(/current visit/i)).not.toBeInTheDocument();
|
|
25
42
|
});
|
|
26
43
|
|
|
27
|
-
test('renders a visit summary when
|
|
44
|
+
test('renders a visit summary when visit context exists', async () => {
|
|
28
45
|
mockGetConfig.mockResolvedValue({ htmlFormEntryForms: [] });
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
47
|
+
patientUuid: mockPatient.id,
|
|
48
|
+
patient: mockPatient,
|
|
49
|
+
visitContext: {
|
|
32
50
|
uuid: 'some-uuid',
|
|
33
51
|
display: 'Visit 1',
|
|
34
52
|
startDatetime: '2021-03-23T10:00:00.000+0300',
|
|
@@ -42,15 +60,17 @@ describe('CurrentVisitSummary', () => {
|
|
|
42
60
|
display: 'Visit Type 1',
|
|
43
61
|
},
|
|
44
62
|
encounters: [],
|
|
63
|
+
patient: {
|
|
64
|
+
uuid: mockPatient.id,
|
|
65
|
+
display: 'Test Patient',
|
|
66
|
+
},
|
|
45
67
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
isValidating: false,
|
|
50
|
-
mutate: jest.fn(),
|
|
68
|
+
mutateVisitContext: null,
|
|
69
|
+
setPatient: jest.fn(),
|
|
70
|
+
setVisitContext: jest.fn(),
|
|
51
71
|
});
|
|
52
72
|
|
|
53
|
-
render(<CurrentVisitSummary patientUuid=
|
|
73
|
+
render(<CurrentVisitSummary patientUuid={mockPatient.id} />);
|
|
54
74
|
|
|
55
75
|
await waitForLoadingToFinish();
|
|
56
76
|
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
type HtmlFormEntryForm,
|
|
45
45
|
launchFormEntryOrHtmlForms,
|
|
46
46
|
invalidateVisitAndEncounterData,
|
|
47
|
+
usePatientChartStore,
|
|
47
48
|
} from '@openmrs/esm-patient-common-lib';
|
|
48
49
|
import { jsonSchemaResourceName } from '../../../../constants';
|
|
49
50
|
import {
|
|
@@ -80,7 +81,7 @@ const EncountersTable: React.FC<EncountersTableProps> = ({
|
|
|
80
81
|
const pageSizes = [10, 20, 30, 40, 50];
|
|
81
82
|
const desktopLayout = isDesktop(useLayoutType());
|
|
82
83
|
const session = useSession();
|
|
83
|
-
const {
|
|
84
|
+
const { mutateVisitContext } = usePatientChartStore(patientUuid);
|
|
84
85
|
const { mutate } = useSWRConfig();
|
|
85
86
|
const responsiveSize = desktopLayout ? 'sm' : 'lg';
|
|
86
87
|
const { data: encounterTypes, isLoading: isLoadingEncounterTypes } = useEncounterTypes();
|
|
@@ -133,7 +134,7 @@ const EncountersTable: React.FC<EncountersTableProps> = ({
|
|
|
133
134
|
deleteEncounter(encounterUuid, abortController)
|
|
134
135
|
.then(() => {
|
|
135
136
|
// Update current visit data for critical components
|
|
136
|
-
|
|
137
|
+
mutateVisitContext?.();
|
|
137
138
|
|
|
138
139
|
// Also invalidate visit history and encounter tables since the encounter was deleted
|
|
139
140
|
invalidateVisitAndEncounterData(mutate, patientUuid);
|
|
@@ -160,7 +161,7 @@ const EncountersTable: React.FC<EncountersTableProps> = ({
|
|
|
160
161
|
},
|
|
161
162
|
});
|
|
162
163
|
},
|
|
163
|
-
[mutate,
|
|
164
|
+
[mutate, mutateVisitContext, patientUuid, t],
|
|
164
165
|
);
|
|
165
166
|
|
|
166
167
|
if (isLoadingEncounterTypes || isLoading) {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { SelectItem, TimePickerSelect, TimePicker, Checkbox } from '@carbon/react';
|
|
2
|
-
import { OpenmrsDatePicker, ResponsiveWrapper, useFeatureFlag,
|
|
2
|
+
import { OpenmrsDatePicker, ResponsiveWrapper, useFeatureFlag, type Visit } from '@openmrs/esm-framework';
|
|
3
3
|
import React, { useEffect, useState } from 'react';
|
|
4
4
|
import { type Control, Controller, useForm } from 'react-hook-form';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import styles from './restrospective-date-time-picker.scss';
|
|
7
|
-
import { useSystemVisitSetting } from '@openmrs/esm-patient-common-lib';
|
|
8
7
|
|
|
9
8
|
type FormValues = {
|
|
10
9
|
retrospectiveDate: Date;
|
|
@@ -13,23 +12,22 @@ type FormValues = {
|
|
|
13
12
|
};
|
|
14
13
|
|
|
15
14
|
type RetrospectiveDateTimePickerProps = {
|
|
16
|
-
|
|
15
|
+
visitContext: Visit;
|
|
17
16
|
control?: Control<FormValues>;
|
|
18
17
|
onChange?: (data: FormValues) => void;
|
|
19
18
|
};
|
|
20
19
|
|
|
21
20
|
const RetrospectiveDateTimePicker = ({
|
|
22
|
-
|
|
21
|
+
visitContext,
|
|
23
22
|
control: propControl,
|
|
24
23
|
onChange,
|
|
25
24
|
}: RetrospectiveDateTimePickerProps) => {
|
|
26
25
|
const { t } = useTranslation();
|
|
27
26
|
const isRdeEnabled = useFeatureFlag('rde');
|
|
28
27
|
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const minDate = currentVisit?.startDatetime;
|
|
28
|
+
const isActiveVisit = !Boolean(visitContext && visitContext.stopDatetime);
|
|
29
|
+
const maxDate = visitContext?.stopDatetime;
|
|
30
|
+
const minDate = visitContext?.startDatetime;
|
|
33
31
|
|
|
34
32
|
const [manuallyEnableDateTimePicker, setManuallyEnableDateTimePicker] = useState<boolean>(false);
|
|
35
33
|
|
|
@@ -1,24 +1,33 @@
|
|
|
1
|
-
import { Button
|
|
2
|
-
import { showModal, useFeatureFlag
|
|
1
|
+
import { Button } from '@carbon/react';
|
|
2
|
+
import { showModal, useFeatureFlag } from '@openmrs/esm-framework';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import styles from './visit-context-header.scss';
|
|
7
7
|
import VisitContextInfo from './visit-context-info.component';
|
|
8
|
-
import { useSystemVisitSetting } from '@openmrs/esm-patient-common-lib';
|
|
8
|
+
import { usePatientChartStore, useSystemVisitSetting } from '@openmrs/esm-patient-common-lib';
|
|
9
9
|
|
|
10
10
|
interface VisitContextHeaderProps {
|
|
11
11
|
patientUuid: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The visit context header displays the currently select visit context in the patient chart store.
|
|
16
|
+
* When creating encounters (ex: visit notes, order entry, vitals), they are associated
|
|
17
|
+
* with the visit in context.
|
|
18
|
+
*
|
|
19
|
+
* This extension uses the patient chart store and should only be shown within the patient chart.
|
|
20
|
+
* It does not render when mounted outside the patient chart.
|
|
21
|
+
*/
|
|
14
22
|
const VisitContextHeader: React.FC<VisitContextHeaderProps> = ({ patientUuid }) => {
|
|
15
23
|
const { t } = useTranslation();
|
|
16
24
|
const { systemVisitEnabled } = useSystemVisitSetting();
|
|
17
25
|
const isRdeEnabled = useFeatureFlag('rde');
|
|
18
|
-
const showVisitContextHeader = systemVisitEnabled && isRdeEnabled;
|
|
19
26
|
|
|
20
|
-
const {
|
|
21
|
-
const isActiveVisit =
|
|
27
|
+
const { visitContext } = usePatientChartStore(patientUuid);
|
|
28
|
+
const isActiveVisit = Boolean(visitContext && !visitContext.stopDatetime);
|
|
29
|
+
|
|
30
|
+
const showVisitContextHeader = systemVisitEnabled && isRdeEnabled && visitContext;
|
|
22
31
|
|
|
23
32
|
const openVisitSwitcherModal = () => {
|
|
24
33
|
const dispose = showModal('visit-context-switcher', {
|
|
@@ -31,26 +40,19 @@ const VisitContextHeader: React.FC<VisitContextHeaderProps> = ({ patientUuid })
|
|
|
31
40
|
if (!showVisitContextHeader) {
|
|
32
41
|
return null;
|
|
33
42
|
}
|
|
34
|
-
if (isLoading) {
|
|
35
|
-
return (
|
|
36
|
-
<div className={styles.visitContextHeader}>
|
|
37
|
-
<Loading small />
|
|
38
|
-
</div>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
43
|
return (
|
|
42
44
|
<div
|
|
43
45
|
className={classNames(styles.visitContextHeader, isActiveVisit ? styles.activeVisit : styles.retroactiveVisit)}
|
|
44
46
|
>
|
|
45
47
|
<div className={styles.addingTo}>{t('addingToVisit', 'Adding to:')}</div>
|
|
46
|
-
<div className={styles.visitType}>{
|
|
48
|
+
<div className={styles.visitType}>{visitContext.visitType?.display}</div>
|
|
47
49
|
<div className={styles.changeVisitButton}>
|
|
48
50
|
<Button kind="ghost" size="sm" onClick={openVisitSwitcherModal}>
|
|
49
51
|
{t('change', 'Change')}
|
|
50
52
|
</Button>
|
|
51
53
|
</div>
|
|
52
54
|
<div className={styles.visitInfo}>
|
|
53
|
-
<VisitContextInfo visit={
|
|
55
|
+
<VisitContextInfo visit={visitContext} />
|
|
54
56
|
</div>
|
|
55
57
|
</div>
|
|
56
58
|
);
|
|
@@ -1,51 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useSystemVisitSetting } from '@openmrs/esm-patient-common-lib';
|
|
1
|
+
import { usePatientChartStore, useSystemVisitSetting } from '@openmrs/esm-patient-common-lib';
|
|
3
2
|
import { render, screen } from '@testing-library/react';
|
|
4
3
|
import { mockCurrentVisit } from '__mocks__';
|
|
5
4
|
import React from 'react';
|
|
6
|
-
import VisitContextHeader from './visit-context-header.
|
|
5
|
+
import VisitContextHeader from './visit-context-header.extension';
|
|
6
|
+
import { mockPatient } from 'tools';
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
errorFetchingSystemVisitSetting: null,
|
|
11
|
-
isLoadingSystemVisitSetting: false,
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
jest.mocked(useVisitContextStore).mockReturnValue({
|
|
15
|
-
manuallySetVisitUuid: null,
|
|
16
|
-
mutateVisitCallbacks: {},
|
|
17
|
-
patientUuid: null,
|
|
18
|
-
setVisitContext: jest.fn(),
|
|
19
|
-
mutateVisit: jest.fn(),
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
jest.mocked(useVisit).mockReturnValue({
|
|
23
|
-
currentVisit: mockCurrentVisit,
|
|
24
|
-
error: null,
|
|
25
|
-
mutate: jest.fn(),
|
|
26
|
-
isValidating: false,
|
|
27
|
-
activeVisit: null,
|
|
28
|
-
currentVisitIsRetrospective: false,
|
|
29
|
-
isLoading: false,
|
|
30
|
-
});
|
|
8
|
+
const mockUsePatientChartStore = jest.mocked(usePatientChartStore);
|
|
9
|
+
const mockUseSystemVisitSetting = jest.mocked(useSystemVisitSetting);
|
|
31
10
|
|
|
32
|
-
jest.mock('@openmrs/esm-patient-common-lib
|
|
33
|
-
|
|
11
|
+
jest.mock('@openmrs/esm-patient-common-lib', () => ({
|
|
12
|
+
usePatientChartStore: jest.fn(),
|
|
13
|
+
useSystemVisitSetting: jest.fn(),
|
|
34
14
|
}));
|
|
35
15
|
|
|
36
16
|
describe('VisitContextHeader', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
mockUseSystemVisitSetting.mockReturnValue({
|
|
19
|
+
systemVisitEnabled: true,
|
|
20
|
+
errorFetchingSystemVisitSetting: null,
|
|
21
|
+
isLoadingSystemVisitSetting: false,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
37
25
|
it('should not show header if system does not support visits', () => {
|
|
38
26
|
mockUseSystemVisitSetting.mockReturnValueOnce({
|
|
39
27
|
systemVisitEnabled: false,
|
|
40
28
|
errorFetchingSystemVisitSetting: null,
|
|
41
29
|
isLoadingSystemVisitSetting: false,
|
|
42
30
|
});
|
|
31
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
32
|
+
patientUuid: mockPatient.id,
|
|
33
|
+
patient: mockPatient,
|
|
34
|
+
visitContext: null,
|
|
35
|
+
mutateVisitContext: null,
|
|
36
|
+
setPatient: jest.fn(),
|
|
37
|
+
setVisitContext: jest.fn(),
|
|
38
|
+
});
|
|
39
|
+
|
|
43
40
|
render(<VisitContextHeader patientUuid="some-uuid" />);
|
|
44
41
|
expect(screen.queryByText('Adding to')).not.toBeInTheDocument();
|
|
45
42
|
});
|
|
46
43
|
|
|
47
44
|
it('should show the current visit', () => {
|
|
48
|
-
|
|
45
|
+
mockUsePatientChartStore.mockReturnValue({
|
|
46
|
+
patientUuid: mockPatient.id,
|
|
47
|
+
patient: mockPatient,
|
|
48
|
+
visitContext: mockCurrentVisit,
|
|
49
|
+
mutateVisitContext: null,
|
|
50
|
+
setPatient: jest.fn(),
|
|
51
|
+
setVisitContext: jest.fn(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
render(<VisitContextHeader patientUuid={mockPatient.id} />);
|
|
49
55
|
expect(screen.getByText(/Adding to/i)).toBeInTheDocument();
|
|
50
56
|
expect(screen.getByText(mockCurrentVisit.visitType.display)).toBeInTheDocument();
|
|
51
57
|
});
|