@openmrs/esm-laboratory-app 1.2.1-pre.739 → 1.2.1-pre.740

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 (41) hide show
  1. package/.turbo/cache/8773ecc69845448d-meta.json +1 -0
  2. package/.turbo/cache/8773ecc69845448d.tar.zst +0 -0
  3. package/.turbo/turbo-build.log +2 -2
  4. package/dist/1120.js +1 -1
  5. package/dist/1120.js.map +1 -1
  6. package/dist/1788.js +1 -1
  7. package/dist/1788.js.map +1 -1
  8. package/dist/3656.js +1 -1
  9. package/dist/3656.js.map +1 -1
  10. package/dist/4069.js +1 -1
  11. package/dist/4069.js.map +1 -1
  12. package/dist/4300.js +1 -1
  13. package/dist/5085.js +1 -1
  14. package/dist/5085.js.map +1 -1
  15. package/dist/6134.js +1 -1
  16. package/dist/6134.js.map +1 -1
  17. package/dist/7423.js +1 -1
  18. package/dist/7423.js.map +1 -1
  19. package/dist/8554.js +1 -1
  20. package/dist/8554.js.map +1 -1
  21. package/dist/8667.js +1 -1
  22. package/dist/8667.js.map +1 -1
  23. package/dist/main.js +1 -1
  24. package/dist/main.js.map +1 -1
  25. package/dist/openmrs-esm-laboratory-app.js.buildmanifest.json +33 -33
  26. package/dist/routes.json +1 -1
  27. package/package.json +1 -1
  28. package/src/components/orders-table/list-order-details.component.tsx +31 -24
  29. package/src/components/orders-table/list-order-details.scss +0 -1
  30. package/src/components/orders-table/orders-data-table.component.tsx +143 -56
  31. package/src/components/orders-table/orders-data-table.test.tsx +215 -0
  32. package/src/config-schema.ts +20 -1
  33. package/src/lab-tabs/data-table-extensions/tests-ordered-table.extension.tsx +1 -1
  34. package/src/lab-tiles/all-lab-requests-tile.component.tsx +1 -1
  35. package/src/lab-tiles/completed-lab-requests-tile.component.tsx +1 -1
  36. package/src/lab-tiles/in-progress-lab-requests-tile.component.tsx +1 -1
  37. package/src/laboratory-resource.ts +24 -33
  38. package/src/types.ts +20 -17
  39. package/translations/en.json +2 -2
  40. package/.turbo/cache/26dd8861bd3eca6d-meta.json +0 -1
  41. package/.turbo/cache/26dd8861bd3eca6d.tar.zst +0 -0
@@ -218,9 +218,9 @@
218
218
  "entry": false,
219
219
  "recorded": false,
220
220
  "reason": "split chunk (cache group: default)",
221
- "size": 40257,
221
+ "size": 40790,
222
222
  "sizes": {
223
- "javascript": 40257
223
+ "javascript": 40790
224
224
  },
225
225
  "names": [],
226
226
  "idHints": [],
@@ -234,7 +234,7 @@
234
234
  "auxiliaryFiles": [
235
235
  "1120.js.map"
236
236
  ],
237
- "hash": "d49cc4835730f829",
237
+ "hash": "29dddb34ccedc4c9",
238
238
  "childrenByOrder": {}
239
239
  },
240
240
  {
@@ -264,10 +264,10 @@
264
264
  "initial": false,
265
265
  "entry": false,
266
266
  "recorded": false,
267
- "size": 74326,
267
+ "size": 75165,
268
268
  "sizes": {
269
269
  "consume-shared": 42,
270
- "javascript": 74284
270
+ "javascript": 75123
271
271
  },
272
272
  "names": [],
273
273
  "idHints": [],
@@ -280,7 +280,7 @@
280
280
  "auxiliaryFiles": [
281
281
  "1788.js.map"
282
282
  ],
283
- "hash": "b71a99918f613cab",
283
+ "hash": "9ff21cc90be63cdf",
284
284
  "childrenByOrder": {}
285
285
  },
286
286
  {
@@ -526,9 +526,9 @@
526
526
  "initial": false,
527
527
  "entry": false,
528
528
  "recorded": false,
529
- "size": 609,
529
+ "size": 606,
530
530
  "sizes": {
531
- "javascript": 609
531
+ "javascript": 606
532
532
  },
533
533
  "names": [],
534
534
  "idHints": [],
@@ -542,7 +542,7 @@
542
542
  "auxiliaryFiles": [
543
543
  "3656.js.map"
544
544
  ],
545
- "hash": "ba700160a1ef21b4",
545
+ "hash": "004c8b5953bfa4a0",
546
546
  "childrenByOrder": {}
547
547
  },
