@openmrs/esm-patient-common-lib 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-patient-common-lib",
3
- "version": "11.3.1-pre.9294",
3
+ "version": "11.3.1-pre.9296",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Library for common patient chart components",
6
6
  "browser": "dist/openmrs-esm-patient-common-lib.js",
@@ -5,7 +5,7 @@ import {
5
5
  queueSynchronizationItem,
6
6
  useConnectivity,
7
7
  useSession,
8
- useVisit,
8
+ type useVisit,
9
9
  type Visit,
10
10
  } from '@openmrs/esm-framework';
11
11
  import { useEffect, useState } from 'react';
@@ -23,20 +23,6 @@ export interface OfflineVisit extends NewVisitPayload {
23
23
  uuid: string;
24
24
  }
25
25
 
26
- /**
27
- * Similar to {@link useVisit}, returns the given patient's active visit, but also considers
28
- * offline visits created by the patient chart while offline.
29
- * @param patientUuid The UUID of the patient.
30
- */
31
- export function useVisitOrOfflineVisit(patientUuid: string) {
32
- const isOnline = useConnectivity();
33
-
34
- const onlineVisit = useVisit(patientUuid);
35
- const offlineVisit = useOfflineVisit(patientUuid);
36
-
37
- return isOnline ? onlineVisit : offlineVisit;
38
- }
39
-
40
26
  /**
41
27
  * Returns the patient's current offline visit.
42
28
  * @param patientUuid The UUID of the patient.
@@ -132,7 +118,7 @@ export async function createOfflineVisitForPatient(
132
118
  };
133
119
 
134
120
  await queueSynchronizationItem(visitSyncType, offlineVisit, descriptor);
135
- return offlineVisit;
121
+ return offlineVisitToVisit(offlineVisit);
136
122
  }
137
123
 
138
124
  function offlineVisitToVisit(offlineVisit: OfflineVisit): Visit {
@@ -2,6 +2,7 @@ import { act, renderHook } from '@testing-library/react';
2
2
  import { useOrderBasket } from './useOrderBasket';
3
3
  import { type OrderBasketItem, type PostDataPrepFunction } from './types';
4
4
  import { _resetOrderBasketStore } from './store';
5
+ import { mockFhirPatient } from '__mocks__';
5
6
 
6
7
  const mockDrugOrderBasketItem = {
7
8
  action: 'NEW',
@@ -19,7 +20,9 @@ describe('useOrderBasket', () => {
19
20
  });
20
21
 
21
22
  it('returns the correct list of orders given a grouping', () => {
22
- const { result } = renderHook(() => useOrderBasket('medications', ((x) => x) as unknown as PostDataPrepFunction));
23
+ const { result } = renderHook(() =>
24
+ useOrderBasket(mockFhirPatient, 'medications', ((x) => x) as unknown as PostDataPrepFunction),
25
+ );
23
26
  expect(result.current.orders).toEqual([]);
24
27
  act(() => {
25
28
  result.current.setOrders([mockDrugOrderBasketItem]);
@@ -29,10 +32,10 @@ describe('useOrderBasket', () => {
29
32
 
30
33
  it('can modify items in one grouping without affecting the other', () => {
31
34
  const { result: drugResult } = renderHook(() =>
32
- useOrderBasket('medications', ((x) => x) as unknown as PostDataPrepFunction),
35
+ useOrderBasket(mockFhirPatient, 'medications', ((x) => x) as unknown as PostDataPrepFunction),
33
36
  );
34
37
  const { result: labResult } = renderHook(() =>
35
- useOrderBasket('labs', ((x) => x) as unknown as PostDataPrepFunction),
38
+ useOrderBasket(mockFhirPatient, 'labs', ((x) => x) as unknown as PostDataPrepFunction),
36
39
  );
37
40
  expect(drugResult.current.orders).toEqual([]);
38
41
  expect(labResult.current.orders).toEqual([]);
@@ -1,16 +1,15 @@
1
1
  import { type Actions, useStoreWithActions } from '@openmrs/esm-framework';
2
2
  import type { OrderBasketItem, PostDataPrepFunction } from './types';
3
- import { getPatientUuidFromStore } from '../store/patient-chart-store';
4
3
  import { useEffect } from 'react';
5
4
  import { type OrderBasketStore, orderBasketStore } from './store';
6
5
 
7
6
  const orderBasketStoreActions = {
8
7
  setOrderBasketItems(
9
8
  state: OrderBasketStore,
9
+ patientUuid: string,
10
10
  grouping: string,
11
11
  value: Array<OrderBasketItem> | (() => Array<OrderBasketItem>),
12
12
  ) {
13
- const patientUuid = getPatientUuidFromStore();
14
13
  if (!Object.keys(state.postDataPrepFunctions).includes(grouping)) {
15
14
  console.warn(`Programming error: You must register a postDataPrepFunction for grouping ${grouping} `);
16
15
  }
@@ -34,8 +33,11 @@ const orderBasketStoreActions = {
34
33
  },
35
34
  } satisfies Actions<OrderBasketStore>;
36
35
 
37
- function getOrderItems(items: OrderBasketStore['items'], grouping?: string | null): Array<OrderBasketItem> {
38
- const patientUuid = getPatientUuidFromStore();
36
+ function getOrderItems(
37
+ patientUuid: string,
38
+ items: OrderBasketStore['items'],
39
+ grouping?: string | null,
40
+ ): Array<OrderBasketItem> {
39
41
  const patientItems = items?.[patientUuid] ?? {};
40
42
  return grouping ? patientItems[grouping] ?? [] : Object.values(patientItems).flat();
41
43
  }
@@ -44,9 +46,8 @@ export interface ClearOrdersOptions {
44
46
  exceptThoseMatching: (order: OrderBasketItem) => boolean;
45
47
  }
46
48
 
47
- function clearOrders(options?: ClearOrdersOptions) {
49
+ function clearOrders(patientUuid: string, options?: ClearOrdersOptions) {
48
50
  const exceptThoseMatchingFcn = options?.exceptThoseMatching ?? (() => false);
49
- const patientUuid = getPatientUuidFromStore();
50
51
  const items = orderBasketStore.getState().items;
51
52
  const patientItems = items[patientUuid] ?? {};
52
53
  const newPatientItems = Object.fromEntries(
@@ -77,13 +78,18 @@ type UseOrderBasketReturn<T, U> = {
77
78
  * A PostDataPrepFunction must be provided for each grouping, but does not necessarily have to be provided
78
79
  * in every usage of useOrderBasket with a grouping key.
79
80
  */
