@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.
Files changed (153) hide show
  1. package/.turbo/turbo-build.log +15 -18
  2. package/dist/1119.js +1 -1
  3. package/dist/1197.js +1 -1
  4. package/dist/1571.js +1 -0
  5. package/dist/1571.js.map +1 -0
  6. package/dist/2146.js +1 -1
  7. package/dist/2690.js +1 -1
  8. package/dist/2717.js +1 -0
  9. package/dist/2717.js.map +1 -0
  10. package/dist/3099.js +1 -1
  11. package/dist/3584.js +1 -1
  12. package/dist/4055.js +1 -1
  13. package/dist/4132.js +1 -1
  14. package/dist/4300.js +1 -1
  15. package/dist/4335.js +1 -1
  16. package/dist/439.js +1 -0
  17. package/dist/4618.js +1 -1
  18. package/dist/4652.js +1 -1
  19. package/dist/4937.js +1 -1
  20. package/dist/4937.js.map +1 -1
  21. package/dist/4944.js +1 -1
  22. package/dist/5134.js +2 -0
  23. package/dist/5134.js.map +1 -0
  24. package/dist/5173.js +1 -1
  25. package/dist/5241.js +1 -1
  26. package/dist/5442.js +1 -1
  27. package/dist/5661.js +1 -1
  28. package/dist/5670.js +1 -1
  29. package/dist/5670.js.map +1 -1
  30. package/dist/6022.js +1 -1
  31. package/dist/6468.js +1 -1
  32. package/dist/6589.js +1 -0
  33. package/dist/6679.js +1 -1
  34. package/dist/6840.js +1 -1
  35. package/dist/6859.js +1 -1
  36. package/dist/7097.js +1 -1
  37. package/dist/7159.js +1 -1
  38. package/dist/723.js +1 -1
  39. package/dist/7617.js +1 -1
  40. package/dist/795.js +1 -1
  41. package/dist/8163.js +1 -1
  42. package/dist/8349.js +1 -1
  43. package/dist/8371.js +1 -0
  44. package/dist/8618.js +1 -1
  45. package/dist/8625.js +1 -0
  46. package/dist/8625.js.map +1 -0
  47. package/dist/8803.js +1 -0
  48. package/dist/8803.js.map +1 -0
  49. package/dist/890.js +1 -1
  50. package/dist/8960.js +1 -0
  51. package/dist/8960.js.map +1 -0
  52. package/dist/9214.js +1 -1
  53. package/dist/9538.js +1 -1
  54. package/dist/9569.js +1 -1
  55. package/dist/986.js +1 -1
  56. package/dist/9879.js +1 -1
  57. package/dist/9895.js +1 -1
  58. package/dist/9900.js +1 -1
  59. package/dist/9913.js +1 -1
  60. package/dist/main.js +1 -1
  61. package/dist/main.js.map +1 -1
  62. package/dist/openmrs-esm-patient-orders-app.js +1 -1
  63. package/dist/openmrs-esm-patient-orders-app.js.buildmanifest.json +276 -259
  64. package/dist/openmrs-esm-patient-orders-app.js.map +1 -1
  65. package/dist/routes.json +1 -1
  66. package/package.json +2 -2
  67. package/src/api/api.ts +25 -0
  68. package/src/components/order-details-table.component.tsx +40 -53
  69. package/src/components/test-order.component.tsx +53 -49
  70. package/src/index.ts +9 -12
  71. package/src/lab-results/lab-results-form.component.tsx +343 -0
  72. package/src/lab-results/lab-results-form.test.tsx +455 -306
  73. package/src/lab-results/lab-results-schema.resource.tsx +13 -4
  74. package/src/lab-results/lab-results.resource.ts +113 -0
  75. package/src/order-basket/general-order-type/general-order-form/general-order-form.component.tsx +25 -71
  76. package/src/order-basket/general-order-type/{general-order-panel.component.tsx → general-order-type.component.tsx} +41 -23
  77. 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
  78. package/src/order-basket/general-order-type/{add-general-order → orderable-concept-search}/search-results.component.tsx +14 -15
  79. package/src/order-basket/general-order-type/resources.ts +2 -3
  80. package/src/order-basket/order-basket.workspace.tsx +251 -35
  81. package/src/order-basket-action-button/order-basket-action-button.extension.tsx +31 -0
  82. package/src/order-basket-action-button/order-basket-action-button.test.tsx +36 -27
  83. package/src/order-cancellation-form/cancel-order-form.component.tsx +85 -82
  84. package/src/routes.json +25 -17
  85. package/src/utils/index.ts +3 -15
  86. package/translations/am.json +6 -20
  87. package/translations/ar.json +6 -20
  88. package/translations/ar_SY.json +6 -20
  89. package/translations/bn.json +6 -20
  90. package/translations/cs.json +114 -0
  91. package/translations/de.json +6 -20
  92. package/translations/en.json +12 -4
  93. package/translations/en_US.json +6 -20
  94. package/translations/es.json +6 -20
  95. package/translations/es_MX.json +6 -20
  96. package/translations/fr.json +7 -21
  97. package/translations/he.json +6 -20
  98. package/translations/hi.json +6 -20
  99. package/translations/hi_IN.json +6 -20
  100. package/translations/id.json +6 -20
  101. package/translations/it.json +6 -20
  102. package/translations/ka.json +6 -20
  103. package/translations/km.json +6 -20
  104. package/translations/ku.json +6 -20
  105. package/translations/ky.json +6 -20
  106. package/translations/lg.json +6 -20
  107. package/translations/ne.json +6 -20
  108. package/translations/pl.json +6 -20
  109. package/translations/pt.json +6 -20
  110. package/translations/pt_BR.json +6 -20
  111. package/translations/qu.json +6 -20
  112. package/translations/ro_RO.json +6 -20
  113. package/translations/ru_RU.json +6 -20
  114. package/translations/si.json +6 -20
  115. package/translations/sq.json +114 -0
  116. package/translations/sw.json +6 -20
  117. package/translations/sw_KE.json +6 -20
  118. package/translations/tr.json +6 -20
  119. package/translations/tr_TR.json +6 -20
  120. package/translations/uk.json +6 -20
  121. package/translations/uz.json +6 -20
  122. package/translations/uz@Latn.json +6 -20
  123. package/translations/uz_UZ.json +6 -20
  124. package/translations/vi.json +6 -20
  125. package/translations/zh.json +6 -20
  126. package/translations/zh_CN.json +6 -20
  127. package/translations/zh_TW.json +114 -0
  128. package/dist/1253.js +0 -1
  129. package/dist/1253.js.map +0 -1
  130. package/dist/1268.js +0 -2
  131. package/dist/1268.js.map +0 -1
  132. package/dist/375.js +0 -1
  133. package/dist/375.js.map +0 -1
  134. package/dist/4341.js +0 -1
  135. package/dist/4341.js.map +0 -1
  136. package/dist/4687.js +0 -1
  137. package/dist/4687.js.map +0 -1
  138. package/dist/6364.js +0 -1
  139. package/dist/6364.js.map +0 -1
  140. package/dist/6473.js +0 -1
  141. package/dist/6473.js.map +0 -1
  142. package/dist/8416.js +0 -1
  143. package/dist/8416.js.map +0 -1
  144. package/src/lab-results/exported-lab-results-form.workspace.tsx +0 -264
  145. package/src/lab-results/lab-results-form.workspace.tsx +0 -25
  146. package/src/order-basket/exported-order-basket.workspace.tsx +0 -54
  147. package/src/order-basket/general-order-type/add-general-order/add-general-order.workspace.tsx +0 -35
  148. package/src/order-basket/general-order-type/add-general-order/exported-add-general-order.workspace.tsx +0 -32
  149. package/src/order-basket/order-basket.component.tsx +0 -213
  150. package/src/order-basket-action-button/order-basket-action-button.component.tsx +0 -35
  151. /package/dist/{1268.js.LICENSE.txt → 5134.js.LICENSE.txt} +0 -0
  152. /package/src/order-basket/general-order-type/{add-general-order → orderable-concept-search}/orderable-concept-search.scss +0 -0
  153. /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 Visit,