548
548
  {
@@ -596,9 +596,9 @@
596
596
  "initial": false,
597
597
  "entry": false,
598
598
  "recorded": false,
599
- "size": 625,
599
+ "size": 655,
600
600
  "sizes": {
601
- "javascript": 625
601
+ "javascript": 655
602
602
  },
603
603
  "names": [],
604
604
  "idHints": [],
@@ -612,7 +612,7 @@
612
612
  "auxiliaryFiles": [
613
613
  "4069.js.map"
614
614
  ],
615
- "hash": "09bb24717667c319",
615
+ "hash": "b296e202ab7e3a08",
616
616
  "childrenByOrder": {}
617
617
  },
618
618
  {
@@ -666,9 +666,9 @@
666
666
  "initial": false,
667
667
  "entry": false,
668
668
  "recorded": false,
669
- "size": 2174,
669
+ "size": 2175,
670
670
  "sizes": {
671
- "javascript": 2174
671
+ "javascript": 2175
672
672
  },
673
673
  "names": [],
674
674
  "idHints": [],
@@ -680,7 +680,7 @@
680
680
  "4300.js"
681
681
  ],
682
682
  "auxiliaryFiles": [],
683
- "hash": "eac8032bf0461437",
683
+ "hash": "71deebbed405413c",
684
684
  "childrenByOrder": {}
685
685
  },
686
686
  {
@@ -800,9 +800,9 @@
800
800
  "initial": false,
801
801
  "entry": false,
802
802
  "recorded": false,
803
- "size": 19080,
803
+ "size": 19613,
804
804
  "sizes": {
805
- "javascript": 19080
805
+ "javascript": 19613
806
806
  },
807
807
  "names": [],
808
808
  "idHints": [],
@@ -816,7 +816,7 @@
816
816
  "auxiliaryFiles": [
817
817
  "5085.js.map"
818
818
  ],
819
- "hash": "5c51bbbd9517d426",
819
+ "hash": "a871c0871547ba58",
820
820
  "childrenByOrder": {}
821
821
  },
822
822
  {
@@ -980,9 +980,9 @@
980
980
  "entry": false,
981
981
  "recorded": false,
982
982
  "reason": "split chunk (cache group: default)",
983
- "size": 146403,
983
+ "size": 148280,
984
984
  "sizes": {
985
- "javascript": 146403
985
+ "javascript": 148280
986
986
  },
987
987
  "names": [],
988
988
  "idHints": [],
@@ -996,7 +996,7 @@
996
996
  "auxiliaryFiles": [
997
997
  "6134.js.map"
998
998
  ],
999
- "hash": "8a6d97f3e3948633",
999
+ "hash": "53524e1d0e200b7e",
1000
1000
  "childrenByOrder": {}
1001
1001
  },
1002
1002
  {
@@ -1184,9 +1184,9 @@
1184
1184
  "initial": false,
1185
1185
  "entry": false,
1186
1186
  "recorded": false,
1187
- "size": 645,
1187
+ "size": 694,
1188
1188
  "sizes": {
1189
- "javascript": 645
1189
+ "javascript": 694
1190
1190
  },
1191
1191
  "names": [],
1192
1192
  "idHints": [],
@@ -1200,7 +1200,7 @@
1200
1200
  "auxiliaryFiles": [
1201
1201
  "7423.js.map"
1202
1202
  ],
1203
- "hash": "935db041eff89a55",
1203
+ "hash": "0fe8a00532cf2f89",
1204
1204
  "childrenByOrder": {}
1205
1205
  },
1206
1206
  {
@@ -1274,9 +1274,9 @@
1274
1274
  "initial": false,
1275
1275
  "entry": false,
1276
1276
  "recorded": false,
1277
- "size": 9073,
1277
+ "size": 9606,
1278
1278
  "sizes": {
1279
- "javascript": 9073
1279
+ "javascript": 9606
1280
1280
  },
1281
1281
  "names": [],
1282
1282
  "idHints": [],
@@ -1290,7 +1290,7 @@
1290
1290
  "auxiliaryFiles": [
1291
1291
  "8554.js.map"
1292
1292
  ],
1293
- "hash": "731befcdfa8ed740",
1293
+ "hash": "63e9e6eda7a0d876",
1294
1294
  "childrenByOrder": {}
1295
1295
  },
