@openmrs/esm-patient-orders-app 11.3.1-pre.9641 → 11.3.1-pre.9645

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 (42) hide show
  1. package/.turbo/turbo-build.log +13 -13
  2. package/dist/1253.js +1 -1
  3. package/dist/1253.js.map +1 -1
  4. package/dist/3685.js +1 -1
  5. package/dist/375.js +1 -1
  6. package/dist/375.js.map +1 -1
  7. package/dist/4300.js +1 -1
  8. package/dist/4341.js +1 -1
  9. package/dist/4341.js.map +1 -1
  10. package/dist/4558.js +1 -1
  11. package/dist/4558.js.map +1 -1
  12. package/dist/5937.js +1 -1
  13. package/dist/5937.js.map +1 -1
  14. package/dist/6411.js +1 -1
  15. package/dist/6542.js +1 -1
  16. package/dist/701.js +2 -0
  17. package/dist/701.js.map +1 -0
  18. package/dist/7160.js +1 -1
  19. package/dist/8154.js +1 -1
  20. package/dist/8376.js +1 -1
  21. package/dist/8376.js.map +1 -1
  22. package/dist/main.js +1 -1
  23. package/dist/main.js.map +1 -1
  24. package/dist/openmrs-esm-patient-orders-app.js +1 -1
  25. package/dist/openmrs-esm-patient-orders-app.js.buildmanifest.json +61 -61
  26. package/dist/openmrs-esm-patient-orders-app.js.map +1 -1
  27. package/dist/routes.json +1 -1
  28. package/package.json +2 -2
  29. package/src/api/api.ts +43 -46
  30. package/src/components/order-details-table.component.tsx +48 -68
  31. package/src/config-schema.ts +7 -0
  32. package/src/order-basket/exported-order-basket.workspace.tsx +2 -0
  33. package/src/order-basket/general-order-type/add-general-order/search-results.component.tsx +3 -11
  34. package/src/order-basket/general-order-type/general-order-form/general-order-form.component.tsx +5 -5
  35. package/src/order-basket/general-order-type/resources.ts +15 -15
  36. package/src/order-basket/order-basket.component.tsx +122 -36
  37. package/src/order-basket/order-basket.scss +15 -0
  38. package/src/utils/index.ts +1 -6
  39. package/translations/en.json +5 -0
  40. package/dist/7657.js +0 -2
  41. package/dist/7657.js.map +0 -1
  42. /package/dist/{7657.js.LICENSE.txt → 701.js.LICENSE.txt} +0 -0
@@ -1,10 +1,11 @@
1
- import React, { useCallback, useMemo, useState } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import classNames from 'classnames';
3
3
  import { useTranslation } from 'react-i18next';
4
- import { Button, ButtonSet, InlineLoading, InlineNotification } from '@carbon/react';
4
+ import { Button, ButtonSet, ComboBox, FormLabel, InlineLoading, InlineNotification, Stack } from '@carbon/react';
5
5
  import { useSWRConfig } from 'swr';