11
- Workspace2,
12
- type Workspace2DefinitionProps,
11
+ type DefaultWorkspaceProps,
13
12
  } from '@openmrs/esm-framework';
14
- import { type OrderBasketItem, useOrderBasket, useOrderType } from '@openmrs/esm-patient-common-lib';
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 AddGeneralOrderProps {
22
- initialOrder: OrderBasketItem;
25
+ interface OrderableConceptSearchWorkspaceProps extends DefaultPatientWorkspaceProps {
26
+ order: OrderBasketItem;
23
27
  orderTypeUuid: string;
24
- patient: fhir.Patient;
25
- visitContext: Visit;
26
- closeWorkspace: Workspace2DefinitionProps['closeWorkspace'];
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
- * This workspace displays the drug order form for adding or editing a general order.
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
- closeWorkspace,
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
- const title = useMemo(() => {
59
+ useEffect(() => {
48
60
  if (orderType) {
49
- if (initialOrder?.action == 'REVISE') {
50
- return t(`editOrderableForOrderType`, 'Edit {{orderTypeDisplay}}', {
61
+ setTitle(
62
+ t(`addOrderableForOrderType`, 'Add {{orderTypeDisplay}}', {
51
63
  orderTypeDisplay: orderType.display.toLocaleLowerCase(),
52
- });
53
- } else {
54
- return t(`addOrderableForOrderType`, 'Add {{orderTypeDisplay}}', {
55
- orderTypeDisplay: orderType.display.toLocaleLowerCase(),
56
- });
57
- }
58
- } else {
59
- return '';
64
+ }),
65
+ );
60
66
  }
61
- }, [orderType, t, initialOrder?.action]);
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
- <Workspace2 title={title} hasUnsavedChanges={hasUnsavedChanges}>
86
- <div className={styles.workspaceWrapper}>
87
- {!isTablet && (
88
- <div className={styles.backButton}>
89
- <Button
90
- iconDescription="Return to order basket"
91
- kind="ghost"
92
- onClick={cancelDrugOrder}
93
- renderIcon={(props: ComponentProps<typeof ArrowLeftIcon>) => <ArrowLeftIcon size={24} {...props} />}
94
- size="sm"
95
- >
96
- <span>{t('backToOrderBasket', 'Back to order basket')}</span>
97
- </Button>
98
- </div>
99
- )}
100
- {currentOrder ? (
101
- <OrderForm
102
- initialOrder={currentOrder}
103
- closeWorkspace={closeWorkspace}
104
- setHasUnsavedChanges={setHasUnsavedChanges}
105
- orderTypeUuid={orderTypeUuid}
106
- patient={patient}
107
- />
108
- ) : (
109
- <ConceptSearch
110
- openOrderForm={openOrderForm}
111
- closeWorkspace={closeWorkspace}
112
- orderableConceptSets={orderableConceptSets}
113
- orderTypeUuid={orderTypeUuid}
114
- patient={patient}
115
- visit={visitContext}
116
- />
117
- )}
118
- </div>
119
- </Workspace2>
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: Workspace2DefinitionProps['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
- type DrugsOrOrders = Pick<OrderBasketItem, 'action' | 'concept'>;
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
- type Visit,
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: Workspace2DefinitionProps['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: Workspace2DefinitionProps['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, visit: Visit) => {
187
- return createEmptyOrder(testType, session.currentProvider?.uuid, visit);
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, visit);
188
+ const orderBasketItem = createOrderBasketItem(concept);
194
189
  orderBasketItem.isOrderIncomplete = true;
195
190
  setOrders([...orders, orderBasketItem]);
196
- closeWorkspace({ discardUnsavedChanges: true });
197
- }, [orders, setOrders, createOrderBasketItem, concept, closeWorkspace, visit]);
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, visit))}
235
+ onClick={() => openOrderForm(createOrderBasketItem(concept))}
237
236
  >