1296
1296
  {
@@ -1320,9 +1320,9 @@
1320
1320
  "initial": false,
1321
1321
  "entry": false,
1322
1322
  "recorded": false,
1323
- "size": 650,
1323
+ "size": 674,
1324
1324
  "sizes": {
1325
- "javascript": 650
1325
+ "javascript": 674
1326
1326
  },
1327
1327
  "names": [],
1328
1328
  "idHints": [],
@@ -1336,7 +1336,7 @@
1336
1336
  "auxiliaryFiles": [
1337
1337
  "8667.js.map"
1338
1338
  ],
1339
- "hash": "23de6d3f05104789",
1339
+ "hash": "170e7977205c7d9a",
1340
1340
  "childrenByOrder": {}
1341
1341
  },
1342
1342
  {
@@ -1344,10 +1344,10 @@
1344
1344
  "initial": true,
1345
1345
  "entry": true,
1346
1346
  "recorded": false,
1347
- "size": 3971892,
1347
+ "size": 3972731,
1348
1348
  "sizes": {
1349
1349
  "consume-shared": 252,
1350
- "javascript": 3950593,
1350
+ "javascript": 3951432,
1351
1351
  "share-init": 210,
1352
1352
  "runtime": 20837
1353
1353
  },
@@ -1364,7 +1364,7 @@
1364
1364
  "auxiliaryFiles": [
1365
1365
  "main.js.map"
1366
1366
  ],
1367
- "hash": "64d7fd9dc133af83",
1367
+ "hash": "2b00889e33bc1a34",
1368
1368
  "childrenByOrder": {}
1369
1369
  },
1370
1370
  {
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"laboratory"}],"extensions":[{"name":"laboratory-dashboard","slot":"laboratory-dashboard-slot","component":"root"},{"name":"laboratory-dashboard-link","slot":"homepage-dashboard-slot","component":"laboratoryDashboardLink","meta":{"name":"laboratory","slot":"laboratory-dashboard-slot","title":"Laboratory"}},{"name":"pickup-lab-request-modal","component":"pickupLabRequestModal"},{"name":"reject-lab-request-modal","component":"rejectLabRequestModal"},{"name":"all-lab-requests-table","slot":"lab-panels-slot","component":"allLabRequestsTable","meta":{"name":"inprogressPanel","title":"Tests ordered"}},{"name":"inprogress-lab-requests-table","slot":"lab-panels-slot","component":"inprogressLabRequestsTable","meta":{"name":"inprogressPanel","title":"In progress"}},{"name":"completed-lab-requests-table","slot":"lab-panels-slot","component":"completedLabRequestsTable","meta":{"name":"completedPanel","title":"Completed"}},{"name":"tests-ordered-tile-component","slot":"lab-tiles-slot","component":"testOrderedTile","meta":{"name":"testsOrderedTileSlot","title":"Ordered tests"}},{"name":"worklist-tile-component","slot":"lab-tiles-slot","component":"worklistTile","meta":{"name":"worklisTileSlot","title":"Worklist"}},{"name":"completed-tile-component","slot":"lab-tiles-slot","component":"completedTile","meta":{"name":"referredPanleSlot","title":"Referred tests"}},{"name":"declined-tile-component","slot":"lab-panels-slot","component":"declinedLabRequestsTable","meta":{"name":"declinedPanel","title":"Declined tests"}},{"name":"pick-lab-request-action","component":"pickupLabRequestAction","slot":"tests-ordered-actions-slot"},{"name":"reject-lab-request-tests-ordered-action","component":"rejectLabRequestAction","slot":"rejected-ordered-actions-slot"},{"name":"edit-lab-request-tests-ordered-action","component":"editLabRequestAction","slot":"completed-ordered-actions-slot"},{"name":"reject-lab-request-worklist-action","component":"rejectLabRequestAction","slot":"inprogress-tests-actions-slot"},{"name":"add-lab-request-results-action","component":"addLabRequestResultsAction","slot":"inprogress-tests-actions-slot"},{"name":"edit-lab-request-tests-ordered-action","component":"editLabRequestAction","slot":"completed-ordered-actions-slot"}],"version":"1.2.1-pre.739"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"laboratory"}],"extensions":[{"name":"laboratory-dashboard","slot":"laboratory-dashboard-slot","component":"root"},{"name":"laboratory-dashboard-link","slot":"homepage-dashboard-slot","component":"laboratoryDashboardLink","meta":{"name":"laboratory","slot":"laboratory-dashboard-slot","title":"Laboratory"}},{"name":"pickup-lab-request-modal","component":"pickupLabRequestModal"},{"name":"reject-lab-request-modal","component":"rejectLabRequestModal"},{"name":"all-lab-requests-table","slot":"lab-panels-slot","component":"allLabRequestsTable","meta":{"name":"inprogressPanel","title":"Tests ordered"}},{"name":"inprogress-lab-requests-table","slot":"lab-panels-slot","component":"inprogressLabRequestsTable","meta":{"name":"inprogressPanel","title":"In progress"}},{"name":"completed-lab-requests-table","slot":"lab-panels-slot","component":"completedLabRequestsTable","meta":{"name":"completedPanel","title":"Completed"}},{"name":"tests-ordered-tile-component","slot":"lab-tiles-slot","component":"testOrderedTile","meta":{"name":"testsOrderedTileSlot","title":"Ordered tests"}},{"name":"worklist-tile-component","slot":"lab-tiles-slot","component":"worklistTile","meta":{"name":"worklisTileSlot","title":"Worklist"}},{"name":"completed-tile-component","slot":"lab-tiles-slot","component":"completedTile","meta":{"name":"referredPanleSlot","title":"Referred tests"}},{"name":"declined-tile-component","slot":"lab-panels-slot","component":"declinedLabRequestsTable","meta":{"name":"declinedPanel","title":"Declined tests"}},{"name":"pick-lab-request-action","component":"pickupLabRequestAction","slot":"tests-ordered-actions-slot"},{"name":"reject-lab-request-tests-ordered-action","component":"rejectLabRequestAction","slot":"rejected-ordered-actions-slot"},{"name":"edit-lab-request-tests-ordered-action","component":"editLabRequestAction","slot":"completed-ordered-actions-slot"},{"name":"reject-lab-request-worklist-action","component":"rejectLabRequestAction","slot":"inprogress-tests-actions-slot"},{"name":"add-lab-request-results-action","component":"addLabRequestResultsAction","slot":"inprogress-tests-actions-slot"},{"name":"edit-lab-request-tests-ordered-action","component":"editLabRequestAction","slot":"completed-ordered-actions-slot"}],"version":"1.2.1-pre.740"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-laboratory-app",
3
- "version": "1.2.1-pre.739",
3
+ "version": "1.2.1-pre.740",
4
4
  "license": "MPL-2.0",
