@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.
Files changed (80) hide show
  1. package/.turbo/turbo-build.log +22 -19
  2. package/dist/1479.js +1 -1
  3. package/dist/1479.js.map +1 -1
  4. package/dist/3509.js +1 -1
  5. package/dist/4055.js +1 -1
  6. package/dist/4300.js +1 -1
  7. package/dist/{1935.js → 5348.js} +1 -1
  8. package/dist/5348.js.map +1 -0
  9. package/dist/5670.js +1 -1
  10. package/dist/5670.js.map +1 -1
  11. package/dist/6231.js +1 -1
  12. package/dist/6231.js.map +1 -1
  13. package/dist/6301.js +1 -1
  14. package/dist/6301.js.map +1 -1
  15. package/dist/6336.js +1 -0
  16. package/dist/6336.js.map +1 -0
  17. package/dist/790.js +1 -1
  18. package/dist/790.js.map +1 -1
  19. package/dist/8307.js +2 -0
  20. package/dist/8307.js.map +1 -0
  21. package/dist/9540.js +2 -0
  22. package/dist/9540.js.map +1 -0
  23. package/dist/9838.js +1 -0
  24. package/dist/9838.js.map +1 -0
  25. package/dist/main.js +1 -1
  26. package/dist/main.js.map +1 -1
  27. package/dist/openmrs-esm-patient-tests-app.js +1 -1
  28. package/dist/openmrs-esm-patient-tests-app.js.buildmanifest.json +172 -193
  29. package/dist/openmrs-esm-patient-tests-app.js.map +1 -1
  30. package/dist/routes.json +1 -1
  31. package/package.json +3 -3
  32. package/src/edit-test-results/modal/edit-lab-results.modal.tsx +8 -4
  33. package/src/routes.json +3 -4
  34. package/src/test-orders/add-test-order/add-test-order.component.tsx +125 -0
  35. package/src/test-orders/add-test-order/add-test-order.test.tsx +23 -43
  36. package/src/test-orders/add-test-order/add-test-order.workspace.tsx +21 -116
  37. package/src/test-orders/add-test-order/exported-add-test-order.workspace.tsx +30 -0
  38. package/src/test-orders/add-test-order/test-order-form.component.tsx +67 -25
  39. package/src/test-orders/add-test-order/test-order.ts +3 -3
  40. package/src/test-orders/add-test-order/test-type-search.component.tsx +40 -24
  41. package/src/test-orders/api.ts +6 -2
  42. package/src/test-orders/lab-order-basket-panel/lab-order-basket-item-tile.component.tsx +1 -1
  43. package/src/test-orders/lab-order-basket-panel/lab-order-basket-panel.extension.tsx +30 -48
  44. package/src/test-orders/lab-order-basket-panel/lab-order-basket-panel.test.tsx +15 -4
  45. package/src/test-results/filter/filter-types.ts +19 -0
  46. package/src/test-results/grouped-timeline/grouped-timeline.test.tsx +49 -0
  47. package/src/test-results/grouped-timeline/reference-range-helpers.test.ts +272 -0
  48. package/src/test-results/grouped-timeline/reference-range-helpers.ts +112 -0
  49. package/src/test-results/grouped-timeline/timeline-data-group.component.tsx +10 -3
  50. package/src/test-results/grouped-timeline/useObstreeData.ts +71 -9
  51. package/src/test-results/individual-results-table/individual-results-table.component.tsx +23 -6
  52. package/src/test-results/individual-results-table/individual-results-table.test.tsx +65 -3
  53. package/src/test-results/individual-results-table-tablet/helper.tsx +8 -2
  54. package/src/test-results/individual-results-table-tablet/individual-results-table-tablet.component.tsx +2 -2
  55. package/src/test-results/individual-results-table-tablet/lab-set-panel.component.tsx +5 -1
  56. package/src/test-results/loadPatientTestData/helpers.test.ts +834 -0
  57. package/src/test-results/loadPatientTestData/helpers.ts +114 -0
  58. package/src/test-results/loadPatientTestData/loadPatientData.ts +66 -11
  59. package/src/test-results/loadPatientTestData/usePatientResultsData.ts +3 -3
  60. package/src/test-results/overview/common-datatable.component.tsx +1 -1
  61. package/src/test-results/overview/useOverviewData.ts +22 -10
  62. package/src/test-results/results-viewer/results-viewer.extension.tsx +4 -3
  63. package/src/test-results/tree-view/tree-view.component.tsx +14 -4
  64. package/src/test-results/trendline/trendline-resource.tsx +48 -5
  65. package/src/types.ts +20 -10
  66. package/translations/en.json +2 -0
  67. package/translations/fr.json +2 -2
  68. package/dist/1935.js.map +0 -1
  69. package/dist/2537.js +0 -1
  70. package/dist/2537.js.map +0 -1
  71. package/dist/34.js +0 -1
  72. package/dist/34.js.map +0 -1
  73. package/dist/4918.js +0 -1
  74. package/dist/4918.js.map +0 -1
  75. package/dist/5836.js +0 -2
  76. package/dist/5836.js.map +0 -1
  77. package/dist/7053.js +0 -2
  78. package/dist/7053.js.map +0 -1
  79. /package/dist/{7053.js.LICENSE.txt → 8307.js.LICENSE.txt} +0 -0
  80. /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 extends DefaultPatientWorkspaceProps {
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
- orderableConceptSets,
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 isEditing = useMemo(() => initialOrder && initialOrder.action === 'REVISE', [initialOrder]);
63
- const { orders, setOrders } = useOrderBasket<TestOrderBasketItem>(orderTypeUuid, prepTestOrderPostData);
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, isLoadingOrderType } = useOrderType(orderTypeUuid);
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 handleFormSubmission = useCallback(
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
- closeWorkspaceWithSavedChanges({
163
- onWorkspaceClose: () => launchWorkspace('order-basket'),
164
- closeWorkspaceGroup: false,
165
- });
170
+ closeWorkspace({ discardUnsavedChanges: true });
166
171
  },
