@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.
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
@@ -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
  );
@@ -1,34 +1,52 @@
1
1
  import React from 'react';
2
2
  import { render, screen } from '@testing-library/react';
3
- import { useVisit, getConfig } from '@openmrs/esm-framework';
4
- import { waitForLoadingToFinish } from 'tools';
5
- import CurrentVisitSummary from './current-visit-summary.component';
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 mockUseVisits = jest.mocked(useVisit);
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
- mockUseVisits.mockReturnValueOnce({
13
- activeVisit: null,
14
- currentVisit: null,
15
- currentVisitIsRetrospective: false,
16
- error: null,
17
- isLoading: false,
18
- isValidating: false,
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 visit to display for this patient')).toBeInTheDocument();
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 for the active visit', async () => {
44
+ test('renders a visit summary when visit context exists', async () => {
28
45
  mockGetConfig.mockResolvedValue({ htmlFormEntryForms: [] });
29
- mockUseVisits.mockReturnValueOnce({
30
- activeVisit: null,
31
- currentVisit: {
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
- currentVisitIsRetrospective: false,
47
- error: null,
48
- isLoading: false,
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="some-uuid" />);
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 { mutate: mutateCurrentVisit } = useVisit(patientUuid);
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
- mutateCurrentVisit();
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, mutateCurrentVisit, patientUuid, t],
164
+ [mutate, mutateVisitContext, patientUuid, t],
164
165
  );
165
166
 
166
167
  if (isLoadingEncounterTypes || isLoading) {
@@ -8,7 +8,6 @@ import {
8
8
  type Encounter,
9
9
  type EncounterType,
10
10
  type Obs,
11
- type OpenmrsResource,
12
11
  useOpenmrsFetchAll,
13
12
  useOpenmrsPagination,
14
13
  } from '@openmrs/esm-framework';
@@ -1,10 +1,9 @@
1
1
  import { SelectItem, TimePickerSelect, TimePicker, Checkbox } from '@carbon/react';
2
- import { OpenmrsDatePicker, ResponsiveWrapper, useFeatureFlag, useVisit } from '@openmrs/esm-framework';
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
- patientUuid: string;
15
+ visitContext: Visit;
17
16
  control?: Control<FormValues>;
18
17
  onChange?: (data: FormValues) => void;
19
18
  };
20
19
 
21
20
  const RetrospectiveDateTimePicker = ({
22
- patientUuid,
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 { currentVisit } = useVisit(patientUuid);
30
- const isActiveVisit = !Boolean(currentVisit && currentVisit.stopDatetime);
31
- const maxDate = currentVisit?.stopDatetime;
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, Loading } from '@carbon/react';
2
- import { showModal, useFeatureFlag, useVisit } from '@openmrs/esm-framework';
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 { currentVisit, isLoading } = useVisit(showVisitContextHeader ? patientUuid : null);
21
- const isActiveVisit = !Boolean(currentVisit && currentVisit.stopDatetime);
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}>{currentVisit.visitType.display}</div>
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={currentVisit} />
55
+ <VisitContextInfo visit={visitContext} />
54
56
  </div>
55
57
  </div>
56
58
  );
@@ -1,51 +1,57 @@
1
- import { useVisit, useVisitContextStore } from '@openmrs/esm-framework';
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.component';
5
+ import VisitContextHeader from './visit-context-header.extension';
6
+ import { mockPatient } from 'tools';
7
7
 
8
- const mockUseSystemVisitSetting = jest.fn(useSystemVisitSetting).mockReturnValue({
9
- systemVisitEnabled: true,
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/src/useSystemVisitSetting', () => ({
33
- useSystemVisitSetting: () => mockUseSystemVisitSetting(),
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
- render(<VisitContextHeader patientUuid="some-uuid" />);
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
  });