5
5
  "description": "An O3 frontend module for managing laboratory requests and queues",
6
6
  "browser": "dist/openmrs-esm-laboratory-app.js",
@@ -10,8 +10,8 @@ import {
10
10
  StructuredListWrapper,
11
11
  } from '@carbon/react';
12
12
  import { capitalize } from 'lodash-es';
13
- import { ExtensionSlot } from '@openmrs/esm-framework';
14
- import { type ListOrdersDetailsProps } from '../../types';
13
+ import { ExtensionSlot, formatDate, parseDate } from '@openmrs/esm-framework';
14
+ import { type GroupedOrders, type OrderAction } from '../../types';
15
15
  import styles from './list-order-details.scss';
16
16
 
17
17
  type OrderDetailsRowProps = {
@@ -19,6 +19,11 @@ type OrderDetailsRowProps = {
19
19
  value: ReactNode;
20
20
  };
21
21
 
22
+ export interface ListOrdersDetailsProps {
23
+ groupedOrders: GroupedOrders;
24
+ actions: Array<OrderAction>;
25
+ }
26
+
22
27
  const OrderDetailRow = ({ label, value }: OrderDetailsRowProps) => {
23
28
  return (
24
29
  <StructuredListRow className={styles.orderDetailsRow}>
@@ -33,48 +38,51 @@ const OrderDetailRow = ({ label, value }: OrderDetailsRowProps) => {
33
38
  };
34
39
  const ListOrderDetails: React.FC<ListOrdersDetailsProps> = ({ groupedOrders }) => {
35
40
  const { t } = useTranslation();
36
- const orders = groupedOrders?.orders ?? [];
41
+ const originalOrders = groupedOrders?.originalOrders ?? [];
37
42
 
38
43
  return (
39
44
  <div>
40
- {orders.map((row) => (
41
- <div key={row.orderNumber} className={styles.orderDetailsContainer}>
45
+ {originalOrders.map((order) => (
46
+ <div key={order.orderNumber} className={styles.orderDetailsContainer}>
42
47
  <StructuredListWrapper className={styles.orderDetailsWrapper}>
43
48
  <StructuredListBody>
44
49
  <OrderDetailRow
45
50
  label={t('urgencyStatus', 'Urgency:')}
46
51
  value={
47
- <div className={styles.priorityPill} data-urgency={row.urgency?.replace('_', ' ')}>
48
- {capitalize(row.urgency?.replace(/_/g, ' '))}
52
+ <div className={styles.priorityPill} data-urgency={order.urgency?.replace('_', ' ')}>
53
+ {capitalize(order.urgency?.replace(/_/g, ' '))}
49
54
  </div>
50
55
  }
51
56
  />
52
- <OrderDetailRow label={t('testOrdered', 'Test ordered:')} value={capitalize(row.display)} />
57
+ <OrderDetailRow label={t('testOrdered', 'Test ordered:')} value={order.display} />
53
58
  <OrderDetailRow
54
59
  label={t('orderStatus', 'Status:')}
55
60
  value={
56
61
  <div
57
62
  className={styles.statusPill}
58
- data-status={(row.fulfillerStatus ?? 'Order not picked').replace('_', ' ')}
63
+ data-status={(order.fulfillerStatus ?? 'Order not picked').replace('_', ' ')}
59
64
  >
60
- {capitalize(row.fulfillerStatus?.replace('_', '')) || t('orderNotPicked', 'Order not picked')}
65
+ {capitalize(order.fulfillerStatus?.replace('_', '')) || t('orderNotPicked', 'Order not picked')}
61
66
  </div>
62
67
  }
63
68
  />
64
- <OrderDetailRow label={t('orderNumbers', 'Order number:')} value={capitalize(row.orderNumber)} />
65
- <OrderDetailRow label={t('orderDate', 'Order date:')} value={row.dateActivated} />
66
- <OrderDetailRow label={t('orderedBy', 'Ordered By:')} value={capitalize(row.orderer?.display)} />
69
+ <OrderDetailRow label={t('orderNumbers', 'Order number:')} value={order.orderNumber} />
70
+ <OrderDetailRow
71
+ label={t('orderDate', 'Order date:')}
72
+ value={formatDate(parseDate(order.dateActivated))}
73
+ />
74
+ <OrderDetailRow label={t('orderedBy', 'Ordered By:')} value={order.orderer.display} />
67
75
  <OrderDetailRow
68
76
  label={t('orderInstructions', 'Instructions:')}
69
- value={row.instructions ?? t('NoInstructionLeft', 'No instructions are provided.')}
77
+ value={order.instructions ?? t('NoInstructionLeft', 'No instructions are provided.')}
70
78
  />
71
79
 
72
- {row.fulfillerStatus === 'DECLINED' && (
73
- <OrderDetailRow label={t('reasonForDecline', 'Reason for decline:')} value={row.fulfillerComment} />
80
+ {order.fulfillerStatus === 'DECLINED' && (
81
+ <OrderDetailRow label={t('reasonForDecline', 'Reason for decline:')} value={order.fulfillerComment} />
74
82
  )}
75
83
  </StructuredListBody>
76
84
  </StructuredListWrapper>
77
- {row.fulfillerStatus === 'COMPLETED' && (
85
+ {order.fulfillerStatus === 'COMPLETED' && (
78
86
  <Accordion>
79
87
  <AccordionItem
80
88
  title={<span className={styles.accordionTitle}>{t('viewTestResults', 'View test results')}</span>}
@@ -82,7 +90,7 @@ const ListOrderDetails: React.FC<ListOrdersDetailsProps> = ({ groupedOrders }) =
82
90
  <div className={styles.viewResults}>
83
91
  <ExtensionSlot
84
92
  className={styles.labResultSlot}
85
- state={{ order: row }}
93
+ state={{ order: order }}
86
94
  name="completed-lab-order-results-slot"
87
95
  />
88
96
  </div>
@@ -91,20 +99,19 @@ const ListOrderDetails: React.FC<ListOrdersDetailsProps> = ({ groupedOrders }) =
91
99
  )}
92
100
 
93
101
  <div className={styles.buttonSection}>
94
- {/* @ts-ignore */}
95
- {row.fulfillerStatus === 'New' || row.fulfillerStatus === 'RECEIVED' || row.fulfillerStatus == null ? (
102
+ {order.fulfillerStatus === 'RECEIVED' || order.fulfillerStatus == null ? (
96
103
  <>
97
104
  <div className={styles.testsOrderedActions}>
98
- <ExtensionSlot state={{ order: row }} name="rejected-ordered-actions-slot" />
99
- <ExtensionSlot state={{ order: row }} name="tests-ordered-actions-slot" />
105
+ <ExtensionSlot state={{ order: order }} name="rejected-ordered-actions-slot" />
106
+ <ExtensionSlot state={{ order: order }} name="tests-ordered-actions-slot" />
100
107
  </div>
101
108
  </>
102
- ) : row.fulfillerStatus === 'IN_PROGRESS' ? (
109
+ ) : order.fulfillerStatus === 'IN_PROGRESS' ? (
103
110
  <>
104
111
  <div className={styles.testsOrderedActions}>
105
112
  <ExtensionSlot
106
113
  className={styles.menuLink}
107
- state={{ order: row }}
114
+ state={{ order: order }}
108
115
  name="inprogress-tests-actions-slot"
109
116
  />
110
117
  </div>
@@ -119,7 +119,6 @@
119
119
  background-color: colors.$green-20;
120
120
  }
121
121
 
122
- &[data-status='DISCONTINUED'],
123
122
  &[data-status='DECLINED'],
124
123
  &[data-status='Order not picked'] {
125
124
  background-color: colors.$red-20;
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useMemo, useState } from 'react';
1
+ import React, { useMemo, useState } from 'react';
2
2
  import {
3
3
  DataTable,
4
4
  DataTableSkeleton,
@@ -22,67 +22,152 @@ import {
22
22
  TableToolbarSearch,
23
23
  Tile,
24
24
  } from '@carbon/react';
25
- import { ExtensionSlot, formatDate, parseDate, showModal, usePagination } from '@openmrs/esm-framework';
25
+ import {
26
+ ExtensionSlot,
27
+ formatDate,
28
+ parseDate,
29
+ type Patient,
30
+ showModal,
31
+ useConfig,
32
+ usePagination,
33
+ } from '@openmrs/esm-framework';
26
34
  import { useTranslation } from 'react-i18next';
27
- import { type Order } from '@openmrs/esm-patient-common-lib';
28
- import type { FulfillerStatus, OrdersDataTableProps } from '../../types';
29
- import { useLabOrders, useSearchGroupedResults } from '../../laboratory-resource';
35
+ import { type Order, type FulfillerStatus } from '@openmrs/esm-patient-common-lib';
36
+ import { type FlattenedOrder, type OrderAction } from '../../types';
37
+ import { useLabOrders } from '../../laboratory-resource';
30
38
  import { OrdersDateRangePicker } from './orders-date-range-picker.component';
31
39
  import ListOrderDetails from './list-order-details.component';
32
40
  import styles from './orders-data-table.scss';
41
+ import { type Config } from '../../config-schema';
42
+
43
+ const labTableColumnSpec = {
44
+ name: {
45
+ // t('patient', 'Patient')
46
+ headerLabelKey: 'patient',
47
+ headerLabelDefault: 'Patient',
48
+ key: 'patientName',
49
+ },
50
+ age: {
51
+ // t('age', 'Age')
52
+ headerLabelKey: 'age',
53
+ headerLabelDefault: 'Age',
54
+ key: 'patientAge',
55
+ },
56
+ sex: {
57
+ // t('sex', 'Sex')
58
+ headerLabelKey: 'sex',
59
+ headerLabelDefault: 'Sex',
60
+ key: 'patientSex',
61
+ },
62
+ totalOrders: {
63
+ // t('totalOrders', 'Total Orders')
64
+ headerLabelKey: 'totalOrders',
65
+ headerLabelDefault: 'Total Orders',
66
+ key: 'totalOrders',
67
+ },
68
+ action: {
69
+ // t('action', 'Action')
70
+ headerLabelKey: 'action',
71
+ headerLabelDefault: 'Action',
72
+ key: 'action',
73
+ },
74
+ patientId: {
75
+ // t('patientId', 'Patient ID')
76
+ headerLabelKey: 'patientId',
77
+ headerLabelDefault: 'Patient ID',
78
+ key: 'patientId',
79
+ },
80
+ };
81
+
82
+ export interface OrdersDataTableProps {
83
+ /* Whether the data table should include a status filter dropdown */
84
+ useFilter?: boolean;
85
+ actionsSlotName?: string;
86
+ excludeColumns?: Array<string>;
87
+ fulfillerStatus?: FulfillerStatus;
88
+ newOrdersOnly?: boolean;
89
+ excludeCanceledAndDiscontinuedOrders?: boolean;
90
+ actions?: Array<OrderAction>;
91
+ }
33
92
 
34
93
  const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
35
94
  const { t } = useTranslation();
36
95
  const [filter, setFilter] = useState<FulfillerStatus>(null);
37
96
  const [searchString, setSearchString] = useState('');
97
+ const { labTableColumns, patientIdIdentifierTypeUuid } = useConfig<Config>();
38
98
 
39
- const { labOrders, isLoading } = useLabOrders(
40
- props.useFilter ? filter : props.fulfillerStatus,
41
- props.excludeCanceledAndDiscontinuedOrders,
42
- );
99
+ const { labOrders, isLoading } = useLabOrders({
100
+ status: props.useFilter ? filter : props.fulfillerStatus,
101
+ newOrdersOnly: props.newOrdersOnly,
102
+ excludeCanceled: props.excludeCanceledAndDiscontinuedOrders,
103
+ includePatientId: labTableColumns.includes('patientId'),
104
+ });
43
105
 
44
- const flattenedLabOrders: Order[] = useMemo(() => {
106
+ const flattenedLabOrders: Array<FlattenedOrder> = useMemo(() => {
45
107
  return (
46
108
  labOrders?.map((order) => {
47
109
  return {
48
- ...order,
110
+ id: order.uuid,
111
+ patientUuid: order.patient.uuid,
112
+ orderNumber: order.orderNumber,
49
113
  dateActivated: formatDate(parseDate(order.dateActivated)),
50
- patientName: order.patient?.person.display,
51
- patientUuid: order.patient?.uuid,
52
- patientAge: order.patient?.person?.age,
53
- status: order.fulfillerStatus ?? '--',
54
- orderer: order.orderer,
114
+ fulfillerStatus: order.fulfillerStatus,
115
+ urgency: order.urgency,
116
+ orderer: order.orderer?.display,
117
+ instructions: order.instructions,
118
+ fulfillerComment: order.fulfillerComment,
119
+ display: order.display,
55
120
  };
56
121
  }) ?? []
57
122
  );
58
123
  }, [labOrders]);
59
124
 
60
- function groupOrdersById(orders) {
61
- if (orders && orders.length > 0) {
62
- const groupedOrders = orders.reduce((acc, item) => {
63
- if (!acc[item.patientUuid]) {
64
- acc[item.patientUuid] = [];
65
- }
66
- acc[item.patientUuid].push(item);
67
- return acc;
68
- }, {});
125
+ const groupedOrdersByPatient = useMemo(() => {
126
+ if (labOrders && labOrders.length > 0) {
127
+ const patientUuids = [...new Set(labOrders.map((order) => order.patient.uuid))];
69
128
 
70
- return Object.keys(groupedOrders).map((patientId) => ({
71
- patientId: patientId,
72
- orders: groupedOrders[patientId],
73
- }));
129
+ return patientUuids.map((patientUuid) => {
130
+ const labOrdersForPatient = labOrders.filter((order) => order.patient.uuid === patientUuid);
131
+ const patient: Patient = labOrdersForPatient[0]?.patient;
132
+ const flattenedLabOrdersForPatient = flattenedLabOrders.filter((order) => order.patientUuid === patientUuid);
133
+ return {
134
+ patientId: patient.identifiers?.find(
135
+ (identifier) =>
136
+ identifier.preferred &&
137
+ !identifier.voided &&
138
+ identifier.identifierType.uuid === patientIdIdentifierTypeUuid,
139
+ )?.identifier,
140
+ patientUuid: patientUuid,
141
+ patientName: patient.person.display,
142
+ patientAge: patient.person.age,
143
+ patientSex: patient.person.gender,
144
+ totalOrders: flattenedLabOrdersForPatient.length,
145
+ orders: flattenedLabOrdersForPatient,
146
+ originalOrders: labOrdersForPatient,
147
+ };
148
+ });
74
149
  } else {
75
150
  return [];
76
151
  }
77
- }
152
+ }, [flattenedLabOrders, labOrders, patientIdIdentifierTypeUuid]);
78
153
 
79
- const groupedOrdersByPatient = groupOrdersById(flattenedLabOrders);
154
+ const searchResults = useMemo(() => {
155
+ if (searchString && searchString.trim() !== '') {
156
+ // Normalize the search string to lowercase
157
+ const lowerSearchString = searchString.toLowerCase();
158
+ return groupedOrdersByPatient.filter(
159
+ (orderGroup) =>
160
+ (labTableColumns.includes('name') && orderGroup.patientName.toLowerCase().includes(lowerSearchString)) ||
161
+ (labTableColumns.includes('patientId') && orderGroup.patientId.toLowerCase().includes(lowerSearchString)) ||
162
+ orderGroup.orders.some((order) => order.orderNumber.toLowerCase().includes(lowerSearchString)),
163
+ );
164
+ }
80
165
 
81
- const searchResults = useSearchGroupedResults(groupedOrdersByPatient, searchString);
166
+ return groupedOrdersByPatient;
167
+ }, [searchString, groupedOrdersByPatient, labTableColumns]);
82
168
 
83
169
  const orderStatuses = [
84
170
  { value: null, display: t('all', 'All') },
85
- { value: 'NEW', display: t('newStatus', 'NEW') },
86
171
  { value: 'RECEIVED', display: t('receivedStatus', 'RECEIVED') },
87
172
  { value: 'IN_PROGRESS', display: t('inProgressStatus', 'IN_PROGRESS') },
88
173
  { value: 'COMPLETED', display: t('completedStatus', 'COMPLETED') },
@@ -92,17 +177,23 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
92
177
  ];
93
178
 
94
179
  const columns = useMemo(() => {
95
- const baseColumns = [
96
- { id: 0, header: t('patient', 'Patient'), key: 'patientName' },
97
- { id: 1, header: t('age', 'Age'), key: 'patientAge' },
98
- { id: 2, header: t('gender', 'Gender'), key: 'patientGender' },
99
- { id: 3, header: t('totalOrders', 'Total Orders'), key: 'totalOrders' },
100
- ];
101
-
102
- const showActionColumn = flattenedLabOrders.some((order) => order.fulfillerStatus === 'COMPLETED');
103
-
104
- return showActionColumn ? [...baseColumns, { id: 4, header: t('action', 'Action'), key: 'action' }] : baseColumns;
105
- }, [t, flattenedLabOrders]);
180
+ return labTableColumns
181
+ .map((column) => {
182
+ const spec = labTableColumnSpec[column];
183
+ if (!spec) {
184
+ throw new Error(`Lab table has been configured with an invalid column: ${column}`);
185
+ }
186
+ if (spec.key === 'action') {
187
+ const showActionColumn = flattenedLabOrders.some((order) => order.fulfillerStatus === 'COMPLETED');
188
+ if (!showActionColumn) {
189
+ return null;
190
+ }
191
+ }
192
+ return { header: t(spec.headerLabelKey, spec.headerLabelDefault), key: spec.key };
193
+ })
194
+ .filter(Boolean)
195
+ .map((column, index) => ({ ...column, id: index }));
196
+ }, [t, flattenedLabOrders, labTableColumns]);
106
197
 
107
198
  const pageSizes = [10, 20, 30, 40, 50];
108
199
  const [currentPageSize, setPageSize] = useState(10);
@@ -127,32 +218,28 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
127
218
  };
128
219
 
129
220
  const tableRows = useMemo(() => {
130
- return paginatedLabOrders.map((order) => ({
131
- id: order.patientId,
132
- patientName: order.orders[0]?.patient.person.display,
133
- orders: order.orders,
134
- totalOrders: order.orders?.length,
135
- patientAge: order.orders[0]?.patient?.person?.age,
136
- patientGender: order.orders[0]?.patient?.person?.gender || '',
137
- action: order.orders.some((o) => o.fulfillerStatus === 'COMPLETED') ? (
221
+ return paginatedLabOrders.map((groupedOrder) => ({
222
+ ...groupedOrder,
223
+ id: groupedOrder.patientUuid,
224
+ action: groupedOrder.orders.some((o) => o.fulfillerStatus === 'COMPLETED') ? (
138
225
  <div className={styles.actionCell}>
139
226
  <OverflowMenu aria-label="Actions" flipped iconDescription="Actions">
140
227
  <ExtensionSlot
141
228
  className={styles.transitionOverflowMenuItemSlot}
142
229
  name="transition-overflow-menu-item-slot"
143
- state={{ patientUuid: order?.patientId }}
230
+ state={{ patientUuid: groupedOrder.patientUuid }}
144
231
  // Without tabIndex={0} here, the overflow menu incorrectly sets initial focus to the second item instead of the first.
145
232
  tabIndex={0}
146
233
  />
147
234
  <OverflowMenuItem
148
235
  className={styles.menuitem}
149
236
  itemText={t('editResults', 'Edit results')}
150
- onClick={() => handleLaunchModal(order?.orders)}
237
+ onClick={() => handleLaunchModal(groupedOrder.originalOrders)}
151
238
  />
152
239
  <OverflowMenuItem
153
240
  className={styles.menuitem}
154
241
  itemText={t('printTestResults', 'Print test results')}
155
- onClick={() => handlePrintModal(order?.orders)}
242
+ onClick={() => handlePrintModal(groupedOrder.originalOrders)}
156
243
  />
157
244
  </OverflowMenu>
158
245
  </div>
@@ -218,7 +305,7 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
218
305
  <TableExpandedRow colSpan={headers.length + 2}>
219
306
  <ListOrderDetails
220
307
  actions={props.actions}
221
- groupedOrders={groupedOrdersByPatient.find((item) => item.patientId === row.id)}
308
+ groupedOrders={groupedOrdersByPatient.find((item) => item.patientUuid === row.id)}
222
309
  />
223
310
  </TableExpandedRow>
224
311
  ) : (