6
6
  import {
7
7
  ExtensionSlot,
8
+ LocationPicker,
8
9
  useConfig,
9
10
  useLayoutType,
10
11
  useSession,
@@ -14,6 +15,7 @@ import {
14
15
  } from '@openmrs/esm-framework';
15
16
  import {
16
17
  invalidateVisitAndEncounterData,
18
+ type Order,
17
19
  type OrderBasketExtensionProps,
18
20
  type OrderBasketItem,
19
21
  postOrders,
@@ -23,7 +25,7 @@ import {
23
25
  useOrderBasket,
24
26
  } from '@openmrs/esm-patient-common-lib';
25
27
  import { type ConfigObject } from '../config-schema';
26
- import { useOrderEncounter } from '../api/api';
28
+ import { type Provider, useOrderEncounterForSystemWithVisitDisabled, useProviders } from '../api/api';
27
29
  import GeneralOrderPanel from './general-order-type/general-order-panel.component';
28
30
  import styles from './order-basket.scss';
29
31
 
@@ -34,6 +36,7 @@ interface OrderBasketProps {
34
36
  mutateVisitContext: () => void;
35
37
  closeWorkspace: Workspace2DefinitionProps['closeWorkspace'];
36
38
  orderBasketExtensionProps: OrderBasketExtensionProps;
39
+ onOrderBasketSubmitted?: (encounterUuid: string, postedOrders: Array<Order>) => void;
37
40
  }
38
41
 
39
42
  const OrderBasket: React.FC<OrderBasketProps> = ({
@@ -43,38 +46,69 @@ const OrderBasket: React.FC<OrderBasketProps> = ({
43
46
  mutateVisitContext,
44
47
  closeWorkspace,
45
48
  orderBasketExtensionProps,
49
+ onOrderBasketSubmitted,
46
50
  }) => {
47
51
  const { t } = useTranslation();
48
52
  const isTablet = useLayoutType() === 'tablet';
49
- const config = useConfig<ConfigObject>();
50
- const session = useSession();
53
+ const { orderTypes, orderEncounterType, ordererProviderRoles } = useConfig<ConfigObject>();
54
+ const {
55
+ currentProvider: _currentProvider,
56
+ sessionLocation,
57
+ user: { person },
58
+ } = useSession();
59
+ const currentProvider: Provider = useMemo(() => ({ ..._currentProvider, person }), [_currentProvider, person]);
51
60
  const { orders, clearOrders } = useOrderBasket(patient);
52
61
  const [ordersWithErrors, setOrdersWithErrors] = useState<OrderBasketItem[]>([]);
53
62
  const {
54
63
  visitRequired,
55
64
  isLoading: isLoadingEncounterUuid,
56
- encounterUuid,
65
+ encounterUuid: orderEncounterUuid,
57
66
  error: errorFetchingEncounterUuid,
58
67
  mutate: mutateEncounterUuid,
59
- } = useOrderEncounter(patientUuid, visitContext, mutateVisitContext, config.orderEncounterType);
68
+ } = useOrderEncounterForSystemWithVisitDisabled(patientUuid);
60
69
  const [isSavingOrders, setIsSavingOrders] = useState(false);
61
70
  const [creatingEncounterError, setCreatingEncounterError] = useState('');
62
71
  const { mutate: mutateOrders } = useMutatePatientOrders(patientUuid);
63
72
  const { mutate } = useSWRConfig();
64
73
 
74
+ const [orderLocationUuid, setOrderLocationUuid] = useState(sessionLocation.uuid);
75
+
76
+ const allowSelectingOrderer = ordererProviderRoles?.length > 0;
77
+ const {
78
+ providers,
79
+ isLoading: isLoadingProviders,
80
+ error: errorLoadingProviders,
81
+ } = useProviders(allowSelectingOrderer ? ordererProviderRoles : null);
82
+
83
+ // If configured to allow selecting providers, we wait till we fetched the allowable providers
84
+ // before setting the orderer. If not configured, we assume the current user is the orderer.
85
+ const [orderer, setOrderer] = useState<Provider>(allowSelectingOrderer ? null : currentProvider);
86
+
87
+ useEffect(() => {
88
+ if (allowSelectingOrderer && providers?.length > 0) {
89
+ // default orderer to current user if they have the right provider roles
90
+ if (providers.some((p) => p.uuid === currentProvider.uuid)) {
91
+ setOrderer(currentProvider);
92
+ }
93
+ }
94
+ }, [allowSelectingOrderer, providers, currentProvider]);
95
+
65
96
  const handleSave = useCallback(async () => {
66
97
  const abortController = new AbortController();
67
98
  setCreatingEncounterError('');
68
- let orderEncounterUuid = encounterUuid;
99
+
69
100
  setIsSavingOrders(true);
70
- // If there's no encounter present, create an encounter along with the orders.
101
+ // orderEncounterUuid should only be preset if the system does not support visits, and the user has an order encounter today.
102
+ // If orderEncounterUuid is not present, then create an encounter along with the orders.
103
+ // If orderEncounterUuid is present, then just post the orders to that encounter.
71
104
  if (!orderEncounterUuid) {
72
105
  try {
73
- await postOrdersOnNewEncounter(
106
+ const postedEncounter = await postOrdersOnNewEncounter(
74
107
  patientUuid,
75
- config?.orderEncounterType,
76
- visitRequired ? visitContext : null,
77
- session?.sessionLocation?.uuid,
108
+ orderEncounterType,
109
+ visitContext,
110
+ orderLocationUuid,
111
+ orderer.uuid,
78
112
  abortController,
79
113
  );
80
114
  await closeWorkspace({ discardUnsavedChanges: true });
@@ -84,6 +118,7 @@ const OrderBasket: React.FC<OrderBasketProps> = ({
84
118
  invalidateVisitAndEncounterData(mutate, patientUuid);
85
119
  clearOrders();
86
120
  await mutateOrders();
121
+ onOrderBasketSubmitted?.(postedEncounter.uuid, postedEncounter.orders);
87
122
 
88
123
  /* Translation keys used by showOrderSuccessToast:
89
124
  * t('discontinued', 'Discontinued')
@@ -106,42 +141,55 @@ const OrderBasket: React.FC<OrderBasketProps> = ({
106
141
  );
107
142
  }
108
143
  } else {
109
- const erroredItems = await postOrders(patientUuid, orderEncounterUuid, abortController);
110
- clearOrders({ exceptThoseMatching: (item) => erroredItems.map((e) => e.display).includes(item.display) });
111
- // Only revalidate current visit since orders create new encounters
112
- mutateVisitContext?.();
113
- await mutateOrders();
114
- invalidateVisitAndEncounterData(mutate, patientUuid);
144
+ try {
145
+ const { postedOrders, erroredItems } = await postOrders(
146
+ patientUuid,
147
+ orderEncounterUuid,
148
+ abortController,
149
+ orderer.uuid,
150
+ );
151
+ clearOrders({ exceptThoseMatching: (item) => erroredItems.map((e) => e.display).includes(item.display) });
152
+ await mutateOrders();
153
+ invalidateVisitAndEncounterData(mutate, patientUuid);
115
154
 
116
- if (erroredItems.length == 0) {
117
- await closeWorkspace({ discardUnsavedChanges: true });
118
- showOrderSuccessToast('@openmrs/esm-patient-orders-app', orders);
119
- } else {
120
- setOrdersWithErrors(erroredItems);
155
+ if (erroredItems.length == 0) {
156
+ await closeWorkspace({ discardUnsavedChanges: true });
157
+ showOrderSuccessToast('@openmrs/esm-patient-orders-app', orders);
158
+ } else {
159
+ setOrdersWithErrors(erroredItems);
160
+ }
161
+ clearOrders({ exceptThoseMatching: (item) => erroredItems.map((e) => e.display).includes(item.display) });
162
+ // Only revalidate current visit since orders create new encounters
163
+ mutateVisitContext?.();
164
+ await mutateOrders();
165
+ invalidateVisitAndEncounterData(mutate, patientUuid);
166
+ onOrderBasketSubmitted?.(orderEncounterUuid, postedOrders);
167
+ } catch (e) {
168
+ console.error(e);
169
+ setCreatingEncounterError(
170
+ e.responseBody?.error?.message ||
171
+ t('tryReopeningTheWorkspaceAgain', 'Please try launching the workspace again'),
172
+ );
121
173
  }
122
- clearOrders({ exceptThoseMatching: (item) => erroredItems.map((e) => e.display).includes(item.display) });
123
- // Only revalidate current visit since orders create new encounters
124
- mutateVisitContext?.();
125
- await mutateOrders();
126
- invalidateVisitAndEncounterData(mutate, patientUuid);
127
174
  }
128
175
  setIsSavingOrders(false);
129
176
  return () => abortController.abort();
130
177
  }, [
131
178
  visitContext,
132
- visitRequired,
133
179
  clearOrders,
134
180
  closeWorkspace,
135
- config,
136
- encounterUuid,
181
+ orderEncounterType,
182
+ orderEncounterUuid,
137
183
  mutateEncounterUuid,
138
184
  mutateOrders,
139
185
  mutateVisitContext,
140
186
  orders,
141
187
  patientUuid,
142
- session,
143
188
  t,
144
189
  mutate,
190
+ orderer,
191
+ orderLocationUuid,
192
+ onOrderBasketSubmitted,
145
193
  ]);
146
194
 
147
195
  const handleCancel = useCallback(() => {
@@ -152,11 +200,47 @@ const OrderBasket: React.FC<OrderBasketProps> = ({
152
200
  });
153
201
  }, [clearOrders, closeWorkspace]);
154
202
 
203
+ const filterItemsByProviderName = useCallback((menu) => {
204
+ return menu?.item?.person?.display?.toLowerCase().includes(menu?.inputValue?.toLowerCase());
205
+ }, []);
206
+
155
207
  return (
156
208
  <Workspace2 title={t('orderBasketWorkspaceTitle', 'Order Basket')} hasUnsavedChanges={!!orders.length}>
157
209
  <div id="order-basket" className={styles.container}>
158
210
  <ExtensionSlot name="visit-context-header-slot" state={{ patientUuid }} />
159
211
  <div className={styles.orderBasketContainer}>
212
+ {!isLoadingProviders &&
213
+ allowSelectingOrderer &&
214
+ (errorLoadingProviders ? (
215
+ <InlineNotification
216
+ kind="warning"
217
+ lowContrast
218
+ className={styles.inlineNotification}
219
+ title={t('errorLoadingClinicians', 'Error loading clinicians')}
220
+ subtitle={t('tryReopeningTheForm', 'Please try launching the form again')}
221
+ />
222
+ ) : (
223
+ <>
224
+ <ComboBox
225
+ id="orderer-combobox"
226
+ items={providers ?? []}
227
+ onChange={({ selectedItem }) => {
228
+ setOrderer(selectedItem);
229
+ }}
230
+ initialSelectedItem={orderer}
231
+ shouldFilterItem={filterItemsByProviderName}
232
+ itemToString={(item: Provider) => item?.person.display ?? ''}
233
+ placeholder={t('searchFieldPlaceholder', 'Search for a Provider')}
234
+ titleText={t('orderer', 'Orderer')}
235
+ />
236
+ <div className={styles.orderLocationOuterContainer}>
237
+ <FormLabel>{t('orderLocation', 'Order location')}</FormLabel>
238
+ <div className={styles.orderLocationContainer}>
239
+ <LocationPicker selectedLocationUuid={orderLocationUuid} onChange={setOrderLocationUuid} />
240
+ </div>
241
+ </div>
242
+ </>
243
+ ))}
160
244
  <ExtensionSlot
161
245
  className={classNames(styles.orderBasketSlot, {
162
246
  [styles.orderBasketSlotTablet]: isTablet,
@@ -164,8 +248,8 @@ const OrderBasket: React.FC<OrderBasketProps> = ({
164
248
  name="order-basket-slot"
165
249
  state={orderBasketExtensionProps as any}
166
250
  />
167
- {config?.orderTypes?.length > 0 &&
168
- config.orderTypes.map((orderType) => (
251
+ {orderTypes?.length > 0 &&
252
+ orderTypes.map((orderType) => (
169
253
  <div className={styles.orderPanel} key={orderType.orderTypeUuid}>
170
254
  <GeneralOrderPanel
171
255
  {...orderType}
@@ -207,7 +291,9 @@ const OrderBasket: React.FC<OrderBasketProps> = ({
207
291
  !orders?.length ||
208
292
  isLoadingEncounterUuid ||
209
293
  (visitRequired && !visitContext) ||
210
- orders?.some(({ isOrderIncomplete }) => isOrderIncomplete)
294
+ orders?.some(({ isOrderIncomplete }) => isOrderIncomplete) ||
295
+ !orderer ||
296
+ !orderLocationUuid
211
297
  }
212
298
  >
213
299
  {isSavingOrders ? (
@@ -18,6 +18,21 @@
18
18
  height: layout.$spacing-10;
19
19
  }
20
20
 
21
+ .orderLocationOuterContainer {
22
+ margin-top: layout.$spacing-05;
23
+ }
24
+
25
+ .orderLocationContainer {
26
+ max-height: 300px;
27
+ overflow-y: auto;
28
+ position: relative;
29
+ margin-top: layout.$spacing-05;
30
+ margin-bottom: layout.$spacing-05;
31
+ > div {
32
+ height: unset;
33
+ }
34
+ }
35
+
21
36
  .orderBasketSlot {
22
37
  display: flex;
23
38
  flex-direction: column;
@@ -29,6 +29,7 @@ export function compare<T>(x?: T, y?: T) {
29
29
 
30
30
  /**
31
31
  * Builds medication order object from the given order object
32
+ * See also same function in esm-patient-medications-app/src/api/api.ts
32
33
  */
33
34
  export function buildMedicationOrder(order: Order, action?: OrderAction): DrugOrderBasketItem {
34
35
  return {
@@ -64,8 +65,6 @@ export function buildMedicationOrder(order: Order, action?: OrderAction): DrugOr
64
65
  pillsDispensed: order.quantity,
65
66
  numRefills: order.numRefills,
66
67
  indication: order.orderReasonNonCoded,
67
- orderer: order.orderer.uuid,
68
- careSetting: order.careSetting.uuid,
69
68
  quantityUnits: {
70
69
  value: order.quantityUnits?.display,
71
70
  valueCoded: order.quantityUnits?.uuid,
@@ -83,8 +82,6 @@ export function buildLabOrder(order: Order, action?: OrderAction): TestOrderBask
83
82
  action: action,
84
83
  display: order.display,
85
84
  previousOrder: action !== 'NEW' ? order.uuid : null,
86
- orderer: order.orderer.uuid,
87
- careSetting: order.careSetting.uuid,
88
85
  instructions: order.instructions,
89
86
  urgency: order.urgency,
90
87
  accessionNumber: order.accessionNumber,
@@ -110,8 +107,6 @@ export function buildGeneralOrder(order: Order, action?: OrderAction): OrderBask
110
107
  action: action,
111
108
  display: order.display,
112
109
  previousOrder: action !== 'NEW' ? order.uuid : null,
113
- orderer: order.orderer.uuid,
114
- careSetting: order.careSetting.uuid,
115
110
  instructions: order.instructions,
116
111
  urgency: order.urgency,
117
112
  accessionNumber: order.accessionNumber,
@@ -27,6 +27,7 @@
27
27
  "enterTestResults": "Enter test results",
28
28
  "error": "Error",
29
29
  "errorFetchingTestTypes": "Error fetching results for \"{{searchTerm}}\"",
30
+ "errorLoadingClinicians": "Error loading clinicians",
30
31
  "errorSavingDrugOrder": "Error saving order",
31
32
  "errorSavingLabResults": "Error saving lab results",
32
33
  "goToDrugOrderForm": "Order form",
@@ -65,7 +66,9 @@
65
66
  "orderDiscontinued": "Order discontinued",
66
67
  "orderedBy": "Ordered by",
67
68
  "orderedFor": "Placed order for",
69
+ "orderer": "Orderer",
68
70
  "ordererInformation": "Orderer information",
71
+ "orderLocation": "Order location",
69
72
  "orderNumber": "Order number",
70
73
  "orderPlaced": "Order placed",
71
74
  "orders": "Orders",
@@ -104,6 +107,7 @@
104
107
  "scheduledDateRequired": "Scheduled date is required",
105
108
  "searchAgain": "search again",
106
109
  "searchFieldOrder": "Search for {{orderType}} order",
110
+ "searchFieldPlaceholder": "Search for a Provider",
107
111
  "searchResultsMatchesForTerm_one": "{{count}} results for \"{{searchTerm}}\"",
108
112
  "searchResultsMatchesForTerm_other": "{{count}} results for \"{{searchTerm}}\"",
109
113
  "searchTable": "Search table",
@@ -115,6 +119,7 @@
115
119
  "test": "Test",
116
120
  "testOrders": "test orders",
117
121
  "testType": "Test type",
122
+ "tryReopeningTheForm": "Please try launching the form again",
118
123
  "tryReopeningTheWorkspaceAgain": "Please try launching the workspace again",
119
124
  "trySearchingAgain": "Please try searching again",
120
125
  "tryTo": "Try to",