@openmrs/esm-patient-tests-app 11.3.1-patch.9064 → 11.3.1-patch.9310
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 +22 -19
- package/dist/1479.js +1 -1
- package/dist/1479.js.map +1 -1
- package/dist/3509.js +1 -1
- package/dist/4055.js +1 -1
- package/dist/4300.js +1 -1
- package/dist/{1935.js → 5348.js} +1 -1
- package/dist/5348.js.map +1 -0
- package/dist/5670.js +1 -1
- package/dist/5670.js.map +1 -1
- package/dist/6231.js +1 -1
- package/dist/6231.js.map +1 -1
- package/dist/6301.js +1 -1
- package/dist/6301.js.map +1 -1
- package/dist/6336.js +1 -0
- package/dist/6336.js.map +1 -0
- package/dist/790.js +1 -1
- package/dist/790.js.map +1 -1
- package/dist/8307.js +2 -0
- package/dist/8307.js.map +1 -0
- package/dist/9540.js +2 -0
- package/dist/9540.js.map +1 -0
- package/dist/9838.js +1 -0
- package/dist/9838.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-tests-app.js +1 -1
- package/dist/openmrs-esm-patient-tests-app.js.buildmanifest.json +172 -193
- package/dist/openmrs-esm-patient-tests-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +3 -3
- package/src/edit-test-results/modal/edit-lab-results.modal.tsx +8 -4
- package/src/routes.json +3 -4
- package/src/test-orders/add-test-order/add-test-order.component.tsx +125 -0
- package/src/test-orders/add-test-order/add-test-order.test.tsx +23 -43
- package/src/test-orders/add-test-order/add-test-order.workspace.tsx +21 -116
- package/src/test-orders/add-test-order/exported-add-test-order.workspace.tsx +30 -0
- package/src/test-orders/add-test-order/test-order-form.component.tsx +67 -25
- package/src/test-orders/add-test-order/test-order.ts +3 -3
- package/src/test-orders/add-test-order/test-type-search.component.tsx +40 -24
- package/src/test-orders/api.ts +6 -2
- package/src/test-orders/lab-order-basket-panel/lab-order-basket-item-tile.component.tsx +1 -1
- package/src/test-orders/lab-order-basket-panel/lab-order-basket-panel.extension.tsx +30 -48
- package/src/test-orders/lab-order-basket-panel/lab-order-basket-panel.test.tsx +15 -4
- package/src/test-results/filter/filter-types.ts +19 -0
- package/src/test-results/grouped-timeline/grouped-timeline.test.tsx +49 -0
- package/src/test-results/grouped-timeline/reference-range-helpers.test.ts +272 -0
- package/src/test-results/grouped-timeline/reference-range-helpers.ts +112 -0
- package/src/test-results/grouped-timeline/timeline-data-group.component.tsx +10 -3
- package/src/test-results/grouped-timeline/useObstreeData.ts +71 -9
- package/src/test-results/individual-results-table/individual-results-table.component.tsx +23 -6
- package/src/test-results/individual-results-table/individual-results-table.test.tsx +65 -3
- package/src/test-results/individual-results-table-tablet/helper.tsx +8 -2
- package/src/test-results/individual-results-table-tablet/individual-results-table-tablet.component.tsx +2 -2
- package/src/test-results/individual-results-table-tablet/lab-set-panel.component.tsx +5 -1
- package/src/test-results/loadPatientTestData/helpers.test.ts +834 -0
- package/src/test-results/loadPatientTestData/helpers.ts +114 -0
- package/src/test-results/loadPatientTestData/loadPatientData.ts +66 -11
- package/src/test-results/loadPatientTestData/usePatientResultsData.ts +3 -3
- package/src/test-results/overview/common-datatable.component.tsx +1 -1
- package/src/test-results/overview/useOverviewData.ts +22 -10
- package/src/test-results/results-viewer/results-viewer.extension.tsx +4 -3
- package/src/test-results/tree-view/tree-view.component.tsx +14 -4
- package/src/test-results/trendline/trendline-resource.tsx +48 -5
- package/src/types.ts +20 -10
- package/translations/en.json +2 -0
- package/translations/fr.json +2 -2
- package/dist/1935.js.map +0 -1
- package/dist/2537.js +0 -1
- package/dist/2537.js.map +0 -1
- package/dist/34.js +0 -1
- package/dist/34.js.map +0 -1
- package/dist/4918.js +0 -1
- package/dist/4918.js.map +0 -1
- package/dist/5836.js +0 -2
- package/dist/5836.js.map +0 -1
- package/dist/7053.js +0 -2
- package/dist/7053.js.map +0 -1
- /package/dist/{7053.js.LICENSE.txt → 8307.js.LICENSE.txt} +0 -0
- /package/dist/{5836.js.LICENSE.txt → 9540.js.LICENSE.txt} +0 -0
|
@@ -19,30 +19,36 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
19
19
|
import { z } from 'zod';
|
|
20
20
|
import { useTranslation } from 'react-i18next';
|
|
21
21
|
import {
|
|
22
|
-
type DefaultPatientWorkspaceProps,
|
|
23
22
|
type OrderUrgency,
|
|
24
23
|
priorityOptions,
|
|
25
24
|
useOrderBasket,
|
|
26
25
|
useOrderType,
|
|
26
|
+
type TestOrderBasketItem,
|
|
27
|
+
postOrder,
|
|
28
|
+
useMutatePatientOrders,
|
|
29
|
+
showOrderSuccessToast,
|
|
27
30
|
} from '@openmrs/esm-patient-common-lib';
|
|
28
31
|
import {
|
|
29
32
|
ExtensionSlot,
|
|
30
|
-
launchWorkspace,
|
|
31
33
|
OpenmrsDatePicker,
|
|
34
|
+
showSnackbar,
|
|
32
35
|
useConfig,
|
|
33
36
|
useLayoutType,
|
|
34
37
|
useSession,
|
|
38
|
+
type Workspace2DefinitionProps,
|
|
35
39
|
} from '@openmrs/esm-framework';
|
|
36
40
|
import { prepTestOrderPostData, useOrderReasons } from '../api';
|
|
37
41
|
import { ordersEqual } from './test-order';
|
|
38
42
|
import { type ConfigObject } from '../../config-schema';
|
|
39
|
-
import { type TestOrderBasketItem } from '../../types';
|
|
40
43
|
import styles from './test-order-form.scss';
|
|
41
44
|
|
|
42
|
-
export interface LabOrderFormProps
|
|
45
|
+
export interface LabOrderFormProps {
|
|
46
|
+
closeWorkspace: Workspace2DefinitionProps['closeWorkspace'];
|
|
43
47
|
initialOrder: TestOrderBasketItem;
|
|
44
48
|
orderTypeUuid: string;
|
|
45
49
|
orderableConceptSets: Array<string>;
|
|
50
|
+
setHasUnsavedChanges: (hasUnsavedChanges: boolean) => void;
|
|
51
|
+
patient: fhir.Patient;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
// Designs:
|
|
@@ -51,20 +57,22 @@ export interface LabOrderFormProps extends DefaultPatientWorkspaceProps {
|
|
|
51
57
|
export function LabOrderForm({
|
|
52
58
|
initialOrder,
|
|
53
59
|
closeWorkspace,
|
|
54
|
-
closeWorkspaceWithSavedChanges,
|
|
55
|
-
promptBeforeClosing,
|
|
56
60
|
orderTypeUuid,
|
|
57
|
-
|
|
61
|
+
setHasUnsavedChanges,
|
|
62
|
+
patient,
|
|
58
63
|
}: LabOrderFormProps) {
|
|
59
64
|
const { t } = useTranslation();
|
|
60
65
|
const isTablet = useLayoutType() === 'tablet';
|
|
61
66
|
const session = useSession();
|
|
62
|
-
const
|
|
63
|
-
|
|
67
|
+
const { orders, setOrders, clearOrders } = useOrderBasket<TestOrderBasketItem>(
|
|
68
|
+
patient,
|
|
69
|
+
orderTypeUuid,
|
|
70
|
+
prepTestOrderPostData,
|
|
71
|
+
);
|
|
64
72
|
const [showErrorNotification, setShowErrorNotification] = useState(false);
|
|
65
73
|
const config = useConfig<ConfigObject>();
|
|
66
|
-
const { orderType
|
|
67
|
-
|
|
74
|
+
const { orderType } = useOrderType(orderTypeUuid);
|
|
75
|
+
const { mutate: mutateOrders } = useMutatePatientOrders(patient.id);
|
|
68
76
|
const orderReasonRequired = useMemo(
|
|
69
77
|
() =>
|
|
70
78
|
(config.labTestsWithOrderReasons?.find((c) => c.labTestUuid === initialOrder?.testType?.conceptUuid) || {})
|
|
@@ -136,7 +144,7 @@ export function LabOrderForm({
|
|
|
136
144
|
return itemDisplay?.includes(inputValue);
|
|
137
145
|
}, []);
|
|
138
146
|
|
|
139
|
-
const
|
|
147
|
+
const saveLabOrderToBasket = useCallback(
|
|
140
148
|
(data: TestOrderBasketItem) => {
|
|
141
149
|
const finalizedOrder: TestOrderBasketItem = {
|
|
142
150
|
...initialOrder,
|
|
@@ -159,22 +167,47 @@ export function LabOrderForm({
|
|
|
159
167
|
|
|
160
168
|
setOrders(newOrders);
|
|
161
169
|
|
|
162
|
-
|
|
163
|
-
onWorkspaceClose: () => launchWorkspace('order-basket'),
|
|
164
|
-
closeWorkspaceGroup: false,
|
|
165
|
-
});
|
|
170
|
+
closeWorkspace({ discardUnsavedChanges: true });
|
|
166
171
|
},
|
|
167
|
-
[orders, setOrders, session?.currentProvider?.uuid,
|
|
172
|
+
[orders, setOrders, session?.currentProvider?.uuid, closeWorkspace, initialOrder],
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const submitLabOrderToServer = useCallback(
|
|
176
|
+
(data: TestOrderBasketItem) => {
|
|
177
|
+
const finalizedOrder: TestOrderBasketItem = {
|
|
178
|
+
...initialOrder,
|
|
179
|
+
...data,
|
|
180
|
+
};
|
|
181
|
+
finalizedOrder.orderer = session.currentProvider.uuid;
|
|
182
|
+
postOrder(prepTestOrderPostData(finalizedOrder, patient.id, finalizedOrder?.encounterUuid))
|
|
183
|
+
.then(() => {
|
|
184
|
+
clearOrders();
|
|
185
|
+
mutateOrders();
|
|
186
|
+
showOrderSuccessToast(t, [finalizedOrder]);
|
|
187
|
+
closeWorkspace({ discardUnsavedChanges: true });
|
|
188
|
+
})
|
|
189
|
+
.catch((error) => {
|
|
190
|
+
showSnackbar({
|
|
191
|
+
isLowContrast: false,
|
|
192
|
+
kind: 'error',
|
|
193
|
+
title: t('errorSavingLabOrder', 'Error saving lab order'),
|
|
194
|
+
subtitle: error.message,
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
[clearOrders, closeWorkspace, initialOrder, mutateOrders, patient.id, session.currentProvider.uuid, t],
|
|
168
199
|
);
|
|
169
200
|
|
|
170
201
|
const cancelOrder = useCallback(() => {
|
|
171
202
|
setOrders(orders.filter((order) => order.testType.conceptUuid !== defaultValues.testType.conceptUuid));
|
|
172
|
-
closeWorkspace(
|
|
173
|
-
onWorkspaceClose: () => launchWorkspace('order-basket'),
|
|
174
|
-
closeWorkspaceGroup: false,
|
|
175
|
-
});
|
|
203
|
+
closeWorkspace();
|
|
176
204
|
}, [closeWorkspace, orders, setOrders, defaultValues]);
|
|
177
205
|
|
|
206
|
+
const closeModifyOrderWorkspace = useCallback(() => {
|
|
207
|
+
clearOrders();
|
|
208
|
+
closeWorkspace();
|
|
209
|
+
}, [clearOrders, closeWorkspace]);
|
|
210
|
+
|
|
178
211
|
const onError = (errors: FieldErrors<TestOrderBasketItem>) => {
|
|
179
212
|
if (errors) {
|
|
180
213
|
setShowErrorNotification(true);
|
|
@@ -195,13 +228,17 @@ export function LabOrderForm({
|
|
|
195
228
|
);
|
|
196
229
|
|
|
197
230
|
useEffect(() => {
|
|
198
|
-
|
|
199
|
-
}, [isDirty,
|
|
231
|
+
setHasUnsavedChanges(isDirty);
|
|
232
|
+
}, [isDirty, setHasUnsavedChanges]);
|
|
200
233
|
|
|
201
234
|
const responsiveSize = isTablet ? 'lg' : 'sm';
|
|
202
235
|
|
|
203
236
|
return (
|
|
204
|
-
<Form
|
|
237
|
+
<Form
|
|
238
|
+
className={styles.orderForm}
|
|
239
|
+
onSubmit={handleSubmit(initialOrder?.action == 'REVISE' ? submitLabOrderToServer : saveLabOrderToBasket, onError)}
|
|
240
|
+
id="drugOrderForm"
|
|
241
|
+
>
|
|
205
242
|
<div className={styles.form}>
|
|
206
243
|
<ExtensionSlot name="top-of-lab-order-form-slot" state={{ order: initialOrder }} />
|
|
207
244
|
<Grid className={styles.gridRow}>
|
|
@@ -351,7 +388,12 @@ export function LabOrderForm({
|
|
|
351
388
|
<ButtonSet
|
|
352
389
|
className={classNames(styles.buttonSet, isTablet ? styles.tabletButtonSet : styles.desktopButtonSet)}
|
|
353
390
|
>
|
|
354
|
-
<Button
|
|
391
|
+
<Button
|
|
392
|
+
className={styles.button}
|
|
393
|
+
kind="secondary"
|
|
394
|
+
onClick={initialOrder?.action == 'REVISE' ? closeModifyOrderWorkspace : cancelOrder}
|
|
395
|
+
size="xl"
|
|
396
|
+
>
|
|
355
397
|
{t('discard', 'Discard')}
|
|
356
398
|
</Button>
|
|
357
399
|
<Button className={styles.button} kind="primary" size="xl" type="submit">
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { priorityOptions, type OrderUrgency } from '@openmrs/esm-patient-common-lib';
|
|
1
|
+
import { priorityOptions, type OrderUrgency, type TestOrderBasketItem } from '@openmrs/esm-patient-common-lib';
|
|
2
2
|
import { type TestType } from './useTestTypes';
|
|
3
|
-
import type { TestOrderBasketItem } from '../../types';
|
|
4
3
|
|
|
5
4
|
type LabOrderRequest = Pick<TestOrderBasketItem, 'action' | 'testType'>;
|
|
6
5
|
|
|
7
|
-
export function createEmptyLabOrder(testType: TestType, orderer: string): TestOrderBasketItem {
|
|
6
|
+
export function createEmptyLabOrder(testType: TestType, orderer: string, visit): TestOrderBasketItem {
|
|
8
7
|
return {
|
|
9
8
|
action: 'NEW',
|
|
10
9
|
urgency: priorityOptions[0].value as OrderUrgency,
|
|
11
10
|
display: testType.label,
|
|
12
11
|
testType,
|
|
13
12
|
orderer,
|
|
13
|
+
visit,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -5,16 +5,15 @@ import { Button, ButtonSkeleton, Search, SkeletonText, Tile } from '@carbon/reac
|
|
|
5
5
|
import { ShoppingCartArrowUp } from '@carbon/react/icons';
|
|
6
6
|
import {
|
|
7
7
|
ArrowRightIcon,
|
|
8
|
-
closeWorkspace,
|
|
9
|
-
launchWorkspace,
|
|
10
8
|
ResponsiveWrapper,
|
|
11
9
|
ShoppingCartArrowDownIcon,
|
|
12
10
|
useDebounce,
|
|
13
11
|
useLayoutType,
|
|
14
12
|
useSession,
|
|
13
|
+
type Visit,
|
|
14
|
+
type Workspace2DefinitionProps,
|
|
15
15
|
} from '@openmrs/esm-framework';
|
|
16
|
-
import { useOrderBasket } from '@openmrs/esm-patient-common-lib';
|
|
17
|
-
import type { TestOrderBasketItem } from '../../types';
|
|
16
|
+
import { useOrderBasket, type TestOrderBasketItem } from '@openmrs/esm-patient-common-lib';
|
|
18
17
|
import { prepTestOrderPostData } from '../api';
|
|
19
18
|
import { createEmptyLabOrder } from './test-order';
|
|
20
19
|
import { useTestTypes, type TestType } from './useTestTypes';
|
|
@@ -24,21 +23,34 @@ export interface TestTypeSearchProps {
|
|
|
24
23
|
openLabForm: (searchResult: TestOrderBasketItem) => void;
|
|
25
24
|
orderTypeUuid: string;
|
|
26
25
|
orderableConceptSets: Array<string>;
|
|
26
|
+
closeWorkspace: Workspace2DefinitionProps['closeWorkspace'];
|
|
27
|
+
patient: fhir.Patient;
|
|
28
|
+
visit: Visit;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
interface TestTypeSearchResultsProps extends TestTypeSearchProps {
|
|
30
|
-
cancelOrder: () => void;
|
|
31
32
|
searchTerm: string;
|
|
32
33
|
focusAndClearSearchInput: () => void;
|
|
34
|
+
patient: fhir.Patient;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
interface TestTypeSearchResultItemProps {
|
|
36
38
|
orderTypeUuid: string;
|
|
37
39
|
testType: TestType;
|
|
38
40
|
openOrderForm: (searchResult: TestOrderBasketItem) => void;
|
|
41
|
+
patient: fhir.Patient;
|
|
42
|
+
visit: Visit;
|
|
43
|
+
closeWorkspace: Workspace2DefinitionProps['closeWorkspace'];
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
export function TestTypeSearch({
|
|
46
|
+
export function TestTypeSearch({
|
|
47
|
+
patient,
|
|
48
|
+
visit,
|
|
49
|
+
openLabForm,
|
|
50
|
+
orderTypeUuid,
|
|
51
|
+
orderableConceptSets,
|
|
52
|
+
closeWorkspace,
|
|
53
|
+
}: TestTypeSearchProps) {
|
|
42
54
|
const { t } = useTranslation();
|
|
43
55
|
const [searchTerm, setSearchTerm] = useState('');
|
|
44
56
|
const debouncedSearchTerm = useDebounce(searchTerm);
|
|
@@ -49,13 +61,6 @@ export function TestTypeSearch({ openLabForm, orderTypeUuid, orderableConceptSet
|
|
|
49
61
|
searchInputRef.current?.focus();
|
|
50
62
|
}, [setSearchTerm]);
|
|
51
63
|
|
|
52
|
-
const cancelOrder = useCallback(() => {
|
|
53
|
-
closeWorkspace('add-lab-order', {
|
|
54
|
-
ignoreChanges: true,
|
|
55
|
-
onWorkspaceClose: () => launchWorkspace('order-basket'),
|
|
56
|
-
});
|
|
57
|
-
}, []);
|
|
58
|
-
|
|
59
64
|
const handleSearchTermChange = useCallback(
|
|
60
65
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
61
66
|
setSearchTerm(event.target.value ?? '');
|
|
@@ -77,24 +82,28 @@ export function TestTypeSearch({ openLabForm, orderTypeUuid, orderableConceptSet
|
|
|
77
82
|
/>
|
|
78
83
|
</ResponsiveWrapper>
|
|
79
84
|
<TestTypeSearchResults
|
|
80
|
-
|
|
85
|
+
closeWorkspace={closeWorkspace}
|
|
81
86
|
orderTypeUuid={orderTypeUuid}
|
|
82
87
|
orderableConceptSets={orderableConceptSets}
|
|
83
88
|
focusAndClearSearchInput={focusAndClearSearchInput}
|
|
84
89
|
openLabForm={openLabForm}
|
|
85
90
|
searchTerm={debouncedSearchTerm}
|
|
91
|
+
patient={patient}
|
|
92
|
+
visit={visit}
|
|
86
93
|
/>
|
|
87
94
|
</>
|
|
88
95
|
);
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
function TestTypeSearchResults({
|
|
92
|
-
|
|
99
|
+
closeWorkspace,
|
|
93
100
|
searchTerm,
|
|
94
101
|
orderTypeUuid,
|
|
95
102
|
orderableConceptSets,
|
|
96
103
|
openLabForm,
|
|
97
104
|
focusAndClearSearchInput,
|
|
105
|
+
patient,
|
|
106
|
+
visit,
|
|
98
107
|
}: TestTypeSearchResultsProps) {
|
|
99
108
|
const { t } = useTranslation();
|
|
100
109
|
const isTablet = useLayoutType() === 'tablet';
|
|
@@ -145,6 +154,9 @@ function TestTypeSearchResults({
|
|
|
145
154
|
orderTypeUuid={orderTypeUuid}
|
|
146
155
|
openOrderForm={openLabForm}
|
|
147
156
|
testType={testType}
|
|
157
|
+
closeWorkspace={closeWorkspace}
|
|
158
|
+
patient={patient}
|
|
159
|
+
visit={visit}
|
|
148
160
|
/>
|
|
149
161
|
))}
|
|
150
162
|
</div>
|
|
@@ -152,7 +164,11 @@ function TestTypeSearchResults({
|
|
|
152
164
|
{isTablet && (
|
|
153
165
|
<div className={styles.separatorContainer}>
|
|
154
166
|
<p className={styles.separator}>{t('or', 'or')}</p>
|
|
155
|
-
<Button
|
|
167
|
+
<Button
|
|
168
|
+
iconDescription="Return to order basket"
|
|
169
|
+
kind="ghost"
|
|
170
|
+
onClick={() => closeWorkspace({ discardUnsavedChanges: true })}
|
|
171
|
+
>
|
|
156
172
|
{t('returnToOrderBasket', 'Return to order basket')}
|
|
157
173
|
</Button>
|
|
158
174
|
</div>
|
|
@@ -185,11 +201,14 @@ const TestTypeSearchResultItem: React.FC<TestTypeSearchResultItemProps> = ({
|
|
|
185
201
|
testType,
|
|
186
202
|
openOrderForm,
|
|
187
203
|
orderTypeUuid,
|
|
204
|
+
closeWorkspace,
|
|
205
|
+
patient,
|
|
206
|
+
visit,
|
|
188
207
|
}) => {
|
|
189
208
|
const { t } = useTranslation();
|
|
190
209
|
const isTablet = useLayoutType() === 'tablet';
|
|
191
210
|
const session = useSession();
|
|
192
|
-
const { orders, setOrders } = useOrderBasket<TestOrderBasketItem>(orderTypeUuid, prepTestOrderPostData);
|
|
211
|
+
const { orders, setOrders } = useOrderBasket<TestOrderBasketItem>(patient, orderTypeUuid, prepTestOrderPostData);
|
|
193
212
|
|
|
194
213
|
const testTypeAlreadyInBasket = useMemo(
|
|
195
214
|
() => orders?.some((order) => order.testType.conceptUuid === testType.conceptUuid),
|
|
@@ -198,20 +217,17 @@ const TestTypeSearchResultItem: React.FC<TestTypeSearchResultItemProps> = ({
|
|
|
198
217
|
|
|
199
218
|
const createLabOrder = useCallback(
|
|
200
219
|
(orderableConcept: TestType) => {
|
|
201
|
-
return createEmptyLabOrder(orderableConcept, session.currentProvider?.uuid);
|
|
220
|
+
return createEmptyLabOrder(orderableConcept, session.currentProvider?.uuid, visit);
|
|
202
221
|
},
|
|
203
|
-
[session.currentProvider.uuid],
|
|
222
|
+
[session.currentProvider.uuid, visit],
|
|
204
223
|
);
|
|
205
224
|
|
|
206
225
|
const addToBasket = useCallback(() => {
|
|
207
226
|
const labOrder = createLabOrder(testType);
|
|
208
227
|
labOrder.isOrderIncomplete = true;
|
|
209
228
|
setOrders([...orders, labOrder]);
|
|
210
|
-
closeWorkspace(
|
|
211
|
-
|
|
212
|
-
onWorkspaceClose: () => launchWorkspace('order-basket'),
|
|
213
|
-
});
|
|
214
|
-
}, [orders, setOrders, createLabOrder, testType]);
|
|
229
|
+
closeWorkspace({ discardUnsavedChanges: true });
|
|
230
|
+
}, [orders, setOrders, createLabOrder, testType, closeWorkspace]);
|
|
215
231
|
|
|
216
232
|
const removeFromBasket = useCallback(() => {
|
|
217
233
|
setOrders(orders.filter((order) => order.testType.conceptUuid !== testType.conceptUuid));
|
package/src/test-orders/api.ts
CHANGED
|
@@ -2,7 +2,12 @@ import { useCallback, useMemo } from 'react';
|
|
|
2
2
|
import { chunk } from 'lodash-es';
|
|
3
3
|
import useSWR, { useSWRConfig } from 'swr';
|
|
4
4
|
import useSWRImmutable from 'swr/immutable';
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
OrderPost,
|
|
7
|
+
PatientOrderFetchResponse,
|
|
8
|
+
TestOrderBasketItem,
|
|
9
|
+
TestOrderPost,
|
|
10
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
6
11
|
import {
|
|
7
12
|
type FetchResponse,
|
|
8
13
|
openmrsFetch,
|
|
@@ -12,7 +17,6 @@ import {
|
|
|
12
17
|
useConfig,
|
|
13
18
|
} from '@openmrs/esm-framework';
|
|
14
19
|
import { type ConfigObject } from '../config-schema';
|
|
15
|
-
import type { TestOrderBasketItem } from '../types';
|
|
16
20
|
|
|
17
21
|
export const careSettingUuid = '6f0c9a92-6f24-11e3-af88-005056821db0';
|
|
18
22
|
|
|
@@ -3,7 +3,7 @@ import classNames from 'classnames';
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { ClickableTile, IconButton, Tile } from '@carbon/react';
|
|
5
5
|
import { ExtensionSlot, TrashCanIcon, useLayoutType, WarningIcon } from '@openmrs/esm-framework';
|
|
6
|
-
import type { TestOrderBasketItem } from '
|
|
6
|
+
import type { TestOrderBasketItem } from '@openmrs/esm-patient-common-lib';
|
|
7
7
|
import styles from './lab-order-basket-item-tile.scss';
|
|
8
8
|
|
|
9
9
|
export interface OrderBasketItemTileProps {
|
|
@@ -2,27 +2,25 @@ import React, { type ComponentProps, useCallback, useEffect, useMemo, useState }
|
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { Button, Tile } from '@carbon/react';
|
|
5
|
+
import { AddIcon, ChevronDownIcon, ChevronUpIcon, useLayoutType, useConfig, MaybeIcon } from '@openmrs/esm-framework';
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
useConfig,
|
|
12
|
-
MaybeIcon,
|
|
13
|
-
launchWorkspace,
|
|
14
|
-
} from '@openmrs/esm-framework';
|
|
15
|
-
import { type OrderBasketItem, useOrderBasket, useOrderType } from '@openmrs/esm-patient-common-lib';
|
|
7
|
+
type OrderBasketExtensionProps,
|
|
8
|
+
useOrderBasket,
|
|
9
|
+
useOrderType,
|
|
10
|
+
type TestOrderBasketItem,
|
|
11
|
+
} from '@openmrs/esm-patient-common-lib';
|
|
16
12
|
import type { ConfigObject } from '../../config-schema';
|
|
17
|
-
import type { TestOrderBasketItem } from '../../types';
|
|
18
13
|
import { LabOrderBasketItemTile } from './lab-order-basket-item-tile.component';
|
|
19
14
|
import { prepTestOrderPostData } from '../api';
|
|
20
15
|
import styles from './lab-order-basket-panel.scss';
|
|
21
16
|
|
|
22
17
|
/**
|
|
18
|
+
* The extension is slotted into order-basket-slot in the main Order Basket workspace by default.
|
|
19
|
+
* It renders the "Add +" button for lab orders, and lists pending lab orders in the order basket.
|
|
20
|
+
*
|
|
23
21
|
* Designs: https://app.zeplin.io/project/60d59321e8100b0324762e05/screen/648c44d9d4052c613e7f23da
|
|
24
22
|
*/
|
|
25
|
-
export
|
|
23
|
+
export function LabOrderBasketPanelExtension({ patient, launchLabOrderForm }: OrderBasketExtensionProps) {
|
|
26
24
|
const { orders, additionalTestOrderTypes } = useConfig<ConfigObject>();
|
|
27
25
|
const { t } = useTranslation();
|
|
28
26
|
const allOrderTypes: ConfigObject['additionalTestOrderTypes'] = [
|
|
@@ -38,7 +36,12 @@ export default function LabOrderBasketPanelExtension() {
|
|
|
38
36
|
return (
|
|
39
37
|
<>
|
|
40
38
|
{allOrderTypes.map((orderTypeConfig) => (
|
|
41
|
-
<LabOrderBasketPanel
|
|
39
|
+
<LabOrderBasketPanel
|
|
40
|
+
key={orderTypeConfig.orderTypeUuid}
|
|
41
|
+
patient={patient}
|
|
42
|
+
{...orderTypeConfig}
|
|
43
|
+
launchLabOrderForm={launchLabOrderForm}
|
|
44
|
+
/>
|
|
42
45
|
))}
|
|
43
46
|
</>
|
|
44
47
|
);
|
|
@@ -46,14 +49,17 @@ export default function LabOrderBasketPanelExtension() {
|
|
|
46
49
|
|
|
47
50
|
type OrderTypeConfig = ConfigObject['additionalTestOrderTypes'][0];
|
|
48
51
|
|
|
49
|
-
interface LabOrderBasketPanelProps extends OrderTypeConfig {
|
|
52
|
+
interface LabOrderBasketPanelProps extends OrderTypeConfig {
|
|
53
|
+
patient: fhir.Patient;
|
|
54
|
+
launchLabOrderForm(orderTypeUuid: string, order?: TestOrderBasketItem): void;
|
|
55
|
+
}
|
|
50
56
|
|
|
51
|
-
function LabOrderBasketPanel({ orderTypeUuid, label, icon }: LabOrderBasketPanelProps) {
|
|
57
|
+
function LabOrderBasketPanel({ orderTypeUuid, label, icon, patient, launchLabOrderForm }: LabOrderBasketPanelProps) {
|
|
52
58
|
const { t } = useTranslation();
|
|
53
59
|
const isTablet = useLayoutType() === 'tablet';
|
|
54
60
|
const { orderType, isLoadingOrderType } = useOrderType(orderTypeUuid);
|
|
55
61
|
|
|
56
|
-
const { orders, setOrders } = useOrderBasket<TestOrderBasketItem>(orderTypeUuid, prepTestOrderPostData);
|
|
62
|
+
const { orders, setOrders } = useOrderBasket<TestOrderBasketItem>(patient, orderTypeUuid, prepTestOrderPostData);
|
|
57
63
|
const [isExpanded, setIsExpanded] = useState(orders.length > 0);
|
|
58
64
|
const {
|
|
59
65
|
incompleteOrderBasketItems,
|
|
@@ -91,32 +97,6 @@ function LabOrderBasketPanel({ orderTypeUuid, label, icon }: LabOrderBasketPanel
|
|
|
91
97
|
};
|
|
92
98
|
}, [orders]);
|
|
93
99
|
|
|
94
|
-
const openNewLabForm = useCallback(() => {
|
|
95
|
-
closeWorkspace('order-basket', {
|
|
96
|
-
ignoreChanges: true,
|
|
97
|
-
onWorkspaceClose: () =>
|
|
98
|
-
launchWorkspace('add-lab-order', {
|
|
99
|
-
orderTypeUuid: orderTypeUuid,
|
|
100
|
-
}),
|
|
101
|
-
closeWorkspaceGroup: false,
|
|
102
|
-
});
|
|
103
|
-
}, [orderTypeUuid]);
|
|
104
|
-
|
|
105
|
-
const openEditLabForm = useCallback(
|
|
106
|
-
(order: OrderBasketItem) => {
|
|
107
|
-
closeWorkspace('order-basket', {
|
|
108
|
-
ignoreChanges: true,
|
|
109
|
-
onWorkspaceClose: () =>
|
|
110
|
-
launchWorkspace('add-lab-order', {
|
|
111
|
-
order,
|
|
112
|
-
orderTypeUuid: orderTypeUuid,
|
|
113
|
-
}),
|
|
114
|
-
closeWorkspaceGroup: false,
|
|
115
|
-
});
|
|
116
|
-
},
|
|
117
|
-
[orderTypeUuid],
|
|
118
|
-
);
|
|
119
|
-
|
|
120
100
|
const removeLabOrder = useCallback(
|
|
121
101
|
(order: TestOrderBasketItem) => {
|
|
122
102
|
const newOrders = [...orders];
|
|
@@ -150,7 +130,7 @@ function LabOrderBasketPanel({ orderTypeUuid, label, icon }: LabOrderBasketPanel
|
|
|
150
130
|
className={styles.addButton}
|
|
151
131
|
iconDescription="Add lab order"
|
|
152
132
|
kind="ghost"
|
|
153
|
-
onClick={
|
|
133
|
+
onClick={() => launchLabOrderForm(orderTypeUuid)}
|
|
154
134
|
renderIcon={(props: ComponentProps<typeof AddIcon>) => <AddIcon size={16} {...props} />}
|
|
155
135
|
size={isTablet ? 'md' : 'sm'}
|
|
156
136
|
>
|
|
@@ -180,7 +160,7 @@ function LabOrderBasketPanel({ orderTypeUuid, label, icon }: LabOrderBasketPanel
|
|
|
180
160
|
{incompleteOrderBasketItems.map((order) => (
|
|
181
161
|
<LabOrderBasketItemTile
|
|
182
162
|
key={order.uuid}
|
|
183
|
-
onItemClick={() =>
|
|
163
|
+
onItemClick={() => launchLabOrderForm(orderTypeUuid, order)}
|
|
184
164
|
onRemoveClick={() => removeLabOrder(order)}
|
|
185
165
|
orderBasketItem={order}
|
|
186
166
|
/>
|
|
@@ -192,7 +172,7 @@ function LabOrderBasketPanel({ orderTypeUuid, label, icon }: LabOrderBasketPanel
|
|
|
192
172
|
{newOrderBasketItems.map((order) => (
|
|
193
173
|
<LabOrderBasketItemTile
|
|
194
174
|
key={order.uuid}
|
|
195
|
-
onItemClick={() =>
|
|
175
|
+
onItemClick={() => launchLabOrderForm(orderTypeUuid, order)}
|
|
196
176
|
onRemoveClick={() => removeLabOrder(order)}
|
|
197
177
|
orderBasketItem={order}
|
|
198
178
|
/>
|
|
@@ -205,7 +185,7 @@ function LabOrderBasketPanel({ orderTypeUuid, label, icon }: LabOrderBasketPanel
|
|
|
205
185
|
{renewedOrderBasketItems.map((order) => (
|
|
206
186
|
<LabOrderBasketItemTile
|
|
207
187
|
key={order.uuid}
|
|
208
|
-
onItemClick={() =>
|
|
188
|
+
onItemClick={() => launchLabOrderForm(orderTypeUuid, order)}
|
|
209
189
|
onRemoveClick={() => removeLabOrder(order)}
|
|
210
190
|
orderBasketItem={order}
|
|
211
191
|
/>
|
|
@@ -218,7 +198,7 @@ function LabOrderBasketPanel({ orderTypeUuid, label, icon }: LabOrderBasketPanel
|
|
|
218
198
|
{revisedOrderBasketItems.map((order) => (
|
|
219
199
|
<LabOrderBasketItemTile
|
|
220
200
|
key={order.uuid}
|
|
221
|
-
onItemClick={() =>
|
|
201
|
+
onItemClick={() => launchLabOrderForm(orderTypeUuid, order)}
|
|
222
202
|
onRemoveClick={() => removeLabOrder(order)}
|
|
223
203
|
orderBasketItem={order}
|
|
224
204
|
/>
|
|
@@ -231,7 +211,7 @@ function LabOrderBasketPanel({ orderTypeUuid, label, icon }: LabOrderBasketPanel
|
|
|
231
211
|
{discontinuedOrderBasketItems.map((order) => (
|
|
232
212
|
<LabOrderBasketItemTile
|
|
233
213
|
key={order.uuid}
|
|
234
|
-
onItemClick={() =>
|
|
214
|
+
onItemClick={() => launchLabOrderForm(orderTypeUuid, order)}
|
|
235
215
|
onRemoveClick={() => removeLabOrder(order)}
|
|
236
216
|
orderBasketItem={order}
|
|
237
217
|
/>
|
|
@@ -245,3 +225,5 @@ function LabOrderBasketPanel({ orderTypeUuid, label, icon }: LabOrderBasketPanel
|
|
|
245
225
|
</Tile>
|
|
246
226
|
);
|
|
247
227
|
}
|
|
228
|
+
|
|
229
|
+
export default LabOrderBasketPanelExtension;
|
|
@@ -3,9 +3,11 @@ import userEvent from '@testing-library/user-event';
|
|
|
3
3
|
import { screen, render } from '@testing-library/react';
|
|
4
4
|
import { useOrderType } from '@openmrs/esm-patient-common-lib';
|
|
5
5
|
import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
|
|
6
|
+
import type { OrderBasketExtensionProps, TestOrderBasketItem } from '@openmrs/esm-patient-common-lib';
|
|
6
7
|
import { type ConfigObject, configSchema } from '../../config-schema';
|
|
7
|
-
import type { TestOrderBasketItem } from '../../types';
|
|
8
8
|
import LabOrderBasketPanel from './lab-order-basket-panel.extension';
|
|
9
|
+
import { mockPatient } from 'tools';
|
|
10
|
+
import { mockVisit } from '__mocks__';
|
|
9
11
|
|
|
10
12
|
const mockUseOrderBasket = jest.fn();
|
|
11
13
|
const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
|
|
@@ -36,10 +38,17 @@ mockUseOrderType.mockReturnValue({
|
|
|
36
38
|
errorFetchingOrderType: undefined,
|
|
37
39
|
});
|
|
38
40
|
|
|
41
|
+
const testProps: OrderBasketExtensionProps = {
|
|
42
|
+
patient: mockPatient,
|
|
43
|
+
launchDrugOrderForm: jest.fn(),
|
|
44
|
+
launchLabOrderForm: jest.fn(),
|
|
45
|
+
launchGeneralOrderForm: jest.fn(),
|
|
46
|
+
};
|
|
47
|
+
|
|
39
48
|
describe('LabOrderBasketPanel', () => {
|
|
40
49
|
test('renders an empty state when no items are selected in the order basket', () => {
|
|
41
50
|
mockUseOrderBasket.mockReturnValue({ orders: [] });
|
|
42
|
-
render(<LabOrderBasketPanel />);
|
|
51
|
+
render(<LabOrderBasketPanel {...testProps} />);
|
|
43
52
|
expect(screen.getByRole('heading', { name: /Lab orders \(0\)/i })).toBeInTheDocument();
|
|
44
53
|
expect(screen.getByRole('button', { name: /Add/i })).toBeInTheDocument();
|
|
45
54
|
});
|
|
@@ -56,6 +65,7 @@ describe('LabOrderBasketPanel', () => {
|
|
|
56
65
|
display: 'HIV VIRAL LOAD',
|
|
57
66
|
urgency: 'ROUTINE',
|
|
58
67
|
uuid: 'order-uuid-1',
|
|
68
|
+
visit: mockVisit,
|
|
59
69
|
},
|
|
60
70
|
{
|
|
61
71
|
action: 'NEW',
|
|
@@ -66,6 +76,7 @@ describe('LabOrderBasketPanel', () => {
|
|
|
66
76
|
display: 'CD4 COUNT',
|
|
67
77
|
urgency: 'STAT',
|
|
68
78
|
uuid: 'order-uuid-2',
|
|
79
|
+
visit: mockVisit,
|
|
69
80
|
},
|
|
70
81
|
];
|
|
71
82
|
let orders = [...labs];
|
|
@@ -76,7 +87,7 @@ describe('LabOrderBasketPanel', () => {
|
|
|
76
87
|
orders: orders,
|
|
77
88
|
setOrders: mockSetOrders,
|
|
78
89
|
}));
|
|
79
|
-
const { rerender } = render(<LabOrderBasketPanel />);
|
|
90
|
+
const { rerender } = render(<LabOrderBasketPanel {...testProps} />);
|
|
80
91
|
expect(screen.getByText(/Lab orders \(2\)/i)).toBeInTheDocument();
|
|
81
92
|
expect(screen.getByText(/HIV VIRAL LOAD/i)).toBeInTheDocument();
|
|
82
93
|
expect(screen.getByText(/CD4 COUNT/i)).toBeInTheDocument();
|
|
@@ -85,7 +96,7 @@ describe('LabOrderBasketPanel', () => {
|
|
|
85
96
|
expect(removeHivButton).toBeVisible();
|
|
86
97
|
|
|
87
98
|
await user.click(removeHivButton);
|
|
88
|
-
rerender(<LabOrderBasketPanel />);
|
|
99
|
+
rerender(<LabOrderBasketPanel {...testProps} />);
|
|
89
100
|
await expect(screen.getByText(/Lab orders \(1\)/i)).toBeInTheDocument();
|
|
90
101
|
expect(screen.getByText(/CD4 COUNT/i)).toBeInTheDocument();
|
|
91
102
|
expect(screen.queryByText(/HIV VIRAL LOAD/i)).not.toBeInTheDocument();
|
|
@@ -14,6 +14,7 @@ export interface TreeNode {
|
|
|
14
14
|
flatName: string;
|
|
15
15
|
subSets?: Array<TreeNode>;
|
|
16
16
|
hasData?: boolean;
|
|
17
|
+
hiAbsolute?: number;
|
|
17
18
|
hiCritical?: number;
|
|
18
19
|
hiNormal?: number;
|
|
19
20
|
lowAbsolute?: number;
|
|
@@ -75,6 +76,15 @@ export interface ObservationData {
|
|
|
75
76
|
obsDatetime: string;
|
|
76
77
|
value: string;
|
|
77
78
|
interpretation: OBSERVATION_INTERPRETATION;
|
|
79
|
+
// Reference range fields from observation-level (criteria-based)
|
|
80
|
+
// Note: Units are only at the concept/node level, not observation-level
|
|
81
|
+
hiAbsolute?: number;
|
|
82
|
+
hiCritical?: number;
|
|
83
|
+
hiNormal?: number;
|
|
84
|
+
lowAbsolute?: number;
|
|
85
|
+
lowCritical?: number;
|
|
86
|
+
lowNormal?: number;
|
|
87
|
+
range?: string; // Formatted range string for display
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
export interface ParsedTimeType {
|
|
@@ -124,6 +134,15 @@ export interface RowData extends TreeNode {
|
|
|
124
134
|
obsDatetime: string;
|
|
125
135
|
value: string;
|
|
126
136
|
interpretation: OBSERVATION_INTERPRETATION;
|
|
137
|
+
// Reference range fields from observation-level (criteria-based)
|
|
138
|
+
// Note: Units are only at the concept/node level, not observation-level
|
|
139
|
+
hiAbsolute?: number;
|
|
140
|
+
hiCritical?: number;
|
|
141
|
+
hiNormal?: number;
|
|
142
|
+
lowAbsolute?: number;
|
|
143
|
+
lowCritical?: number;
|
|
144
|
+
lowNormal?: number;
|
|
145
|
+
range?: string; // Formatted range string for display
|
|
127
146
|
}
|
|
128
147
|
| undefined
|
|
129
148
|
>;
|