@openmrs/esm-patient-orders-app 11.3.1-patch.9310 → 11.3.1-patch.9508
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 +15 -18
- package/dist/1119.js +1 -1
- package/dist/1197.js +1 -1
- package/dist/1571.js +1 -0
- package/dist/1571.js.map +1 -0
- package/dist/2146.js +1 -1
- package/dist/2690.js +1 -1
- package/dist/2717.js +1 -0
- package/dist/2717.js.map +1 -0
- package/dist/3099.js +1 -1
- package/dist/3584.js +1 -1
- package/dist/4055.js +1 -1
- package/dist/4132.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/4335.js +1 -1
- package/dist/439.js +1 -0
- package/dist/4618.js +1 -1
- package/dist/4652.js +1 -1
- package/dist/4937.js +1 -1
- package/dist/4937.js.map +1 -1
- package/dist/4944.js +1 -1
- package/dist/5134.js +2 -0
- package/dist/5134.js.map +1 -0
- package/dist/5173.js +1 -1
- package/dist/5241.js +1 -1
- package/dist/5442.js +1 -1
- package/dist/5661.js +1 -1
- package/dist/5670.js +1 -1
- package/dist/5670.js.map +1 -1
- package/dist/6022.js +1 -1
- package/dist/6468.js +1 -1
- package/dist/6589.js +1 -0
- package/dist/6679.js +1 -1
- package/dist/6840.js +1 -1
- package/dist/6859.js +1 -1
- package/dist/7097.js +1 -1
- package/dist/7159.js +1 -1
- package/dist/723.js +1 -1
- package/dist/7617.js +1 -1
- package/dist/795.js +1 -1
- package/dist/8163.js +1 -1
- package/dist/8349.js +1 -1
- package/dist/8371.js +1 -0
- package/dist/8618.js +1 -1
- package/dist/8625.js +1 -0
- package/dist/8625.js.map +1 -0
- package/dist/8803.js +1 -0
- package/dist/8803.js.map +1 -0
- package/dist/890.js +1 -1
- package/dist/8960.js +1 -0
- package/dist/8960.js.map +1 -0
- package/dist/9214.js +1 -1
- package/dist/9538.js +1 -1
- package/dist/9569.js +1 -1
- package/dist/986.js +1 -1
- package/dist/9879.js +1 -1
- package/dist/9895.js +1 -1
- package/dist/9900.js +1 -1
- package/dist/9913.js +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 +276 -259
- 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 +25 -0
- package/src/components/order-details-table.component.tsx +40 -53
- package/src/components/test-order.component.tsx +53 -49
- package/src/index.ts +9 -12
- package/src/lab-results/lab-results-form.component.tsx +343 -0
- package/src/lab-results/lab-results-form.test.tsx +455 -306
- package/src/lab-results/lab-results-schema.resource.tsx +13 -4
- package/src/lab-results/lab-results.resource.ts +113 -0
- package/src/order-basket/general-order-type/general-order-form/general-order-form.component.tsx +25 -71
- package/src/order-basket/general-order-type/{general-order-panel.component.tsx → general-order-type.component.tsx} +41 -23
- package/src/order-basket/general-order-type/{add-general-order/add-general-order.component.tsx → orderable-concept-search/orderable-concept-search.workspace.tsx} +83 -78
- package/src/order-basket/general-order-type/{add-general-order → orderable-concept-search}/search-results.component.tsx +14 -15
- package/src/order-basket/general-order-type/resources.ts +2 -3
- package/src/order-basket/order-basket.workspace.tsx +251 -35
- package/src/order-basket-action-button/order-basket-action-button.extension.tsx +31 -0
- package/src/order-basket-action-button/order-basket-action-button.test.tsx +36 -27
- package/src/order-cancellation-form/cancel-order-form.component.tsx +85 -82
- package/src/routes.json +25 -17
- package/src/utils/index.ts +3 -15
- package/translations/am.json +6 -20
- package/translations/ar.json +6 -20
- package/translations/ar_SY.json +6 -20
- package/translations/bn.json +6 -20
- package/translations/cs.json +114 -0
- package/translations/de.json +6 -20
- package/translations/en.json +12 -4
- package/translations/en_US.json +6 -20
- package/translations/es.json +6 -20
- package/translations/es_MX.json +6 -20
- package/translations/fr.json +7 -21
- package/translations/he.json +6 -20
- package/translations/hi.json +6 -20
- package/translations/hi_IN.json +6 -20
- package/translations/id.json +6 -20
- package/translations/it.json +6 -20
- package/translations/ka.json +6 -20
- package/translations/km.json +6 -20
- package/translations/ku.json +6 -20
- package/translations/ky.json +6 -20
- package/translations/lg.json +6 -20
- package/translations/ne.json +6 -20
- package/translations/pl.json +6 -20
- package/translations/pt.json +6 -20
- package/translations/pt_BR.json +6 -20
- package/translations/qu.json +6 -20
- package/translations/ro_RO.json +6 -20
- package/translations/ru_RU.json +6 -20
- package/translations/si.json +6 -20
- package/translations/sq.json +114 -0
- package/translations/sw.json +6 -20
- package/translations/sw_KE.json +6 -20
- package/translations/tr.json +6 -20
- package/translations/tr_TR.json +6 -20
- package/translations/uk.json +6 -20
- package/translations/uz.json +6 -20
- package/translations/uz@Latn.json +6 -20
- package/translations/uz_UZ.json +6 -20
- package/translations/vi.json +6 -20
- package/translations/zh.json +6 -20
- package/translations/zh_CN.json +6 -20
- package/translations/zh_TW.json +114 -0
- package/dist/1253.js +0 -1
- package/dist/1253.js.map +0 -1
- package/dist/1268.js +0 -2
- package/dist/1268.js.map +0 -1
- package/dist/375.js +0 -1
- package/dist/375.js.map +0 -1
- package/dist/4341.js +0 -1
- package/dist/4341.js.map +0 -1
- package/dist/4687.js +0 -1
- package/dist/4687.js.map +0 -1
- package/dist/6364.js +0 -1
- package/dist/6364.js.map +0 -1
- package/dist/6473.js +0 -1
- package/dist/6473.js.map +0 -1
- package/dist/8416.js +0 -1
- package/dist/8416.js.map +0 -1
- package/src/lab-results/exported-lab-results-form.workspace.tsx +0 -264
- package/src/lab-results/lab-results-form.workspace.tsx +0 -25
- package/src/order-basket/exported-order-basket.workspace.tsx +0 -54
- package/src/order-basket/general-order-type/add-general-order/add-general-order.workspace.tsx +0 -35
- package/src/order-basket/general-order-type/add-general-order/exported-add-general-order.workspace.tsx +0 -32
- package/src/order-basket/order-basket.component.tsx +0 -213
- package/src/order-basket-action-button/order-basket-action-button.component.tsx +0 -35
- /package/dist/{1268.js.LICENSE.txt → 5134.js.LICENSE.txt} +0 -0
- /package/src/order-basket/general-order-type/{add-general-order → orderable-concept-search}/orderable-concept-search.scss +0 -0
- /package/src/order-basket/general-order-type/{add-general-order → orderable-concept-search}/search-results.scss +0 -0
|
@@ -1,40 +1,53 @@
|
|
|
1
|
-
import React, { type ComponentProps, useCallback, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import React, { type ComponentProps, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { Button, Search } from '@carbon/react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import {
|
|
5
5
|
ArrowLeftIcon,
|
|
6
|
+
launchWorkspace,
|
|
6
7
|
ResponsiveWrapper,
|
|
7
8
|
useConfig,
|
|
8
9
|
useDebounce,
|
|
9
10
|
useLayoutType,
|
|
10
|
-
type
|
|
11
|
-
Workspace2,
|
|
12
|
-
type Workspace2DefinitionProps,
|
|
11
|
+
type DefaultWorkspaceProps,
|
|
13
12
|
} from '@openmrs/esm-framework';
|
|
14
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
type DefaultPatientWorkspaceProps,
|
|
15
|
+
type OrderBasketItem,
|
|
16
|
+
useOrderBasket,
|
|
17
|
+
useOrderType,
|
|
18
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
15
19
|
import { OrderForm } from '../general-order-form/general-order-form.component';
|
|
16
20
|
import { prepOrderPostData } from '../resources';
|
|
17
21
|
import { type ConfigObject } from '../../../config-schema';
|
|
18
22
|
import OrderableConceptSearchResults from './search-results.component';
|
|
19
23
|
import styles from './orderable-concept-search.scss';
|
|
20
24
|
|
|
21
|
-
interface
|
|
22
|
-
|
|
25
|
+
interface OrderableConceptSearchWorkspaceProps extends DefaultPatientWorkspaceProps {
|
|
26
|
+
order: OrderBasketItem;
|
|
23
27
|
orderTypeUuid: string;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
orderableConceptClasses: Array<string>;
|
|
29
|
+
orderableConceptSets: Array<string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const careSettingUuid = '6f0c9a92-6f24-11e3-af88-005056821db0';
|
|
33
|
+
|
|
34
|
+
type DrugsOrOrders = Pick<OrderBasketItem, 'action' | 'concept'>;
|
|
35
|
+
|
|
36
|
+
export function ordersEqual(order1: DrugsOrOrders, order2: DrugsOrOrders) {
|
|
37
|
+
return order1.action === order2.action && order1.concept.uuid === order2.concept.uuid;
|
|
27
38
|
}
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*/
|
|
32
|
-
const AddGeneralOrder: React.FC<AddGeneralOrderProps> = ({
|
|
33
|
-
initialOrder,
|
|
40
|
+
const OrderableConceptSearchWorkspace: React.FC<OrderableConceptSearchWorkspaceProps> = ({
|
|
41
|
+
order: initialOrder,
|
|
34
42
|
orderTypeUuid,
|
|
43
|
+
closeWorkspace,
|
|
44
|
+
closeWorkspaceWithSavedChanges,
|
|
45
|
+
promptBeforeClosing,
|
|
46
|
+
setTitle,
|
|
47
|
+
patientUuid,
|
|
35
48
|
patient,
|
|
36
49
|
visitContext,
|
|
37
|
-
|
|
50
|
+
mutateVisitContext,
|
|
38
51
|
}) => {
|
|
39
52
|
const { t } = useTranslation();
|
|
40
53
|
const isTablet = useLayoutType() === 'tablet';
|
|
@@ -42,23 +55,16 @@ const AddGeneralOrder: React.FC<AddGeneralOrderProps> = ({
|
|
|
42
55
|
const { orderTypes } = useConfig<ConfigObject>();
|
|
43
56
|
const [currentOrder, setCurrentOrder] = useState(initialOrder);
|
|
44
57
|
const { orderType } = useOrderType(orderTypeUuid);
|
|
45
|
-
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
|
46
58
|
|
|
47
|
-
|
|
59
|
+
useEffect(() => {
|
|
48
60
|
if (orderType) {
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
setTitle(
|
|
62
|
+
t(`addOrderableForOrderType`, 'Add {{orderTypeDisplay}}', {
|
|
51
63
|
orderTypeDisplay: orderType.display.toLocaleLowerCase(),
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
return t(`addOrderableForOrderType`, 'Add {{orderTypeDisplay}}', {
|
|
55
|
-
orderTypeDisplay: orderType.display.toLocaleLowerCase(),
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
return '';
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
60
66
|
}
|
|
61
|
-
}, [orderType, t,
|
|
67
|
+
}, [orderType, t, setTitle]);
|
|
62
68
|
|
|
63
69
|
const orderableConceptSets = useMemo(
|
|
64
70
|
() => orderTypes.find((orderType) => orderType.orderTypeUuid === orderTypeUuid).orderableConceptSets,
|
|
@@ -66,7 +72,10 @@ const AddGeneralOrder: React.FC<AddGeneralOrderProps> = ({
|
|
|
66
72
|
);
|
|
67
73
|
|
|
68
74
|
const cancelDrugOrder = useCallback(() => {
|
|
69
|
-
closeWorkspace(
|
|
75
|
+
closeWorkspace({
|
|
76
|
+
onWorkspaceClose: () => launchWorkspace('order-basket'),
|
|
77
|
+
closeWorkspaceGroup: false,
|
|
78
|
+
});
|
|
70
79
|
}, [closeWorkspace]);
|
|
71
80
|
|
|
72
81
|
const openOrderForm = useCallback(
|
|
@@ -82,51 +91,53 @@ const AddGeneralOrder: React.FC<AddGeneralOrderProps> = ({
|
|
|
82
91
|
);
|
|
83
92
|
|
|
84
93
|
return (
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
{
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
94
|
+
<div className={styles.workspaceWrapper}>
|
|
95
|
+
{!isTablet && (
|
|
96
|
+
<div className={styles.backButton}>
|
|
97
|
+
<Button
|
|
98
|
+
iconDescription="Return to order basket"
|
|
99
|
+
kind="ghost"
|
|
100
|
+
onClick={cancelDrugOrder}
|
|
101
|
+
renderIcon={(props: ComponentProps<typeof ArrowLeftIcon>) => <ArrowLeftIcon size={24} {...props} />}
|
|
102
|
+
size="sm"
|
|
103
|
+
>
|
|
104
|
+
<span>{t('backToOrderBasket', 'Back to order basket')}</span>
|
|
105
|
+
</Button>
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
{currentOrder ? (
|
|
109
|
+
<OrderForm
|
|
110
|
+
initialOrder={currentOrder}
|
|
111
|
+
closeWorkspace={closeWorkspace}
|
|
112
|
+
closeWorkspaceWithSavedChanges={closeWorkspaceWithSavedChanges}
|
|
113
|
+
promptBeforeClosing={promptBeforeClosing}
|
|
114
|
+
orderTypeUuid={orderTypeUuid}
|
|
115
|
+
orderableConceptSets={orderableConceptSets}
|
|
116
|
+
patientUuid={patientUuid}
|
|
117
|
+
patient={patient}
|
|
118
|
+
visitContext={visitContext}
|
|
119
|
+
mutateVisitContext={mutateVisitContext}
|
|
120
|
+
setTitle={() => {}}
|
|
121
|
+
/>
|
|
122
|
+
) : (
|
|
123
|
+
<ConceptSearch
|
|
124
|
+
openOrderForm={openOrderForm}
|
|
125
|
+
closeWorkspace={closeWorkspace}
|
|
126
|
+
orderableConceptSets={orderableConceptSets}
|
|
127
|
+
orderTypeUuid={orderTypeUuid}
|
|
128
|
+
patient={patient}
|
|
129
|
+
/>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
120
132
|
);
|
|
121
133
|
};
|
|
122
134
|
|
|
123
135
|
interface ConceptSearchProps {
|
|
124
|
-
closeWorkspace:
|
|
136
|
+
closeWorkspace: DefaultWorkspaceProps['closeWorkspace'];
|
|
125
137
|
openOrderForm: (search: OrderBasketItem) => void;
|
|
126
138
|
orderTypeUuid: string;
|
|
127
139
|
orderableConceptSets: Array<string>;
|
|
128
140
|
patient: fhir.Patient;
|
|
129
|
-
visit: Visit;
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
function ConceptSearch({
|
|
@@ -135,7 +146,6 @@ function ConceptSearch({
|
|
|
135
146
|
openOrderForm,
|
|
136
147
|
orderableConceptSets,
|
|
137
148
|
patient,
|
|
138
|
-
visit,
|
|
139
149
|
}: ConceptSearchProps) {
|
|
140
150
|
const { t } = useTranslation();
|
|
141
151
|
const { orderType } = useOrderType(orderTypeUuid);
|
|
@@ -145,7 +155,9 @@ function ConceptSearch({
|
|
|
145
155
|
const searchInputRef = useRef(null);
|
|
146
156
|
|
|
147
157
|
const cancelDrugOrder = useCallback(() => {
|
|
148
|
-
closeWorkspace(
|
|
158
|
+
closeWorkspace({
|
|
159
|
+
onWorkspaceClose: () => launchWorkspace('order-basket'),
|
|
160
|
+
});
|
|
149
161
|
}, [closeWorkspace]);
|
|
150
162
|
|
|
151
163
|
const focusAndClearSearchInput = () => {
|
|
@@ -179,12 +191,11 @@ function ConceptSearch({
|
|
|
179
191
|
searchTerm={debouncedSearchTerm}
|
|
180
192
|
openOrderForm={openOrderForm}
|
|
181
193
|
focusAndClearSearchInput={focusAndClearSearchInput}
|
|
194
|
+
closeWorkspace={closeWorkspace}
|
|
182
195
|
orderTypeUuid={orderTypeUuid}
|
|
183
196
|
cancelOrder={() => {}}
|
|
184
197
|
orderableConceptSets={orderableConceptSets}
|
|
185
|
-
closeWorkspace={closeWorkspace}
|
|
186
198
|
patient={patient}
|
|
187
|
-
visit={visit}
|
|
188
199
|
/>
|
|
189
200
|
{isTablet && (
|
|
190
201
|
<div className={styles.separatorContainer}>
|
|
@@ -198,10 +209,4 @@ function ConceptSearch({
|
|
|
198
209
|
);
|
|
199
210
|
}
|
|
200
211
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
function ordersEqual(order1: DrugsOrOrders, order2: DrugsOrOrders) {
|
|
204
|
-
return order1.action === order2.action && order1.concept.uuid === order2.concept.uuid;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export default AddGeneralOrder;
|
|
212
|
+
export default OrderableConceptSearchWorkspace;
|
|
@@ -4,12 +4,12 @@ import { ShoppingCartArrowUp } from '@carbon/react/icons';
|
|
|
4
4
|
import { Tile, Button, SkeletonText, ButtonSkeleton } from '@carbon/react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import {
|
|
7
|
-
type Workspace2DefinitionProps,
|
|
8
7
|
ArrowRightIcon,
|
|
8
|
+
type DefaultWorkspaceProps,
|
|
9
9
|
ShoppingCartArrowDownIcon,
|
|
10
10
|
useLayoutType,
|
|
11
11
|
useSession,
|
|
12
|
-
|
|
12
|
+
launchWorkspace,
|
|
13
13
|
} from '@openmrs/esm-framework';
|
|
14
14
|
import {
|
|
15
15
|
useOrderBasket,
|
|
@@ -27,9 +27,8 @@ interface OrderableConceptSearchResultsProps {
|
|
|
27
27
|
cancelOrder: () => void;
|
|
28
28
|
orderableConceptSets: Array<string>;
|
|
29
29
|
orderTypeUuid: string;
|
|
30
|
-
closeWorkspace:
|
|
30
|
+
closeWorkspace: DefaultWorkspaceProps['closeWorkspace'];
|
|
31
31
|
patient: fhir.Patient;
|
|
32
|
-
visit: Visit;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
const OrderableConceptSearchResults: React.FC<OrderableConceptSearchResultsProps> = ({
|
|
@@ -41,7 +40,6 @@ const OrderableConceptSearchResults: React.FC<OrderableConceptSearchResultsProps
|
|
|
41
40
|
orderTypeUuid,
|
|
42
41
|
closeWorkspace,
|
|
43
42
|
patient,
|
|
44
|
-
visit,
|
|
45
43
|
}) => {
|
|
46
44
|
const { t } = useTranslation();
|
|
47
45
|
const isTablet = useLayoutType() === 'tablet';
|
|
@@ -94,7 +92,6 @@ const OrderableConceptSearchResults: React.FC<OrderableConceptSearchResultsProps
|
|
|
94
92
|
orderTypeUuid={orderTypeUuid}
|
|
95
93
|
closeWorkspace={closeWorkspace}
|
|
96
94
|
patient={patient}
|
|
97
|
-
visit={visit}
|
|
98
95
|
/>
|
|
99
96
|
))}
|
|
100
97
|
</div>
|
|
@@ -159,9 +156,8 @@ interface TestTypeSearchResultItemProps {
|
|
|
159
156
|
concept: OrderableConcept;
|
|
160
157
|
openOrderForm: (searchResult: OrderBasketItem) => void;
|
|
161
158
|
orderTypeUuid: string;
|
|
162
|
-
closeWorkspace:
|
|
159
|
+
closeWorkspace: DefaultWorkspaceProps['closeWorkspace'];
|
|
163
160
|
patient: fhir.Patient;
|
|
164
|
-
visit: Visit;
|
|
165
161
|
}
|
|
166
162
|
|
|
167
163
|
const TestTypeSearchResultItem: React.FC<TestTypeSearchResultItemProps> = ({
|
|
@@ -170,7 +166,6 @@ const TestTypeSearchResultItem: React.FC<TestTypeSearchResultItemProps> = ({
|
|
|
170
166
|
orderTypeUuid,
|
|
171
167
|
closeWorkspace,
|
|
172
168
|
patient,
|
|
173
|
-
visit,
|
|
174
169
|
}) => {
|
|
175
170
|
const { t } = useTranslation();
|
|
176
171
|
const isTablet = useLayoutType() === 'tablet';
|
|
@@ -183,18 +178,22 @@ const TestTypeSearchResultItem: React.FC<TestTypeSearchResultItemProps> = ({
|
|
|
183
178
|
);
|
|
184
179
|
|
|
185
180
|
const createOrderBasketItem = useCallback(
|
|
186
|
-
(testType: OrderableConcept
|
|
187
|
-
return createEmptyOrder(testType, session.currentProvider?.uuid
|
|
181
|
+
(testType: OrderableConcept) => {
|
|
182
|
+
return createEmptyOrder(testType, session.currentProvider?.uuid);
|
|
188
183
|
},
|
|
189
184
|
[session.currentProvider.uuid],
|
|
190
185
|
);
|
|
191
186
|
|
|
192
187
|
const addToBasket = useCallback(() => {
|
|
193
|
-
const orderBasketItem = createOrderBasketItem(concept
|
|
188
|
+
const orderBasketItem = createOrderBasketItem(concept);
|
|
194
189
|
orderBasketItem.isOrderIncomplete = true;
|
|
195
190
|
setOrders([...orders, orderBasketItem]);
|
|
196
|
-
closeWorkspace({
|
|
197
|
-
|
|
191
|
+
closeWorkspace({
|
|
192
|
+
ignoreChanges: true,
|
|
193
|
+
onWorkspaceClose: () => launchWorkspace('order-basket'),
|
|
194
|
+
closeWorkspaceGroup: false,
|
|
195
|
+
});
|
|
196
|
+
}, [orders, setOrders, createOrderBasketItem, concept, closeWorkspace]);
|
|
198
197
|
|
|
199
198
|
const removeFromBasket = useCallback(() => {
|
|
200
199
|
setOrders(orders.filter((order) => order?.concept?.uuid !== concept?.uuid));
|
|
@@ -233,7 +232,7 @@ const TestTypeSearchResultItem: React.FC<TestTypeSearchResultItemProps> = ({
|
|
|
233
232
|
<Button
|
|
234
233
|
kind="ghost"
|
|
235
234
|
renderIcon={(props: ComponentProps<typeof ArrowRightIcon>) => <ArrowRightIcon size={16} {...props} />}
|
|
236
|
-
onClick={() => openOrderForm(createOrderBasketItem(concept
|
|
235
|
+
onClick={() => openOrderForm(createOrderBasketItem(concept))}
|
|
237
236
|
>
|
|
238
237
|
{t('goToDrugOrderForm', 'Order form')}
|
|
239
238
|
</Button>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toOmrsIsoString
|
|
1
|
+
import { toOmrsIsoString } from '@openmrs/esm-framework';
|
|
2
2
|
import {
|
|
3
3
|
type OrderBasketItem,
|
|
4
4
|
priorityOptions,
|
|
@@ -7,14 +7,13 @@ import {
|
|
|
7
7
|
type OrderableConcept,
|
|
8
8
|
} from '@openmrs/esm-patient-common-lib';
|
|
9
9
|
|
|
10
|
-
export function createEmptyOrder(concept: OrderableConcept, orderer: string
|
|
10
|
+
export function createEmptyOrder(concept: OrderableConcept, orderer: string): 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,
|
|
18
17
|
};
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -1,46 +1,262 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useCallback, useEffect, useState } from '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';
|
|
2
6
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
7
|
+
ExtensionSlot,
|
|
8
|
+
showModal,
|
|
9
|
+
showSnackbar,
|
|
10
|
+
useConfig,
|
|
11
|
+
useLayoutType,
|
|
12
|
+
useSession,
|
|
13
|
+
type Visit,
|
|
14
|
+
} from '@openmrs/esm-framework';
|
|
15
|
+
import {
|
|
16
|
+
type DefaultPatientWorkspaceProps,
|
|
5
17
|
type OrderBasketItem,
|
|
6
|
-
|
|
18
|
+
invalidateVisitAndEncounterData,
|
|
19
|
+
postOrders,
|
|
20
|
+
postOrdersOnNewEncounter,
|
|
21
|
+
useOrderBasket,
|
|
7
22
|
} from '@openmrs/esm-patient-common-lib';
|
|
8
|
-
import
|
|
23
|
+
import { useSWRConfig } from 'swr';
|
|
24
|
+
import { type ConfigObject } from '../config-schema';
|
|
25
|
+
import { useMutatePatientOrders, useOrderEncounter } from '../api/api';
|
|
26
|
+
import GeneralOrderType from './general-order-type/general-order-type.component';
|
|
27
|
+
import styles from './order-basket.scss';
|
|
28
|
+
|
|
29
|
+
interface OrderBasketSlotProps {
|
|
30
|
+
patientUuid: string;
|
|
31
|
+
patient: fhir.Patient;
|
|
32
|
+
visitContext: Visit;
|
|
33
|
+
mutateVisitContext: () => void;
|
|
34
|
+
}
|
|
9
35
|
|
|
10
|
-
const
|
|
11
|
-
|
|
36
|
+
const OrderBasket: React.FC<DefaultPatientWorkspaceProps> = ({
|
|
37
|
+
patientUuid,
|
|
38
|
+
patient,
|
|
12
39
|
closeWorkspace,
|
|
13
|
-
|
|
40
|
+
closeWorkspaceWithSavedChanges,
|
|
41
|
+
promptBeforeClosing,
|
|
42
|
+
visitContext,
|
|
43
|
+
mutateVisitContext,
|
|
14
44
|
}) => {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
45
|
+
const { t } = useTranslation();
|
|
46
|
+
const isTablet = useLayoutType() === 'tablet';
|
|
47
|
+
const config = useConfig<ConfigObject>();
|
|
48
|
+
const session = useSession();
|
|
49
|
+
const { orders, clearOrders } = useOrderBasket(patient);
|
|
50
|
+
const [ordersWithErrors, setOrdersWithErrors] = useState<OrderBasketItem[]>([]);
|
|
51
|
+
const {
|
|
52
|
+
visitRequired,
|
|
53
|
+
isLoading: isLoadingEncounterUuid,
|
|
54
|
+
encounterUuid,
|
|
55
|
+
error: errorFetchingEncounterUuid,
|
|
56
|
+
mutate: mutateEncounterUuid,
|
|
57
|
+
} = useOrderEncounter(patientUuid, visitContext, mutateVisitContext, config.orderEncounterType);
|
|
58
|
+
const [isSavingOrders, setIsSavingOrders] = useState(false);
|
|
59
|
+
const [creatingEncounterError, setCreatingEncounterError] = useState('');
|
|
60
|
+
const { mutate: mutateOrders } = useMutatePatientOrders(patientUuid);
|
|
61
|
+
const { mutate } = useSWRConfig();
|
|
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;
|
|
33
148
|
|
|
34
149
|
return (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
150
|
+
<>
|
|
151
|
+
<div className={styles.container}>
|
|
152
|
+
<ExtensionSlot name="visit-context-header-slot" state={extensionProps} />
|
|
153
|
+
<div className={styles.orderBasketContainer}>
|
|
154
|
+
<ExtensionSlot
|
|
155
|
+
className={classNames(styles.orderBasketSlot, {
|
|
156
|
+
[styles.orderBasketSlotTablet]: isTablet,
|
|
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
|
+
</>
|
|
43
234
|
);
|
|
44
235
|
};
|
|
45
236
|
|
|
46
|
-
|
|
237
|
+
function showOrderSuccessToast(t: TFunction, patientOrderItems: OrderBasketItem[]) {
|
|
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;
|