@openmrs/esm-patient-orders-app 11.3.1-pre.9452 → 11.3.1-pre.9455
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 +14 -14
- package/dist/1253.js +1 -0
- package/dist/1253.js.map +1 -0
- package/dist/375.js +1 -0
- package/dist/375.js.map +1 -0
- package/dist/4300.js +1 -1
- package/dist/4341.js +1 -0
- package/dist/4341.js.map +1 -0
- package/dist/4558.js +1 -0
- package/dist/4558.js.map +1 -0
- package/dist/5937.js +1 -0
- package/dist/5937.js.map +1 -0
- package/dist/6473.js +1 -0
- package/dist/6473.js.map +1 -0
- package/dist/6483.js +1 -0
- package/dist/6483.js.map +1 -0
- package/dist/8376.js +1 -0
- package/dist/8376.js.map +1 -0
- package/dist/8389.js +1 -0
- package/dist/8389.js.map +1 -0
- package/dist/8416.js +1 -0
- package/dist/8416.js.map +1 -0
- package/dist/8894.js +1 -0
- package/dist/8894.js.map +1 -0
- 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 +230 -107
- 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 +0 -25
- package/src/components/order-details-table.component.tsx +53 -40
- package/src/index.ts +19 -11
- package/src/lab-results/{lab-results-form.component.tsx → exported-lab-results-form.workspace.tsx} +105 -108
- package/src/lab-results/lab-results-form.test.tsx +24 -38
- package/src/lab-results/lab-results-form.workspace.tsx +26 -0
- package/src/order-basket/exported-order-basket.workspace.tsx +59 -0
- package/src/order-basket/general-order-type/{orderable-concept-search/orderable-concept-search.workspace.tsx → add-general-order/add-general-order.component.tsx} +78 -83
- package/src/order-basket/general-order-type/add-general-order/add-general-order.workspace.tsx +38 -0
- package/src/order-basket/general-order-type/add-general-order/exported-add-general-order.workspace.tsx +35 -0
- package/src/order-basket/general-order-type/{orderable-concept-search → add-general-order}/search-results.component.tsx +15 -14
- package/src/order-basket/general-order-type/general-order-form/general-order-form.component.tsx +71 -25
- package/src/order-basket/general-order-type/{general-order-type.component.tsx → general-order-panel.component.tsx} +23 -41
- package/src/order-basket/general-order-type/resources.ts +3 -2
- package/src/order-basket/order-basket.component.tsx +213 -0
- package/src/order-basket/order-basket.workspace.tsx +42 -252
- package/src/order-basket-action-button/order-basket-action-button.component.tsx +35 -0
- package/src/order-basket-action-button/order-basket-action-button.test.tsx +27 -36
- package/src/routes.json +17 -25
- package/src/utils/index.ts +15 -3
- package/translations/en.json +4 -20
- package/dist/1571.js +0 -1
- package/dist/1571.js.map +0 -1
- package/dist/2717.js +0 -1
- package/dist/2717.js.map +0 -1
- package/dist/4937.js +0 -1
- package/dist/4937.js.map +0 -1
- package/dist/8625.js +0 -1
- package/dist/8625.js.map +0 -1
- package/dist/8803.js +0 -1
- package/dist/8803.js.map +0 -1
- package/dist/8960.js +0 -1
- package/dist/8960.js.map +0 -1
- package/src/order-basket-action-button/order-basket-action-button.extension.tsx +0 -31
- package/src/order-cancellation-form/cancel-order-form.component.tsx +0 -180
- package/src/order-cancellation-form/cancel-order-form.scss +0 -87
- package/src/order-cancellation-form/cancel-order.resource.tsx +0 -15
- /package/src/order-basket/general-order-type/{orderable-concept-search → add-general-order}/orderable-concept-search.scss +0 -0
- /package/src/order-basket/general-order-type/{orderable-concept-search → add-general-order}/search-results.scss +0 -0
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import React, { type ComponentProps, useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { Button, Tile } from '@carbon/react';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
|
-
import {
|
|
5
|
-
AddIcon,
|
|
6
|
-
ChevronDownIcon,
|
|
7
|
-
ChevronUpIcon,
|
|
8
|
-
type DefaultWorkspaceProps,
|
|
9
|
-
MaybeIcon,
|
|
10
|
-
useLayoutType,
|
|
11
|
-
launchWorkspace,
|
|
12
|
-
} from '@openmrs/esm-framework';
|
|
4
|
+
import { AddIcon, ChevronDownIcon, ChevronUpIcon, MaybeIcon, useLayoutType } from '@openmrs/esm-framework';
|
|
13
5
|
import { useTranslation } from 'react-i18next';
|
|
14
6
|
import { type OrderBasketItem, useOrderBasket, useOrderType } from '@openmrs/esm-patient-common-lib';
|
|
15
7
|
import { type OrderTypeDefinition } from '../../config-schema';
|
|
@@ -18,11 +10,24 @@ import OrderBasketItemTile from './order-basket-item-tile.component';
|
|
|
18
10
|
import styles from './general-order-panel.scss';
|
|
19
11
|
|
|
20
12
|
interface GeneralOrderTypeProps extends OrderTypeDefinition {
|
|
21
|
-
closeWorkspace: DefaultWorkspaceProps['closeWorkspace'];
|
|
22
13
|
patient: fhir.Patient;
|
|
14
|
+
orderTypeUuid: string;
|
|
15
|
+
launchGeneralOrderForm(orderTypeUuid: string, order?: OrderBasketItem): void;
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The extension is slotted into order-basket-slot in the main Order Basket workspace by default.
|
|
20
|
+
* It renders the "Add +" button for general orders, and lists pending general orders in the order basket.
|
|
21
|
+
*
|
|
22
|
+
* Designs: https://app.zeplin.io/project/60d59321e8100b0324762e05/screen/62c6bb9500e7671a618efa56
|
|
23
|
+
*/
|
|
24
|
+
const GeneralOrderPanel: React.FC<GeneralOrderTypeProps> = ({
|
|
25
|
+
patient,
|
|
26
|
+
orderTypeUuid,
|
|
27
|
+
label,
|
|
28
|
+
icon,
|
|
29
|
+
launchGeneralOrderForm,
|
|
30
|
+
}) => {
|
|
26
31
|
const { t } = useTranslation();
|
|
27
32
|
const isTablet = useLayoutType() === 'tablet';
|
|
28
33
|
const { orderType, isLoadingOrderType } = useOrderType(orderTypeUuid);
|
|
@@ -65,29 +70,6 @@ const GeneralOrderType: React.FC<GeneralOrderTypeProps> = ({ patient, orderTypeU
|
|
|
65
70
|
};
|
|
66
71
|
}, [orders]);
|
|
67
72
|
|
|
68
|
-
const openConceptSearch = () => {
|
|
69
|
-
closeWorkspace({
|
|
70
|
-
ignoreChanges: true,
|
|
71
|
-
onWorkspaceClose: () =>
|
|
72
|
-
launchWorkspace('orderable-concept-workspace', {
|
|
73
|
-
orderTypeUuid,
|
|
74
|
-
}),
|
|
75
|
-
closeWorkspaceGroup: false,
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const openOrderForm = (order: OrderBasketItem) => {
|
|
80
|
-
closeWorkspace({
|
|
81
|
-
ignoreChanges: true,
|
|
82
|
-
onWorkspaceClose: () =>
|
|
83
|
-
launchWorkspace('orderable-concept-workspace', {
|
|
84
|
-
order,
|
|
85
|
-
orderTypeUuid,
|
|
86
|
-
}),
|
|
87
|
-
closeWorkspaceGroup: false,
|
|
88
|
-
});
|
|
89
|
-
};
|
|
90
|
-
|
|
91
73
|
const removeOrder = useCallback(
|
|
92
74
|
(order: OrderBasketItem) => {
|
|
93
75
|
const newOrders = [...orders];
|
|
@@ -120,7 +102,7 @@ const GeneralOrderType: React.FC<GeneralOrderTypeProps> = ({ patient, orderTypeU
|
|
|
120
102
|
kind="ghost"
|
|
121
103
|
renderIcon={(props: ComponentProps<typeof AddIcon>) => <AddIcon size={16} {...props} />}
|
|
122
104
|
iconDescription={t('addMedication', 'Add medication')}
|
|
123
|
-
onClick={
|
|
105
|
+
onClick={() => launchGeneralOrderForm(orderTypeUuid)}
|
|
124
106
|
size={isTablet ? 'md' : 'sm'}
|
|
125
107
|
>
|
|
126
108
|
{t('add', 'Add')}
|
|
@@ -148,7 +130,7 @@ const GeneralOrderType: React.FC<GeneralOrderTypeProps> = ({ patient, orderTypeU
|
|
|
148
130
|
<OrderBasketItemTile
|
|
149
131
|
key={`incomplete-${order.action}-${order.concept?.uuid}-${index}`}
|
|
150
132
|
orderBasketItem={order}
|
|
151
|
-
onItemClick={() =>
|
|
133
|
+
onItemClick={() => launchGeneralOrderForm(orderTypeUuid, order)}
|
|
152
134
|
onRemoveClick={() => removeOrder(order)}
|
|
153
135
|
/>
|
|
154
136
|
))}
|
|
@@ -160,7 +142,7 @@ const GeneralOrderType: React.FC<GeneralOrderTypeProps> = ({ patient, orderTypeU
|
|
|
160
142
|
<OrderBasketItemTile
|
|
161
143
|
key={`new-${order.action}-${order.concept?.uuid}-${index}`}
|
|
162
144
|
orderBasketItem={order}
|
|
163
|
-
onItemClick={() =>
|
|
145
|
+
onItemClick={() => launchGeneralOrderForm(orderTypeUuid, order)}
|
|
164
146
|
onRemoveClick={() => removeOrder(order)}
|
|
165
147
|
/>
|
|
166
148
|
))}
|
|
@@ -173,7 +155,7 @@ const GeneralOrderType: React.FC<GeneralOrderTypeProps> = ({ patient, orderTypeU
|
|
|
173
155
|
<OrderBasketItemTile
|
|
174
156
|
key={`renewed-${item.action}-${item.concept?.uuid}-${index}`}
|
|
175
157
|
orderBasketItem={item}
|
|
176
|
-
onItemClick={() =>
|
|
158
|
+
onItemClick={() => launchGeneralOrderForm(orderTypeUuid, item)}
|
|
177
159
|
onRemoveClick={() => removeOrder(item)}
|
|
178
160
|
/>
|
|
179
161
|
))}
|
|
@@ -186,7 +168,7 @@ const GeneralOrderType: React.FC<GeneralOrderTypeProps> = ({ patient, orderTypeU
|
|
|
186
168
|
<OrderBasketItemTile
|
|
187
169
|
key={`revised-${item.action}-${item.concept?.uuid}-${index}`}
|
|
188
170
|
orderBasketItem={item}
|
|
189
|
-
onItemClick={() =>
|
|
171
|
+
onItemClick={() => launchGeneralOrderForm(orderTypeUuid, item)}
|
|
190
172
|
onRemoveClick={() => removeOrder(item)}
|
|
191
173
|
/>
|
|
192
174
|
))}
|
|
@@ -199,7 +181,7 @@ const GeneralOrderType: React.FC<GeneralOrderTypeProps> = ({ patient, orderTypeU
|
|
|
199
181
|
<OrderBasketItemTile
|
|
200
182
|
key={`discontinued-${item.action}-${item.concept?.uuid}-${index}`}
|
|
201
183
|
orderBasketItem={item}
|
|
202
|
-
onItemClick={() =>
|
|
184
|
+
onItemClick={() => launchGeneralOrderForm(orderTypeUuid, item)}
|
|
203
185
|
onRemoveClick={() => removeOrder(item)}
|
|
204
186
|
/>
|
|
205
187
|
))}
|
|
@@ -211,4 +193,4 @@ const GeneralOrderType: React.FC<GeneralOrderTypeProps> = ({ patient, orderTypeU
|
|
|
211
193
|
);
|
|
212
194
|
};
|
|
213
195
|
|
|
214
|
-
export default
|
|
196
|
+
export default GeneralOrderPanel;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toOmrsIsoString } from '@openmrs/esm-framework';
|
|
1
|
+
import { toOmrsIsoString, type Visit } from '@openmrs/esm-framework';
|
|
2
2
|
import {
|
|
3
3
|
type OrderBasketItem,
|
|
4
4
|
priorityOptions,
|
|
@@ -7,13 +7,14 @@ import {
|
|
|
7
7
|
type OrderableConcept,
|
|
8
8
|
} from '@openmrs/esm-patient-common-lib';
|
|
9
9
|
|
|
10
|
-
export function createEmptyOrder(concept: OrderableConcept, orderer: string): OrderBasketItem {
|
|
10
|
+
export function createEmptyOrder(concept: OrderableConcept, orderer: string, visit: Visit): OrderBasketItem {
|
|
11
11
|
return {
|
|
12
12
|
action: 'NEW',
|
|
13
13
|
urgency: priorityOptions[0].value as OrderUrgency,
|
|
14
14
|
display: concept.display,
|
|
15
15
|
concept,
|
|
16
16
|
orderer,
|
|
17
|
+
visit,
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { Button, ButtonSet, InlineLoading, InlineNotification } from '@carbon/react';
|
|
5
|
+
import {
|
|
6
|
+
ExtensionSlot,
|
|
7
|
+
useConfig,
|
|
8
|
+
useLayoutType,
|
|
9
|
+
useSession,
|
|
10
|
+
type Visit,
|
|
11
|
+
Workspace2,
|
|
12
|
+
type Workspace2DefinitionProps,
|
|
13
|
+
} from '@openmrs/esm-framework';
|
|
14
|
+
import {
|
|
15
|
+
invalidateVisitAndEncounterData,
|
|
16
|
+
type OrderBasketExtensionProps,
|
|
17
|
+
type OrderBasketItem,
|
|
18
|
+
postOrders,
|
|
19
|
+
postOrdersOnNewEncounter,
|
|
20
|
+
showOrderSuccessToast,
|
|
21
|
+
useMutatePatientOrders,
|
|
22
|
+
useOrderBasket,
|
|
23
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
24
|
+
import { useSWRConfig } from 'swr';
|
|
25
|
+
import { type ConfigObject } from '../config-schema';
|
|
26
|
+
import { useOrderEncounter } from '../api/api';
|
|
27
|
+
import GeneralOrderPanel from './general-order-type/general-order-panel.component';
|
|
28
|
+
import styles from './order-basket.scss';
|
|
29
|
+
|
|
30
|
+
interface OrderBasketProps {
|
|
31
|
+
patientUuid: string;
|
|
32
|
+
patient: fhir.Patient;
|
|
33
|
+
visitContext: Visit;
|
|
34
|
+
mutateVisitContext: () => void;
|
|
35
|
+
closeWorkspace: Workspace2DefinitionProps['closeWorkspace'];
|
|
36
|
+
orderBasketExtensionProps: OrderBasketExtensionProps;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const OrderBasket: React.FC<OrderBasketProps> = ({
|
|
40
|
+
patientUuid,
|
|
41
|
+
patient,
|
|
42
|
+
visitContext,
|
|
43
|
+
mutateVisitContext,
|
|
44
|
+
closeWorkspace,
|
|
45
|
+
orderBasketExtensionProps,
|
|
46
|
+
}) => {
|
|
47
|
+
const { t } = useTranslation();
|
|
48
|
+
const isTablet = useLayoutType() === 'tablet';
|
|
49
|
+
const config = useConfig<ConfigObject>();
|
|
50
|
+
const session = useSession();
|
|
51
|
+
const { orders, clearOrders } = useOrderBasket(patient);
|
|
52
|
+
const [ordersWithErrors, setOrdersWithErrors] = useState<OrderBasketItem[]>([]);
|
|
53
|
+
const {
|
|
54
|
+
visitRequired,
|
|
55
|
+
isLoading: isLoadingEncounterUuid,
|
|
56
|
+
encounterUuid,
|
|
57
|
+
error: errorFetchingEncounterUuid,
|
|
58
|
+
mutate: mutateEncounterUuid,
|
|
59
|
+
} = useOrderEncounter(patientUuid, visitContext, mutateVisitContext, config.orderEncounterType);
|
|
60
|
+
const [isSavingOrders, setIsSavingOrders] = useState(false);
|
|
61
|
+
const [creatingEncounterError, setCreatingEncounterError] = useState('');
|
|
62
|
+
const { mutate: mutateOrders } = useMutatePatientOrders(patientUuid);
|
|
63
|
+
const { mutate } = useSWRConfig();
|
|
64
|
+
|
|
65
|
+
const handleSave = useCallback(async () => {
|
|
66
|
+
const abortController = new AbortController();
|
|
67
|
+
setCreatingEncounterError('');
|
|
68
|
+
let orderEncounterUuid = encounterUuid;
|
|
69
|
+
setIsSavingOrders(true);
|
|
70
|
+
// If there's no encounter present, create an encounter along with the orders.
|
|
71
|
+
if (!orderEncounterUuid) {
|
|
72
|
+
try {
|
|
73
|
+
await postOrdersOnNewEncounter(
|
|
74
|
+
patientUuid,
|
|
75
|
+
config?.orderEncounterType,
|
|
76
|
+
visitRequired ? visitContext : null,
|
|
77
|
+
session?.sessionLocation?.uuid,
|
|
78
|
+
abortController,
|
|
79
|
+
);
|
|
80
|
+
await closeWorkspace({ discardUnsavedChanges: true });
|
|
81
|
+
mutateEncounterUuid();
|
|
82
|
+
// Only revalidate current visit since orders create new encounters
|
|
83
|
+
mutateVisitContext?.();
|
|
84
|
+
invalidateVisitAndEncounterData(mutate, patientUuid);
|
|
85
|
+
clearOrders();
|
|
86
|
+
await mutateOrders();
|
|
87
|
+
showOrderSuccessToast(t, orders);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error(e);
|
|
90
|
+
setCreatingEncounterError(
|
|
91
|
+
e.responseBody?.error?.message ||
|
|
92
|
+
t('tryReopeningTheWorkspaceAgain', 'Please try launching the workspace again'),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
const erroredItems = await postOrders(patientUuid, orderEncounterUuid, abortController);
|
|
97
|
+
clearOrders({ exceptThoseMatching: (item) => erroredItems.map((e) => e.display).includes(item.display) });
|
|
98
|
+
// Only revalidate current visit since orders create new encounters
|
|
99
|
+
mutateVisitContext?.();
|
|
100
|
+
await mutateOrders();
|
|
101
|
+
invalidateVisitAndEncounterData(mutate, patientUuid);
|
|
102
|
+
|
|
103
|
+
if (erroredItems.length == 0) {
|
|
104
|
+
await closeWorkspace({ discardUnsavedChanges: true });
|
|
105
|
+
showOrderSuccessToast(t, orders);
|
|
106
|
+
} else {
|
|
107
|
+
setOrdersWithErrors(erroredItems);
|
|
108
|
+
}
|
|
109
|
+
clearOrders({ exceptThoseMatching: (item) => erroredItems.map((e) => e.display).includes(item.display) });
|
|
110
|
+
// Only revalidate current visit since orders create new encounters
|
|
111
|
+
mutateVisitContext?.();
|
|
112
|
+
await mutateOrders();
|
|
113
|
+
invalidateVisitAndEncounterData(mutate, patientUuid);
|
|
114
|
+
}
|
|
115
|
+
setIsSavingOrders(false);
|
|
116
|
+
return () => abortController.abort();
|
|
117
|
+
}, [
|
|
118
|
+
visitContext,
|
|
119
|
+
visitRequired,
|
|
120
|
+
clearOrders,
|
|
121
|
+
closeWorkspace,
|
|
122
|
+
config,
|
|
123
|
+
encounterUuid,
|
|
124
|
+
mutateEncounterUuid,
|
|
125
|
+
mutateOrders,
|
|
126
|
+
mutateVisitContext,
|
|
127
|
+
orders,
|
|
128
|
+
patientUuid,
|
|
129
|
+
session,
|
|
130
|
+
t,
|
|
131
|
+
mutate,
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
const handleCancel = useCallback(() => {
|
|
135
|
+
closeWorkspace().then((didClose) => {
|
|
136
|
+
if (didClose) {
|
|
137
|
+
clearOrders();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}, [clearOrders, closeWorkspace]);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<Workspace2 title={t('orderBasketWorkspaceTitle', 'Order Basket')} hasUnsavedChanges={!!orders.length}>
|
|
144
|
+
<div id="order-basket" className={styles.container}>
|
|
145
|
+
<ExtensionSlot name="visit-context-header-slot" state={{ patientUuid }} />
|
|
146
|
+
<div className={styles.orderBasketContainer}>
|
|
147
|
+
<ExtensionSlot
|
|
148
|
+
className={classNames(styles.orderBasketSlot, {
|
|
149
|
+
[styles.orderBasketSlotTablet]: isTablet,
|
|
150
|
+
})}
|
|
151
|
+
name="order-basket-slot"
|
|
152
|
+
state={orderBasketExtensionProps as any}
|
|
153
|
+
/>
|
|
154
|
+
{config?.orderTypes?.length > 0 &&
|
|
155
|
+
config.orderTypes.map((orderType) => (
|
|
156
|
+
<div className={styles.orderPanel} key={orderType.orderTypeUuid}>
|
|
157
|
+
<GeneralOrderPanel
|
|
158
|
+
{...orderType}
|
|
159
|
+
launchGeneralOrderForm={orderBasketExtensionProps.launchGeneralOrderForm}
|
|
160
|
+
patient={patient}
|
|
161
|
+
/>
|
|
162
|
+
</div>
|
|
163
|
+
))}
|
|
164
|
+
</div>
|
|
165
|
+
<div>
|
|
166
|
+
{(creatingEncounterError || errorFetchingEncounterUuid) && (
|
|
167
|
+
<InlineNotification
|
|
168
|
+
kind="error"
|
|
169
|
+
title={t('tryReopeningTheWorkspaceAgain', 'Please try launching the workspace again')}
|
|
170
|
+
subtitle={creatingEncounterError}
|
|
171
|
+
lowContrast={true}
|
|
172
|
+
className={styles.inlineNotification}
|
|
173
|
+
/>
|
|
174
|
+
)}
|
|
175
|
+
{ordersWithErrors.map((order) => (
|
|
176
|
+
<InlineNotification
|
|
177
|
+
lowContrast
|
|
178
|
+
kind="error"
|
|
179
|
+
title={t('saveDrugOrderFailed', 'Error ordering {{orderName}}', { orderName: order.display })}
|
|
180
|
+
subtitle={order.extractedOrderError?.fieldErrors?.join(', ')}
|
|
181
|
+
className={styles.inlineNotification}
|
|
182
|
+
/>
|
|
183
|
+
))}
|
|
184
|
+
<ButtonSet className={styles.buttonSet}>
|
|
185
|
+
<Button className={styles.actionButton} kind="secondary" onClick={handleCancel}>
|
|
186
|
+
{t('cancel', 'Cancel')}
|
|
187
|
+
</Button>
|
|
188
|
+
<Button
|
|
189
|
+
className={styles.actionButton}
|
|
190
|
+
kind="primary"
|
|
191
|
+
onClick={handleSave}
|
|
192
|
+
disabled={
|
|
193
|
+
isSavingOrders ||
|
|
194
|
+
!orders?.length ||
|
|
195
|
+
isLoadingEncounterUuid ||
|
|
196
|
+
(visitRequired && !visitContext) ||
|
|
197
|
+
orders?.some(({ isOrderIncomplete }) => isOrderIncomplete)
|
|
198
|
+
}
|
|
199
|
+
>
|
|
200
|
+
{isSavingOrders ? (
|
|
201
|
+
<InlineLoading description={t('saving', 'Saving') + '...'} />
|
|
202
|
+
) : (
|
|
203
|
+
<span>{t('signAndClose', 'Sign and close')}</span>
|
|
204
|
+
)}
|
|
205
|
+
</Button>
|
|
206
|
+
</ButtonSet>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</Workspace2>
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export default OrderBasket;
|
|
@@ -1,262 +1,52 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import classNames from 'classnames';
|
|
3
|
-
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import type { TFunction } from 'i18next';
|
|
5
|
-
import { ActionableNotification, Button, ButtonSet, InlineLoading, InlineNotification } from '@carbon/react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
6
2
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
showSnackbar,
|
|
10
|
-
useConfig,
|
|
11
|
-
useLayoutType,
|
|
12
|
-
useSession,
|
|
13
|
-
type Visit,
|
|
14
|
-
} from '@openmrs/esm-framework';
|
|
15
|
-
import {
|
|
16
|
-
type DefaultPatientWorkspaceProps,
|
|
3
|
+
type OrderBasketExtensionProps,
|
|
4
|
+
type OrderBasketWindowProps,
|
|
17
5
|
type OrderBasketItem,
|
|
18
|
-
|
|
19
|
-
postOrders,
|
|
20
|
-
postOrdersOnNewEncounter,
|
|
21
|
-
useOrderBasket,
|
|
6
|
+
type PatientWorkspace2DefinitionProps,
|
|
22
7
|
} from '@openmrs/esm-patient-common-lib';
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
mutateVisitContext: () => void;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const OrderBasket: React.FC<DefaultPatientWorkspaceProps> = ({
|
|
37
|
-
patientUuid,
|
|
38
|
-
patient,
|
|
8
|
+
import OrderBasket from './order-basket.component';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This workspace renders the main order basket, which contains the buttons to add a drug order and to add a lab order.
|
|
12
|
+
*
|
|
13
|
+
* This workspace must only be used within the patient chart
|
|
14
|
+
* @see exported-order-basket.workspace.tsx
|
|
15
|
+
*/
|
|
16
|
+
const OrderBasketWorkspace: React.FC<PatientWorkspace2DefinitionProps<{}, OrderBasketWindowProps>> = ({
|
|
17
|
+
groupProps: { patientUuid, patient, visitContext, mutateVisitContext },
|
|
39
18
|
closeWorkspace,
|
|
40
|
-
|
|
41
|
-
promptBeforeClosing,
|
|
42
|
-
visitContext,
|
|
43
|
-
mutateVisitContext,
|
|
19
|
+
launchChildWorkspace,
|
|
44
20
|
}) => {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
promptBeforeClosing(() => !!orders.length);
|
|
65
|
-
}, [orders, promptBeforeClosing]);
|
|
66
|
-
|
|
67
|
-
const openStartVisitDialog = useCallback(() => {
|
|
68
|
-
const dispose = showModal('start-visit-dialog', {
|
|
69
|
-
patientUuid,
|
|
70
|
-
closeModal: () => dispose(),
|
|
71
|
-
});
|
|
72
|
-
}, [patientUuid]);
|
|
73
|
-
|
|
74
|
-
const handleSave = useCallback(async () => {
|
|
75
|
-
const abortController = new AbortController();
|
|
76
|
-
setCreatingEncounterError('');
|
|
77
|
-
let orderEncounterUuid = encounterUuid;
|
|
78
|
-
setIsSavingOrders(true);
|
|
79
|
-
// If there's no encounter present, create an encounter along with the orders.
|
|
80
|
-
if (!orderEncounterUuid) {
|
|
81
|
-
try {
|
|
82
|
-
await postOrdersOnNewEncounter(
|
|
83
|
-
patientUuid,
|
|
84
|
-
config?.orderEncounterType,
|
|
85
|
-
visitRequired ? visitContext : null,
|
|
86
|
-
session?.sessionLocation?.uuid,
|
|
87
|
-
abortController,
|
|
88
|
-
);
|
|
89
|
-
mutateEncounterUuid();
|
|
90
|
-
// Only revalidate current visit since orders create new encounters
|
|
91
|
-
mutateVisitContext?.();
|
|
92
|
-
clearOrders();
|
|
93
|
-
await mutateOrders();
|
|
94
|
-
|
|
95
|
-
closeWorkspaceWithSavedChanges();
|
|
96
|
-
showOrderSuccessToast(t, orders);
|
|
97
|
-
} catch (e) {
|
|
98
|
-
console.error(e);
|
|
99
|
-
setCreatingEncounterError(
|
|
100
|
-
e.responseBody?.error?.message ||
|
|
101
|
-
t('tryReopeningTheWorkspaceAgain', 'Please try launching the workspace again'),
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
const erroredItems = await postOrders(patientUuid, orderEncounterUuid, abortController);
|
|
106
|
-
clearOrders({ exceptThoseMatching: (item) => erroredItems.map((e) => e.display).includes(item.display) });
|
|
107
|
-
// Only revalidate current visit since orders create new encounters
|
|
108
|
-
mutateVisitContext?.();
|
|
109
|
-
await mutateOrders();
|
|
110
|
-
invalidateVisitAndEncounterData(mutate, patientUuid);
|
|
111
|
-
|
|
112
|
-
if (erroredItems.length == 0) {
|
|
113
|
-
closeWorkspaceWithSavedChanges();
|
|
114
|
-
showOrderSuccessToast(t, orders);
|
|
115
|
-
} else {
|
|
116
|
-
setOrdersWithErrors(erroredItems);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
setIsSavingOrders(false);
|
|
120
|
-
return () => abortController.abort();
|
|
121
|
-
}, [
|
|
122
|
-
visitContext,
|
|
123
|
-
visitRequired,
|
|
124
|
-
clearOrders,
|
|
125
|
-
closeWorkspaceWithSavedChanges,
|
|
126
|
-
config,
|
|
127
|
-
encounterUuid,
|
|
128
|
-
mutateEncounterUuid,
|
|
129
|
-
mutateOrders,
|
|
130
|
-
mutateVisitContext,
|
|
131
|
-
orders,
|
|
132
|
-
patientUuid,
|
|
133
|
-
session,
|
|
134
|
-
t,
|
|
135
|
-
mutate,
|
|
136
|
-
]);
|
|
137
|
-
|
|
138
|
-
const handleCancel = useCallback(() => {
|
|
139
|
-
closeWorkspace({ onWorkspaceClose: clearOrders });
|
|
140
|
-
}, [clearOrders, closeWorkspace]);
|
|
141
|
-
|
|
142
|
-
const extensionProps = {
|
|
143
|
-
patientUuid,
|
|
144
|
-
patient,
|
|
145
|
-
visitContext,
|
|
146
|
-
mutateVisitContext,
|
|
147
|
-
} satisfies OrderBasketSlotProps;
|
|
21
|
+
const orderBasketExtensionProps = useMemo(() => {
|
|
22
|
+
const launchDrugOrderForm = (order: OrderBasketItem) => {
|
|
23
|
+
launchChildWorkspace('add-drug-order', { order });
|
|
24
|
+
};
|
|
25
|
+
const launchLabOrderForm = (orderTypeUuid: string, order: OrderBasketItem) => {
|
|
26
|
+
launchChildWorkspace('add-lab-order', { orderTypeUuid, order });
|
|
27
|
+
};
|
|
28
|
+
const launchGeneralOrderForm = (orderTypeUuid: string, order: OrderBasketItem) => {
|
|
29
|
+
launchChildWorkspace('orderable-concept-workspace', { orderTypeUuid, order });
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
patient,
|
|
34
|
+
launchDrugOrderForm,
|
|
35
|
+
launchLabOrderForm,
|
|
36
|
+
launchGeneralOrderForm,
|
|
37
|
+
} satisfies OrderBasketExtensionProps;
|
|
38
|
+
}, [launchChildWorkspace, patient]);
|
|
148
39
|
|
|
149
40
|
return (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
name="order-basket-slot"
|
|
159
|
-
state={extensionProps}
|
|
160
|
-
/>
|
|
161
|
-
{config?.orderTypes?.length > 0 &&
|
|
162
|
-
config.orderTypes.map((orderType) => (
|
|
163
|
-
<div className={styles.orderPanel} key={orderType.orderTypeUuid}>
|
|
164
|
-
<GeneralOrderType
|
|
165
|
-
key={orderType.orderTypeUuid}
|
|
166
|
-
orderTypeUuid={orderType.orderTypeUuid}
|
|
167
|
-
label={orderType.label}
|
|
168
|
-
orderableConceptSets={orderType.orderableConceptSets}
|
|
169
|
-
closeWorkspace={closeWorkspace}
|
|
170
|
-
patient={patient}
|
|
171
|
-
/>
|
|
172
|
-
</div>
|
|
173
|
-
))}
|
|
174
|
-
</div>
|
|
175
|
-
|
|
176
|
-
<div>
|
|
177
|
-
{(creatingEncounterError || errorFetchingEncounterUuid) && (
|
|
178
|
-
<InlineNotification
|
|
179
|
-
kind="error"
|
|
180
|
-
title={t('tryReopeningTheWorkspaceAgain', 'Please try launching the workspace again')}
|
|
181
|
-
subtitle={creatingEncounterError}
|
|
182
|
-
lowContrast={true}
|
|
183
|
-
className={styles.inlineNotification}
|
|
184
|
-
/>
|
|
185
|
-
)}
|
|
186
|
-
{ordersWithErrors.map((order) => (
|
|
187
|
-
<InlineNotification
|
|
188
|
-
lowContrast
|
|
189
|
-
kind="error"
|
|
190
|
-
title={t('saveDrugOrderFailed', 'Error ordering {{orderName}}', { orderName: order.display })}
|
|
191
|
-
subtitle={order.extractedOrderError?.fieldErrors?.join(', ')}
|
|
192
|
-
className={styles.inlineNotification}
|
|
193
|
-
/>
|
|
194
|
-
))}
|
|
195
|
-
<ButtonSet className={styles.buttonSet}>
|
|
196
|
-
<Button className={styles.actionButton} kind="secondary" onClick={handleCancel}>
|
|
197
|
-
{t('cancel', 'Cancel')}
|
|
198
|
-
</Button>
|
|
199
|
-
<Button
|
|
200
|
-
className={styles.actionButton}
|
|
201
|
-
kind="primary"
|
|
202
|
-
onClick={handleSave}
|
|
203
|
-
disabled={
|
|
204
|
-
isSavingOrders ||
|
|
205
|
-
!orders?.length ||
|
|
206
|
-
isLoadingEncounterUuid ||
|
|
207
|
-
(visitRequired && !visitContext) ||
|
|
208
|
-
orders?.some(({ isOrderIncomplete }) => isOrderIncomplete)
|
|
209
|
-
}
|
|
210
|
-
>
|
|
211
|
-
{isSavingOrders ? (
|
|
212
|
-
<InlineLoading description={t('saving', 'Saving') + '...'} />
|
|
213
|
-
) : (
|
|
214
|
-
<span>{t('signAndClose', 'Sign and close')}</span>
|
|
215
|
-
)}
|
|
216
|
-
</Button>
|
|
217
|
-
</ButtonSet>
|
|
218
|
-
</div>
|
|
219
|
-
</div>
|
|
220
|
-
{visitRequired && !visitContext && (
|
|
221
|
-
<ActionableNotification
|
|
222
|
-
kind="error"
|
|
223
|
-
actionButtonLabel={t('startVisit', 'Start visit')}
|
|
224
|
-
onActionButtonClick={openStartVisitDialog}
|
|
225
|
-
title={t('startAVisitToRecordOrders', 'Start a visit to order')}
|
|
226
|
-
subtitle={t('visitRequired', 'You must select a visit to make orders')}
|
|
227
|
-
lowContrast={true}
|
|
228
|
-
inline
|
|
229
|
-
className={styles.actionNotification}
|
|
230
|
-
hasFocus
|
|
231
|
-
/>
|
|
232
|
-
)}
|
|
233
|
-
</>
|
|
41
|
+
<OrderBasket
|
|
42
|
+
patientUuid={patientUuid}
|
|
43
|
+
patient={patient}
|
|
44
|
+
visitContext={visitContext}
|
|
45
|
+
mutateVisitContext={mutateVisitContext}
|
|
46
|
+
closeWorkspace={closeWorkspace}
|
|
47
|
+
orderBasketExtensionProps={orderBasketExtensionProps}
|
|
48
|
+
/>
|
|
234
49
|
);
|
|
235
50
|
};
|
|
236
51
|
|
|
237
|
-
|
|
238
|
-
const orderedString = patientOrderItems
|
|
239
|
-
.filter((item) => ['NEW', 'RENEW'].includes(item.action))
|
|
240
|
-
.map((item) => item.display)
|
|
241
|
-
.join(', ');
|
|
242
|
-
const updatedString = patientOrderItems
|
|
243
|
-
.filter((item) => item.action === 'REVISE')
|
|
244
|
-
.map((item) => item.display)
|
|
245
|
-
.join(', ');
|
|
246
|
-
const discontinuedString = patientOrderItems
|
|
247
|
-
.filter((item) => item.action === 'DISCONTINUE')
|
|
248
|
-
.map((item) => item.display)
|
|
249
|
-
.join(', ');
|
|
250
|
-
|
|
251
|
-
showSnackbar({
|
|
252
|
-
isLowContrast: true,
|
|
253
|
-
kind: 'success',
|
|
254
|
-
title: t('orderCompleted', 'Placed orders'),
|
|
255
|
-
subtitle:
|
|
256
|
-
(orderedString && `${t('ordered', 'Placed order for')} ${orderedString}. `) +
|
|
257
|
-
(updatedString && `${t('updated', 'Updated')} ${updatedString}. `) +
|
|
258
|
-
(discontinuedString && `${t('discontinued', 'Discontinued')} ${discontinuedString}.`),
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export default OrderBasket;
|
|
52
|
+
export default OrderBasketWorkspace;
|