80
- export function useOrderBasket<T extends OrderBasketItem>(): UseOrderBasketReturn<T, void>;
81
- export function useOrderBasket<T extends OrderBasketItem>(grouping: string): UseOrderBasketReturn<T, string>;
81
+ export function useOrderBasket<T extends OrderBasketItem>(patient: fhir.Patient): UseOrderBasketReturn<T, void>;
82
82
  export function useOrderBasket<T extends OrderBasketItem>(
83
+ patient: fhir.Patient,
84
+ grouping: string,
85
+ ): UseOrderBasketReturn<T, string>;
86
+ export function useOrderBasket<T extends OrderBasketItem>(
87
+ patient: fhir.Patient,
83
88
  grouping: string,
84
89
  postDataPrepFunction: PostDataPrepFunction,
85
90
  ): UseOrderBasketReturn<T, string>;
86
91
  export function useOrderBasket<T extends OrderBasketItem>(
92
+ patient: fhir.Patient,
87
93
  grouping?: string | null,
88
94
  postDataPrepFunction?: PostDataPrepFunction,
89
95
  ): UseOrderBasketReturn<T, string | void> {
@@ -91,7 +97,7 @@ export function useOrderBasket<T extends OrderBasketItem>(
91
97
  orderBasketStore,
92
98
  orderBasketStoreActions,
93
99
  );
94
- const orders = getOrderItems(items, grouping);
100
+ const orders = getOrderItems(patient.id, items, grouping);
95
101
 
96
102
  useEffect(() => {
97
103
  if (postDataPrepFunction && !postDataPrepFunctions[grouping]) {
@@ -99,15 +105,19 @@ export function useOrderBasket<T extends OrderBasketItem>(
99
105
  }
100
106
  }, [postDataPrepFunction, grouping, postDataPrepFunctions, setPostDataPrepFunctionForGrouping]);
101
107
 
108
+ const clearOrdersForPatient = (options?: ClearOrdersOptions) => {
109
+ clearOrders(patient.id, options);
110
+ };
111
+
102
112
  if (typeof grouping === 'string') {
103
113
  const setOrders = (value: Array<T> | (() => Array<T>)) => {
104
- return setOrderBasketItems(grouping, value);
114
+ return setOrderBasketItems(patient.id, grouping, value);
105
115
  };
106
- return { orders, clearOrders, setOrders } as UseOrderBasketReturn<T, string>;
116
+ return { orders, clearOrders: clearOrdersForPatient, setOrders } as UseOrderBasketReturn<T, string>;
107
117
  } else {
108
118
  const setOrders = (groupingKey: string, value: Array<T> | (() => Array<T>)) => {
109
- setOrderBasketItems(groupingKey, value);
119
+ setOrderBasketItems(patient.id, groupingKey, value);
110
120
  };
111
- return { orders, clearOrders, setOrders } as UseOrderBasketReturn<T, void>;
121
+ return { orders, clearOrders: clearOrdersForPatient, setOrders } as UseOrderBasketReturn<T, void>;
112
122
  }
113
123
  }
@@ -1,40 +1,54 @@
1
- import { createGlobalStore, useStore } from '@openmrs/esm-framework';
1
+ import { type Actions, createGlobalStore, useStoreWithActions, type Visit } from '@openmrs/esm-framework';
2
2
 
3
3
  export interface PatientChartStore {
4
- patientUuid?: string;
5
- patient?: fhir.Patient;
4
+ patientUuid: string;
5
+ patient: fhir.Patient;
6
+ visitContext: Visit;
7
+ mutateVisitContext: () => void;
6
8
  }
7
9
 
8
10
  const patientChartStoreName = 'patient-chart-global-store';
9
11
 
10
- const patientChartStore = createGlobalStore<PatientChartStore>(patientChartStoreName, {});
12
+ const patientChartStore = createGlobalStore<PatientChartStore>(patientChartStoreName, {
13
+ patientUuid: null,
14
+ patient: null,
15
+ visitContext: null,
16
+ mutateVisitContext: null,
17
+ });
11
18
 
12
- /**
13
- * This function returns the patient chart store.
14
- *
15
- * The patient chart store is used to store all global variables used in the patient chart.
16
- * In the recent requirements, patient chart is now not only bound with `/patient/{patientUuid}/` path.
17
- */
18
- export function getPatientChartStore() {
19
- return patientChartStore;
20
- }
21
-
22
- export function usePatientChartStore() {
23
- return useStore(patientChartStore);
24
- }
19
+ const patientChartStoreActions = {
20
+ setPatient(_, patient: fhir.Patient) {
21
+ return { patient, patientUuid: patient?.id ?? null };
22
+ },
23
+ setVisitContext(_, visitContext: Visit, mutateVisitContext: () => void) {
24
+ return { visitContext, mutateVisitContext };
25
+ },
26
+ } satisfies Actions<PatientChartStore>;
25
27
 
26
28
  /**
27
- * This function will get the patient UUID from either URL, or will look into the patient chart store.
28
- * @returns {string} patientUuid
29
- */
30
- export function getPatientUuidFromStore(): string | undefined {
31
- return patientChartStore.getState().patientUuid;
32
- }
33
-
34
- /**
35
- * This function will get the current patient from the store
36
- * @returns {string} patientUuid
29
+ * Hook to access the values and sets of the patient chart store.
30
+ * Note: This hooks SHOULD only be used by components inside the patient chart app.
31
+ *
32
+ * Workspaces / extensions that can be mounted by other apps (ex: the start visit form in the queue's app,
33
+ * the clinical forms workspace in the ward app)
34
+ * should have the patient / visitContext explicitly passed in as props.
35
+ *
36
+ * As a safety feature, this hook requires the patientUuid as the input, and only
37
+ * returns the actual store values if input patientUuid matches that in the store.
37
38
  */
38
- export function getPatientFromStore(): fhir.Patient | undefined {
39
- return patientChartStore.getState().patient;
39
+ export function usePatientChartStore(patientUuid: string) {
40
+ const store = useStoreWithActions(patientChartStore, patientChartStoreActions);
41
+ if (store.patientUuid === patientUuid) {
42
+ return store;
43
+ } else {
44
+ const fakeStore: typeof store = {
45
+ ...store,
46
+ mutateVisitContext: null,
47
+ setVisitContext: () => {},
48
+ patient: null,
49
+ patientUuid: null,
50
+ visitContext: null,
51
+ };
52
+ return fakeStore;
53
+ }
40
54
  }
@@ -100,3 +100,12 @@ export function invalidateVisitAndEncounterData(mutate: KeyedMutator<unknown>, p
100
100
  invalidateVisitHistory(mutate, patientUuid);
101
101
  invalidatePatientEncounters(mutate, patientUuid);
102
102
  }
103
+
104
+ /**
105
+ * Invalidates a visit fetched by URL /visit/<uuid>
106
+ * @param mutate - SWR mutate function from useSWRConfig()
107
+ * @param visitUuid
108
+ */
109
+ export function invalidateVisitByUuid(mutate: KeyedMutator<unknown>, visitUuid: string) {
110
+ mutate(new RegExp(`${restBaseUrl}/visit/${visitUuid}`));
111
+ }
@@ -1,6 +1,7 @@
1
1
  import { useCallback } from 'react';
2
2
  import { useSWRConfig } from 'swr';
3
3
  import { useVisit, type Visit, restBaseUrl } from '@openmrs/esm-framework';
4
+ import { usePatientChartStore } from '../store/patient-chart-store';
4
5
 
5
6
  export interface VisitMutationOptions {
6
7
  encounters?: boolean;
@@ -19,7 +20,7 @@ export interface VisitMutationOptions {
19
20
  */
20
21
  export function useOptimisticVisitMutations(patientUuid: string) {
21
22
  const { mutate } = useSWRConfig();
22
- const { currentVisit } = useVisit(patientUuid);
23
+ const { visitContext, mutateVisitContext } = usePatientChartStore(patientUuid);
23
24
 
24
25
  /**
25
26
  * Optimistically updates visit data in SWR caches without triggering network requests.
@@ -28,15 +29,8 @@ export function useOptimisticVisitMutations(patientUuid: string) {
28
29
  const updateVisitOptimistically = useCallback(
29
30
  (visitUuid: string, updates: Partial<Visit>) => {
30
31
  // Update current visit SWR cache if it matches
31
- if (currentVisit?.uuid === visitUuid) {
32
- mutate(
33
- `${restBaseUrl}/visit?patient=${patientUuid}&v=custom`,
34
- (current: any) => ({
35
- ...current,
36
- data: { ...current?.data, ...updates },
37
- }),
38
- false, // Don't revalidate
39
- );
32
+ if (visitContext?.uuid === visitUuid) {
33
+ mutateVisitContext();
40
34
  }
41
35
 
42
36
  // Update visit lists across all hooks using regex pattern matching
@@ -59,7 +53,7 @@ export function useOptimisticVisitMutations(patientUuid: string) {
59
53
  false, // Don't revalidate
60
54
  );
61
55
  },
62
- [currentVisit, mutate, patientUuid],
56
+ [visitContext, mutateVisitContext, mutate, patientUuid],
63
57
  );
64
58
 
65
59
  /**
@@ -87,11 +81,11 @@ export function useOptimisticVisitMutations(patientUuid: string) {
87
81
  );
88
82
 
89
83
  // If deleted visit was current, revalidate current visit to get new state
90
- if (currentVisit?.uuid === visitUuid) {
91
- mutate(`${restBaseUrl}/visit?patient=${patientUuid}&v=custom`);
84
+ if (visitContext?.uuid === visitUuid) {
85
+ mutateVisitContext();
92
86
  }
93
87
  },
94
- [currentVisit, mutate, patientUuid],
88
+ [visitContext, mutateVisitContext, mutate, patientUuid],
95
89
  );
96
90
 
97
91
  /**
package/src/workspaces.ts CHANGED
@@ -5,15 +5,17 @@ import {
5
5
  navigateAndLaunchWorkspace,
6
6
  showModal,
7
7
  useFeatureFlag,
8
+ type Visit,
8
9
  } from '@openmrs/esm-framework';
9
10
  import { launchStartVisitPrompt } from './launchStartVisitPrompt';
10
11
  import { usePatientChartStore } from './store/patient-chart-store';
11
12
  import { useSystemVisitSetting } from './useSystemVisitSetting';
12
- import { useVisitOrOfflineVisit } from './offline/visit';
13
13
 
14
14
  export interface DefaultPatientWorkspaceProps extends DefaultWorkspaceProps {
15
15
  patient: fhir.Patient;
16
16
  patientUuid: string;
17
+ visitContext: Visit;
18
+ mutateVisitContext: () => void;
17
19
  }
18
20
 
19
21
  export function launchPatientChartWithWorkspaceOpen({
@@ -35,15 +37,14 @@ export function launchPatientChartWithWorkspaceOpen({
35
37
  });
36
38
  }
37
39
 
38
- export function useLaunchWorkspaceRequiringVisit<T extends object>(workspaceName: string) {
39
- const { patientUuid } = usePatientChartStore();
40
+ export function useLaunchWorkspaceRequiringVisit<T extends object>(patientUuid: string, workspaceName: string) {
41
+ const { visitContext } = usePatientChartStore(patientUuid);
40
42
  const { systemVisitEnabled } = useSystemVisitSetting();
41
- const { currentVisit } = useVisitOrOfflineVisit(patientUuid);
42
43
  const isRdeEnabled = useFeatureFlag('rde');
43
44
 
44
45
  const launchPatientWorkspaceCb = useCallback(
45
46
  (additionalProps?: T) => {
46
- if (!systemVisitEnabled || currentVisit) {
47
+ if (!systemVisitEnabled || visitContext) {
47
48
  launchWorkspace(workspaceName, additionalProps);
48
49
  } else {
49
50
  if (isRdeEnabled) {
@@ -58,7 +59,7 @@ export function useLaunchWorkspaceRequiringVisit<T extends object>(workspaceName
58
59
  }
59
60
  }
60
61
  },
61
- [currentVisit, systemVisitEnabled, workspaceName, isRdeEnabled, patientUuid],
62
+ [visitContext, systemVisitEnabled, workspaceName, isRdeEnabled, patientUuid],
62
63
  );
63
64
  return launchPatientWorkspaceCb;
64
65
  }