167
- [orders, setOrders, session?.currentProvider?.uuid, closeWorkspaceWithSavedChanges, initialOrder],
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
- promptBeforeClosing(() => isDirty);
199
- }, [isDirty, promptBeforeClosing]);
231
+ setHasUnsavedChanges(isDirty);
232
+ }, [isDirty, setHasUnsavedChanges]);
200
233
 
201
234
  const responsiveSize = isTablet ? 'lg' : 'sm';
202
235
 
203
236
  return (
204
- <Form className={styles.orderForm} onSubmit={handleSubmit(handleFormSubmission, onError)} id="drugOrderForm">
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 className={styles.button} kind="secondary" onClick={cancelOrder} size="xl">
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({ openLabForm, orderTypeUuid, orderableConceptSets }: TestTypeSearchProps) {
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
- cancelOrder={cancelOrder}
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
- cancelOrder,
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 iconDescription="Return to order basket" kind="ghost" onClick={cancelOrder}>
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('add-lab-order', {
211
- ignoreChanges: true,
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));
@@ -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 { OrderPost, PatientOrderFetchResponse, TestOrderPost } from '@openmrs/esm-patient-common-lib';
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 '../../types';
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
- AddIcon,
7
- closeWorkspace,
8
- ChevronDownIcon,
9
- ChevronUpIcon,
10
- useLayoutType,
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 default function LabOrderBasketPanelExtension() {
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 key={orderTypeConfig.orderTypeUuid} {...orderTypeConfig} />
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={openNewLabForm}
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={() => openEditLabForm(order)}
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={() => openEditLabForm(order)}
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={() => openEditLabForm(order)}
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={() => openEditLabForm(order)}
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={() => openEditLabForm(order)}
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
  >;