238
237
  {t('goToDrugOrderForm', 'Order form')}
239
238
  </Button>
@@ -1,4 +1,4 @@
1
- import { toOmrsIsoString, type Visit } from '@openmrs/esm-framework';
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, visit: Visit): OrderBasketItem {
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, { useMemo } from '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
- type OrderBasketExtensionProps,
4
- type OrderBasketWindowProps,
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
- type PatientWorkspace2DefinitionProps,
18
+ invalidateVisitAndEncounterData,
19
+ postOrders,
20
+ postOrdersOnNewEncounter,
21
+ useOrderBasket,
7
22
  } from '@openmrs/esm-patient-common-lib';
8
- import OrderBasket from './order-basket.component';
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 OrderBasketWorkspace: React.FC<PatientWorkspace2DefinitionProps<{}, OrderBasketWindowProps>> = ({
11
- groupProps: { patientUuid, patient, visitContext, mutateVisitContext },
36
+ const OrderBasket: React.FC<DefaultPatientWorkspaceProps> = ({
37
+ patientUuid,
38
+ patient,
12
39
  closeWorkspace,
13
- launchChildWorkspace,
40
+ closeWorkspaceWithSavedChanges,
41
+ promptBeforeClosing,
42
+ visitContext,
43
+ mutateVisitContext,
14
44
  }) => {
15
- const orderBasketExtensionProps = useMemo(() => {
16
- const launchDrugOrderForm = (order: OrderBasketItem) => {
17
- launchChildWorkspace('add-drug-order', { order });
18
- };
19
- const launchLabOrderForm = (orderTypeUuid: string, order: OrderBasketItem) => {
20
- launchChildWorkspace('add-lab-order', { orderTypeUuid, order });
21
- };
22
- const launchGeneralOrderForm = (orderTypeUuid: string, order: OrderBasketItem) => {
23
- launchChildWorkspace('orderable-concept-workspace', { orderTypeUuid, order });
24
- };
25
-
26
- return {
27
- patient,
28
- launchDrugOrderForm,
29
- launchLabOrderForm,
30
- launchGeneralOrderForm,
31
- } satisfies OrderBasketExtensionProps;
32
- }, [launchChildWorkspace, patient]);
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
- <OrderBasket
36
- patientUuid={patientUuid}
37
- patient={patient}
38
- visitContext={visitContext}
39
- mutateVisitContext={mutateVisitContext}
40
- closeWorkspace={closeWorkspace}
41
- orderBasketExtensionProps={orderBasketExtensionProps}
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
- export default OrderBasketWorkspace;
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;