@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.
- package/.turbo/turbo-build.log +13 -13
- package/dist/1253.js +1 -1
- package/dist/1253.js.map +1 -1
- package/dist/3685.js +1 -1
- package/dist/375.js +1 -1
- package/dist/375.js.map +1 -1
- package/dist/4300.js +1 -1
- package/dist/4341.js +1 -1
- package/dist/4341.js.map +1 -1
- package/dist/4558.js +1 -1
- package/dist/4558.js.map +1 -1
- package/dist/5937.js +1 -1
- package/dist/5937.js.map +1 -1
- package/dist/6411.js +1 -1
- package/dist/6542.js +1 -1
- package/dist/701.js +2 -0
- package/dist/701.js.map +1 -0
- package/dist/7160.js +1 -1
- package/dist/8154.js +1 -1
- package/dist/8376.js +1 -1
- package/dist/8376.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-orders-app.js +1 -1
- package/dist/openmrs-esm-patient-orders-app.js.buildmanifest.json +61 -61
- package/dist/openmrs-esm-patient-orders-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -2
- package/src/api/api.ts +43 -46
- package/src/components/order-details-table.component.tsx +48 -68
- package/src/config-schema.ts +7 -0
- package/src/order-basket/exported-order-basket.workspace.tsx +2 -0
- package/src/order-basket/general-order-type/add-general-order/search-results.component.tsx +3 -11
- package/src/order-basket/general-order-type/general-order-form/general-order-form.component.tsx +5 -5
- package/src/order-basket/general-order-type/resources.ts +15 -15
- package/src/order-basket/order-basket.component.tsx +122 -36
- package/src/order-basket/order-basket.scss +15 -0
- package/src/utils/index.ts +1 -6
- package/translations/en.json +5 -0
- package/dist/7657.js +0 -2
- package/dist/7657.js.map +0 -1
- /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 {
|
|
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
|
|
50
|
-
const
|
|
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
|
-
} =
|
|
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
|
-
|
|
99
|
+
|
|
69
100
|
setIsSavingOrders(true);
|
|
70
|
-
//
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
{
|
|
168
|
-
|
|
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;
|
package/src/utils/index.ts
CHANGED
|
@@ -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,
|
package/translations/en.json
CHANGED
|
@@ -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",
|