@kenyaemr/esm-imaging-orders-app 4.0.1-pre.1

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 (110) hide show
  1. package/.turbo/turbo-build.log +188 -0
  2. package/README.md +8 -0
  3. package/dist/123.js +2 -0
  4. package/dist/123.js.LICENSE.txt +40 -0
  5. package/dist/123.js.map +1 -0
  6. package/dist/144.js +2 -0
  7. package/dist/144.js.LICENSE.txt +19 -0
  8. package/dist/144.js.map +1 -0
  9. package/dist/225.js +1 -0
  10. package/dist/225.js.map +1 -0
  11. package/dist/300.js +1 -0
  12. package/dist/364.js +1 -0
  13. package/dist/364.js.map +1 -0
  14. package/dist/372.js +1 -0
  15. package/dist/372.js.map +1 -0
  16. package/dist/41.js +2 -0
  17. package/dist/41.js.LICENSE.txt +9 -0
  18. package/dist/41.js.map +1 -0
  19. package/dist/495.js +1 -0
  20. package/dist/495.js.map +1 -0
  21. package/dist/606.js +1 -0
  22. package/dist/606.js.map +1 -0
  23. package/dist/831.js +2 -0
  24. package/dist/831.js.LICENSE.txt +5 -0
  25. package/dist/831.js.map +1 -0
  26. package/dist/876.js +2 -0
  27. package/dist/876.js.LICENSE.txt +9 -0
  28. package/dist/876.js.map +1 -0
  29. package/dist/913.js +2 -0
  30. package/dist/913.js.LICENSE.txt +32 -0
  31. package/dist/913.js.map +1 -0
  32. package/dist/kenyaemr-esm-imaging-orders-app.js +1 -0
  33. package/dist/kenyaemr-esm-imaging-orders-app.js.buildmanifest.json +401 -0
  34. package/dist/kenyaemr-esm-imaging-orders-app.js.map +1 -0
  35. package/dist/main.js +2 -0
  36. package/dist/main.js.LICENSE.txt +60 -0
  37. package/dist/main.js.map +1 -0
  38. package/dist/routes.json +1 -0
  39. package/jest.config.js +8 -0
  40. package/package.json +55 -0
  41. package/src/config-schema.ts +51 -0
  42. package/src/constants.ts +3 -0
  43. package/src/declarations.d.ts +6 -0
  44. package/src/form/imaging-orders/add-imaging-orders/add-imaging-order.scss +44 -0
  45. package/src/form/imaging-orders/add-imaging-orders/add-imaging-order.workspace.tsx +86 -0
  46. package/src/form/imaging-orders/add-imaging-orders/imaging-order-form.component.tsx +354 -0
  47. package/src/form/imaging-orders/add-imaging-orders/imaging-order-form.scss +79 -0
  48. package/src/form/imaging-orders/add-imaging-orders/imaging-order.ts +19 -0
  49. package/src/form/imaging-orders/add-imaging-orders/imaging-type-search.scss +115 -0
  50. package/src/form/imaging-orders/add-imaging-orders/imaging-type-search.tsx +235 -0
  51. package/src/form/imaging-orders/add-imaging-orders/useImagingTypes.ts +89 -0
  52. package/src/form/imaging-orders/api.ts +230 -0
  53. package/src/form/imaging-orders/imaging-order-basket-panel/imaging-icon.component.tsx +40 -0
  54. package/src/form/imaging-orders/imaging-order-basket-panel/imaging-order-basket-item-tile.component.tsx +96 -0
  55. package/src/form/imaging-orders/imaging-order-basket-panel/imaging-order-basket-item-tile.scss +72 -0
  56. package/src/form/imaging-orders/imaging-order-basket-panel/imaging-order-basket-panel.extension.tsx +191 -0
  57. package/src/form/imaging-orders/imaging-order-basket-panel/imaging-order-basket-panel.scss +74 -0
  58. package/src/form/imaging-orders/useOrderConfig.ts +48 -0
  59. package/src/form/imaging-report-form/imaging-report-form.component.tsx +161 -0
  60. package/src/form/imaging-report-form/imaging-report-form.scss +30 -0
  61. package/src/form/imaging-report-form/imaging.resource.ts +360 -0
  62. package/src/header/imagining-header.component.tsx +17 -0
  63. package/src/header/imagining-header.scss +5 -0
  64. package/src/hooks/useOrdersWorklist.ts +59 -0
  65. package/src/hooks/useSearchGroupedResults.ts +27 -0
  66. package/src/hooks/useSearchResults.ts +51 -0
  67. package/src/imaging-orders.component.tsx +14 -0
  68. package/src/imaging-tabs/approved/approved-orders.component.tsx +31 -0
  69. package/src/imaging-tabs/approved/approved-orders.scss +0 -0
  70. package/src/imaging-tabs/imaging-tabs.component.tsx +79 -0
  71. package/src/imaging-tabs/imaging-tabs.scss +5 -0
  72. package/src/imaging-tabs/orders-not-done/orders-not-done.component.tsx +42 -0
  73. package/src/imaging-tabs/referred-test/referred-ordered.component.tsx +26 -0
  74. package/src/imaging-tabs/referred-test/referred-ordered.scss +6 -0
  75. package/src/imaging-tabs/review-ordered/review-imaging-report-modal/review-imaging-report-dialog.component.tsx +138 -0
  76. package/src/imaging-tabs/review-ordered/review-imaging-report-modal/review-imaging-report-dialog.scss +5 -0
  77. package/src/imaging-tabs/review-ordered/review-ordered.component.tsx +28 -0
  78. package/src/imaging-tabs/review-ordered/review-ordered.scss +0 -0
  79. package/src/imaging-tabs/test-ordered/pick-imaging-order/add-to-worklist-dialog.component.tsx +94 -0
  80. package/src/imaging-tabs/test-ordered/pick-imaging-order/add-to-worklist-dialog.resource.ts +137 -0
  81. package/src/imaging-tabs/test-ordered/pick-imaging-order/add-to-worklist-dialog.scss +38 -0
  82. package/src/imaging-tabs/test-ordered/reject-order-dialog/radiology-reject-reason.component.tsx +40 -0
  83. package/src/imaging-tabs/test-ordered/reject-order-dialog/reject-order-dialog.component.tsx +95 -0
  84. package/src/imaging-tabs/test-ordered/reject-order-dialog/reject-order-dialog.scss +14 -0
  85. package/src/imaging-tabs/test-ordered/tests-ordered.component.tsx +35 -0
  86. package/src/imaging-tabs/test-ordered/tests-ordered.scss +13 -0
  87. package/src/imaging-tabs/test-ordered/transition-patient-new-queue/transition-latest-queue-entry-button.component.tsx +34 -0
  88. package/src/imaging-tabs/test-ordered/transition-patient-new-queue/transition-latest-queue-entry-button.scss +14 -0
  89. package/src/imaging-tabs/work-list/work-list.component.tsx +45 -0
  90. package/src/imaging-tabs/work-list/work-list.resource.ts +150 -0
  91. package/src/imaging-tabs/work-list/work-list.scss +207 -0
  92. package/src/index.ts +45 -0
  93. package/src/left-panel-link.tsx +42 -0
  94. package/src/root.component.tsx +19 -0
  95. package/src/routes.json +58 -0
  96. package/src/shared/imaging.resource.tsx +65 -0
  97. package/src/shared/ui/common/action-button/action-button.component.tsx +66 -0
  98. package/src/shared/ui/common/action-button/order-action-extension.component.tsx +21 -0
  99. package/src/shared/ui/common/grouped-imaging-types.ts +48 -0
  100. package/src/shared/ui/common/grouped-orders-table.component.tsx +154 -0
  101. package/src/shared/ui/common/grouped-orders-table.scss +13 -0
  102. package/src/shared/ui/common/list-order-details.component.tsx +72 -0
  103. package/src/shared/ui/common/list-order-details.scss +52 -0
  104. package/src/shared/ui/common/order-detail.component.tsx +14 -0
  105. package/src/shared/ui/common/order-detail.scss +14 -0
  106. package/src/types/index.ts +49 -0
  107. package/src/utils/functions.ts +238 -0
  108. package/translations/en.json +69 -0
  109. package/tsconfig.json +5 -0
  110. package/webpack.config.js +1 -0
package/src/index.ts ADDED
@@ -0,0 +1,45 @@
1
+ import Root from './root.component';
2
+ import { moduleName } from './constants';
3
+ import { configSchema } from './config-schema';
4
+
5
+ import { defineConfigSchema, getSyncLifecycle } from '@openmrs/esm-framework';
6
+ import { createLeftPanelLink } from './left-panel-link';
7
+ import RejectImagingOrderModal from './imaging-tabs/test-ordered/reject-order-dialog/reject-order-dialog.component';
8
+ import ReviewImagingReportModal from './imaging-tabs/review-ordered/review-imaging-report-modal/review-imaging-report-dialog.component';
9
+ import ImagingReportForm from './form/imaging-report-form/imaging-report-form.component';
10
+ import AddImagingOrderWorkspace from './form/imaging-orders/add-imaging-orders/add-imaging-order.workspace';
11
+ import ImagingOrderBasketPanelExtension from './form/imaging-orders/imaging-order-basket-panel/imaging-order-basket-panel.extension';
12
+ import AddImagingToWorkListModal from './imaging-tabs/test-ordered/pick-imaging-order/add-to-worklist-dialog.component';
13
+
14
+ const options = {
15
+ featureName: 'esm-imaging-orders-app',
16
+ moduleName,
17
+ };
18
+
19
+ export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
20
+
21
+ export function startupApp() {
22
+ defineConfigSchema(moduleName, configSchema);
23
+ }
24
+
25
+ export const root = getSyncLifecycle(Root, options);
26
+
27
+ export const imagingOrdersLink = getSyncLifecycle(
28
+ createLeftPanelLink({
29
+ name: 'imaging-orders',
30
+ title: 'Imaging Orders',
31
+ }),
32
+ options,
33
+ );
34
+
35
+ // Modals
36
+ export const reviewImagingReportModal = getSyncLifecycle(ReviewImagingReportModal, options);
37
+
38
+ export const imagingOrderPanel = getSyncLifecycle(ImagingOrderBasketPanelExtension, options);
39
+ export const rejectImagingOrderModal = getSyncLifecycle(RejectImagingOrderModal, options);
40
+
41
+ // t('addImagingOrderWorkspaceTitle', 'Add Imaging order')
42
+ export const addImagingOrderWorkspace = getSyncLifecycle(AddImagingOrderWorkspace, options);
43
+
44
+ export const imagingReportForm = getSyncLifecycle(ImagingReportForm, options);
45
+ export const addImagingToWorkListModal = getSyncLifecycle(AddImagingToWorkListModal, options);
@@ -0,0 +1,42 @@
1
+ import React, { useMemo } from 'react';
2
+ import last from 'lodash-es/last';
3
+ import { BrowserRouter, useLocation } from 'react-router-dom';
4
+ import { ConfigurableLink } from '@openmrs/esm-framework';
5
+
6
+ export interface LinkConfig {
7
+ name: string;
8
+ title: string;
9
+ }
10
+
11
+ const isUuid = (value: string) => {
12
+ const regex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
13
+ return regex.test(value);
14
+ };
15
+
16
+ export const createLeftPanelLink = (config: LinkConfig) => () => {
17
+ return (
18
+ <BrowserRouter>
19
+ <LinkExtension config={config} />
20
+ </BrowserRouter>
21
+ );
22
+ };
23
+
24
+ export function LinkExtension({ config }: { config: LinkConfig }) {
25
+ const { name, title } = config;
26
+ const location = useLocation();
27
+ const spaBasePath = window.getOpenmrsSpaBase() + 'home';
28
+
29
+ let urlSegment = useMemo(() => decodeURIComponent(last(location.pathname.split('/'))), [location.pathname]);
30
+
31
+ if (isUuid(urlSegment)) {
32
+ urlSegment = 'billing';
33
+ }
34
+
35
+ return (
36
+ <ConfigurableLink
37
+ to={spaBasePath + '/' + name}
38
+ className={`cds--side-nav__link ${name === urlSegment && 'active-left-nav-link'}`}>
39
+ {title}
40
+ </ConfigurableLink>
41
+ );
42
+ }
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { BrowserRouter, Route, Routes } from 'react-router-dom';
3
+ import ImagingOrders from './imaging-orders.component';
4
+ import { WorkspaceContainer } from '@openmrs/esm-framework';
5
+
6
+ const Root: React.FC = () => {
7
+ const baseName = window.getOpenmrsSpaBase() + 'home/imaging-orders';
8
+
9
+ return (
10
+ <BrowserRouter basename={baseName}>
11
+ <Routes>
12
+ <Route path="/" element={<ImagingOrders />} />
13
+ </Routes>
14
+ <WorkspaceContainer contextKey="imaging-orders" />
15
+ </BrowserRouter>
16
+ );
17
+ };
18
+
19
+ export default Root;
@@ -0,0 +1,58 @@
1
+ {
2
+ "$schema": "https://json.openmrs.org/routes.schema.json",
3
+ "backendDependencies": {
4
+ "fhir2": ">=1.2",
5
+ "webservices.rest": "^2.24.0"
6
+ },
7
+ "extensions": [
8
+ {
9
+ "component": "imagingOrdersLink",
10
+ "name": "imaging-orders-link",
11
+ "slot": "homepage-dashboard-slot",
12
+ "meta": {
13
+ "name": "imaging-orders",
14
+ "title": "imaging-orders",
15
+ "slot": "imaging-dashboard-slot"
16
+ }
17
+ },
18
+ {
19
+ "component": "root",
20
+ "name": "imaging-dashboard-root",
21
+ "slot": "imaging-dashboard-slot"
22
+ },
23
+ {
24
+ "name": "imaging-order-panel",
25
+ "component": "imagingOrderPanel",
26
+ "slot": "order-basket-slot",
27
+ "order": 3
28
+ }
29
+ ],
30
+ "workspaces": [
31
+ {
32
+ "name": "add-imaging-order",
33
+ "type": "order",
34
+ "component": "addImagingOrderWorkspace",
35
+ "title": "Add Imaging order"
36
+ },
37
+ {
38
+ "name": "imaging-report-form",
39
+ "component": "imagingReportForm",
40
+ "title": "Imaging Report Form",
41
+ "type": "form"
42
+ }
43
+ ],
44
+ "modals": [
45
+ {
46
+ "name": "review-imaging-report-modal",
47
+ "component": "reviewImagingReportModal"
48
+ },
49
+ {
50
+ "name": "reject-imaging-order-modal",
51
+ "component": "rejectImagingOrderModal"
52
+ },
53
+ {
54
+ "name": "add-imaging-to-work-list-modal",
55
+ "component": "addImagingToWorkListModal"
56
+ }
57
+ ]
58
+ }
@@ -0,0 +1,65 @@
1
+ import useSWR, { mutate } from 'swr';
2
+ import { openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
3
+
4
+ import { Result } from '../imaging-tabs/work-list/work-list.resource';
5
+ import { useCallback, useMemo } from 'react';
6
+ import { ImagingConfig } from '../config-schema';
7
+
8
+ /**
9
+ * Hook to fetch and process imaging order statistics based on fulfiller status.
10
+ *
11
+ * @param fulfillerStatus - The status of the order to filter by.
12
+ * @returns An object containing the count of orders, loading state, error state, and a mutate function.
13
+ */
14
+ export function useImagingOrderStats(fulfillerStatus: string) {
15
+ const {
16
+ orders: { radiologyOrderTypeUuid },
17
+ radiologyConceptClassUuid,
18
+ } = useConfig<ImagingConfig>();
19
+
20
+ const responseFormat =
21
+ 'custom:(uuid,orderNumber,patient:ref,concept:(uuid,display,conceptClass),action,careSetting,orderer:ref,urgency,instructions,commentToFulfiller,display,fulfillerStatus,dateStopped)';
22
+ const apiUrl = useMemo(() => {
23
+ const orderTypeParam = `orderTypes=${radiologyOrderTypeUuid}&fulfillerStatus=${fulfillerStatus}&v=${responseFormat}`;
24
+ return `/ws/rest/v1/order?${orderTypeParam}`;
25
+ }, [radiologyOrderTypeUuid, fulfillerStatus, responseFormat]);
26
+
27
+ const mutateOrders = useCallback(() => {
28
+ return mutate(
29
+ (key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/order?orderType=${radiologyOrderTypeUuid}`),
30
+ );
31
+ }, [radiologyOrderTypeUuid]);
32
+
33
+ const { data, error, isLoading } = useSWR<{ data: { results: Array<Result> } }, Error>(apiUrl, openmrsFetch);
34
+
35
+ const radiologyOrders = useMemo(() => {
36
+ return data?.data?.results?.filter((order) => {
37
+ const baseConditions =
38
+ order.dateStopped === null && order.concept.conceptClass.uuid === radiologyConceptClassUuid;
39
+
40
+ switch (fulfillerStatus) {
41
+ case '':
42
+ return baseConditions && order.fulfillerStatus === null && order.action === 'NEW';
43
+ case 'IN_PROGRESS':
44
+ case 'DECLINED':
45
+ case 'COMPLETED':
46
+ case 'EXCEPTION':
47
+ return baseConditions && order.fulfillerStatus === fulfillerStatus && order.action !== 'DISCONTINUE';
48
+ default:
49
+ return false;
50
+ }
51
+ });
52
+ }, [data, fulfillerStatus, radiologyConceptClassUuid]);
53
+
54
+ const count = useMemo(
55
+ () => (fulfillerStatus != null ? radiologyOrders?.length ?? 0 : 0),
56
+ [fulfillerStatus, radiologyOrders],
57
+ );
58
+
59
+ return {
60
+ count,
61
+ isLoading,
62
+ isError: error,
63
+ mutate: mutateOrders,
64
+ };
65
+ }
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button } from '@carbon/react';
4
+ import { showModal, launchWorkspace } from '@openmrs/esm-framework';
5
+ import { Order } from '@openmrs/esm-patient-common-lib';
6
+ import OrderActionExtension from './order-action-extension.component';
7
+ import { Result } from '../../../../imaging-tabs/work-list/work-list.resource';
8
+
9
+ type ActionButtonProps = {
10
+ action: {
11
+ actionName: string;
12
+ order: number;
13
+ };
14
+ order: Result;
15
+ patientUuid: string;
16
+ };
17
+
18
+ const ActionButton: React.FC<ActionButtonProps> = ({ action, order, patientUuid }) => {
19
+ const { t } = useTranslation();
20
+
21
+ const handleOpenImagingReportForm = () => {
22
+ launchWorkspace('imaging-report-form', {
23
+ patientUuid,
24
+ order,
25
+ });
26
+ };
27
+
28
+ switch (action.actionName) {
29
+ case 'add-imaging-to-work-list-modal':
30
+ return <OrderActionExtension order={order as unknown as Order} />;
31
+
32
+ case 'imaging-report-form':
33
+ return (
34
+ <Button kind="primary" onClick={handleOpenImagingReportForm}>
35
+ {t('imagingReportForm', 'Imaging Report Form')}
36
+ </Button>
37
+ );
38
+
39
+ case 'review-imaging-report-dialog':
40
+ case 'reject-imaging-order-modal':
41
+ return (
42
+ <Button
43
+ kind={action.actionName === 'reject-imaging-order-modal' ? 'danger' : 'tertiary'}
44
+ onClick={() => {
45
+ const dispose = showModal(action.actionName, {
46
+ closeModal: () => dispose(),
47
+ order: order,
48
+ });
49
+ }}>
50
+ {t(
51
+ action.actionName.replace(/-/g, ''),
52
+ action.actionName
53
+ .split('-')
54
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
55
+ .join(' ')
56
+ .replace('Modal', ''),
57
+ )}
58
+ </Button>
59
+ );
60
+
61
+ default:
62
+ return null;
63
+ }
64
+ };
65
+
66
+ export default ActionButton;
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { Order } from '@openmrs/esm-patient-common-lib';
3
+ import { ExtensionSlot, restBaseUrl } from '@openmrs/esm-framework';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ type OrderActionProps = {
7
+ order: Order;
8
+ };
9
+
10
+ const OrderActionExtension: React.FC<OrderActionProps> = ({ order }) => {
11
+ const { t } = useTranslation();
12
+ const state = {
13
+ order: order,
14
+ modalName: 'add-imaging-to-work-list-modal',
15
+ actionText: t('pickImagingOrder', 'Pick Imaging Order'),
16
+ additionalProps: { mutateUrl: `${restBaseUrl}/order` },
17
+ };
18
+ return <ExtensionSlot name="imaging-orders-action" state={state} />;
19
+ };
20
+
21
+ export default OrderActionExtension;
@@ -0,0 +1,48 @@
1
+ import { Result } from '../../../imaging-tabs/work-list/work-list.resource';
2
+
3
+ export type FulfillerStatus = '' | 'IN_PROGRESS' | 'DECLINED' | 'COMPLETED' | 'EXCEPTION';
4
+
5
+ export type WorkListProps = {
6
+ fulfillerStatus: FulfillerStatus;
7
+ };
8
+
9
+ export interface ResultsOrderProps {
10
+ order: Result;
11
+ patientUuid: string;
12
+ }
13
+
14
+ export interface RejectOrderProps {
15
+ order: Result;
16
+ }
17
+
18
+ export interface InstructionsProps {
19
+ order: Result;
20
+ }
21
+
22
+ export interface GroupedOrders {
23
+ patientId: string;
24
+ orders: Array<Result>;
25
+ }
26
+ export interface GroupedOrdersTableProps {
27
+ orders: Array<Result>;
28
+ showStatus: boolean;
29
+ showStartButton: boolean;
30
+ showActions: boolean;
31
+ showOrderType: boolean;
32
+ actions: Array<OrderAction>;
33
+ title: string;
34
+ }
35
+
36
+ export interface ListOrdersDetailsProps {
37
+ groupedOrders: GroupedOrders;
38
+ showStatus: boolean;
39
+ showStartButton: boolean;
40
+ showActions: boolean;
41
+ showOrderType: boolean;
42
+ actions: Array<OrderAction>;
43
+ }
44
+
45
+ export interface OrderAction {
46
+ actionName: string;
47
+ order: 0 | number;
48
+ }
@@ -0,0 +1,154 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import styles from './grouped-orders-table.scss';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { usePagination } from '@openmrs/esm-framework';
5
+ import { GroupedOrdersTableProps } from './grouped-imaging-types';
6
+ import {
7
+ Table,
8
+ TableHead,
9
+ TableRow,
10
+ TableHeader,
11
+ TableBody,
12
+ TableExpandRow,
13
+ TableExpandedRow,
14
+ TableExpandHeader,
15
+ TableCell,
16
+ DataTable,
17
+ TableContainer,
18
+ TableToolbarSearch,
19
+ TableToolbarContent,
20
+ TableToolbar,
21
+ } from '@carbon/react';
22
+ import ListOrderDetails from './list-order-details.component';
23
+ import { EmptyState } from '@openmrs/esm-patient-common-lib';
24
+ import { useSearchGroupedResults } from '../../../hooks/useSearchGroupedResults';
25
+ import TransitionLatestQueueEntryButton from '../../../imaging-tabs/test-ordered/transition-patient-new-queue/transition-latest-queue-entry-button.component';
26
+
27
+ const GroupedOrdersTable: React.FC<GroupedOrdersTableProps> = (props) => {
28
+ const workListEntries = props.orders;
29
+ const { t } = useTranslation();
30
+ const [currentPageSize] = useState<number>(10);
31
+ const [searchString, setSearchString] = useState<string>('');
32
+
33
+ function groupOrdersById(orders) {
34
+ if (orders && orders.length > 0) {
35
+ const groupedOrders = orders.reduce((acc, item) => {
36
+ if (!acc[item.patient.uuid]) {
37
+ acc[item.patient.uuid] = [];
38
+ }
39
+ acc[item.patient.uuid].push(item);
40
+ return acc;
41
+ }, {});
42
+
43
+ // Convert the result to an array of objects with patientId and orders
44
+ return Object.keys(groupedOrders).map((patientId) => ({
45
+ patientId: patientId,
46
+ orders: groupedOrders[patientId],
47
+ }));
48
+ } else {
49
+ return [];
50
+ }
51
+ }
52
+ const groupedOrdersByPatient = groupOrdersById(workListEntries);
53
+ const searchResults = useSearchGroupedResults(groupedOrdersByPatient, searchString);
54
+ const { goTo, results: paginatedResults, currentPage } = usePagination(searchResults, currentPageSize);
55
+
56
+ const rowData = useMemo(() => {
57
+ return paginatedResults.map((patient) => ({
58
+ id: patient.patientId,
59
+ patientName: patient.orders[0].patient?.display?.split('-')[1],
60
+ orders: patient.orders,
61
+ totalOrders: patient.orders?.length,
62
+ fulfillerStatus: patient.orders[0].fulfillerStatus,
63
+ action:
64
+ patient.orders[0].fulfillerStatus === 'COMPLETED' ? (
65
+ <TransitionLatestQueueEntryButton patientUuid={patient.patientId} />
66
+ ) : null,
67
+ }));
68
+ }, [paginatedResults]);
69
+
70
+ const tableColumns = useMemo(() => {
71
+ const baseColumns = [
72
+ { key: 'patientName', header: t('patientName', 'Patient Name') },
73
+ { key: 'totalOrders', header: t('totalOrders', 'Total Orders') },
74
+ ];
75
+
76
+ const showActionColumn = workListEntries.some((order) => order.fulfillerStatus === 'COMPLETED');
77
+
78
+ return showActionColumn ? [...baseColumns, { key: 'action', header: t('action', 'Action') }] : baseColumns;
79
+ }, [workListEntries, t]);
80
+
81
+ if (paginatedResults.length === 0) {
82
+ return <EmptyState headerTitle={props.title} displayText={t('noOrdersDescription', 'No orders')} />;
83
+ }
84
+
85
+ return (
86
+ <DataTable size="md" useZebraStyle rows={rowData} headers={tableColumns}>
87
+ {({ rows, headers, getHeaderProps, getRowProps, getExpandedRowProps, getTableProps, getTableContainerProps }) => (
88
+ <TableContainer
89
+ className={styles.dataTable}
90
+ title={props.title}
91
+ description={t('groupedOrdersTableDescription', 'Orders grouped by patient, expand row to view all orders')}
92
+ {...getTableContainerProps()}>
93
+ <TableToolbar>
94
+ <TableToolbarContent>
95
+ <TableToolbarSearch
96
+ size="sm"
97
+ placeholder="Search by patient name"
98
+ persistent={true}
99
+ onChange={(event) => setSearchString(event.target.value)}
100
+ />
101
+ </TableToolbarContent>
102
+ </TableToolbar>
103
+ <Table {...getTableProps()} aria-label="sample table">
104
+ <TableHead>
105
+ <TableRow>
106
+ <TableExpandHeader aria-label="expand row" />
107
+ {headers.map((header, i) => (
108
+ <TableHeader
109
+ key={i}
110
+ {...getHeaderProps({
111
+ header,
112
+ })}>
113
+ {header.header}
114
+ </TableHeader>
115
+ ))}
116
+ </TableRow>
117
+ </TableHead>
118
+ <TableBody>
119
+ {rows.map((row) => (
120
+ <React.Fragment key={row.id}>
121
+ <TableExpandRow
122
+ {...getRowProps({
123
+ row,
124
+ })}>
125
+ {row.cells.map((cell) => (
126
+ <TableCell key={cell.id}>{cell.value}</TableCell>
127
+ ))}
128
+ </TableExpandRow>
129
+ <TableExpandedRow
130
+ colSpan={headers.length + 1}
131
+ className="demo-expanded-td"
132
+ {...getExpandedRowProps({
133
+ row,
134
+ })}>
135
+ <ListOrderDetails
136
+ actions={props.actions}
137
+ groupedOrders={groupedOrdersByPatient.find((item) => item.patientId === row.id)}
138
+ showActions={props.showActions}
139
+ showOrderType={props.showOrderType}
140
+ showStartButton={props.showStartButton}
141
+ showStatus={props.showStatus}
142
+ />
143
+ </TableExpandedRow>
144
+ </React.Fragment>
145
+ ))}
146
+ </TableBody>
147
+ </Table>
148
+ </TableContainer>
149
+ )}
150
+ </DataTable>
151
+ );
152
+ };
153
+
154
+ export default GroupedOrdersTable;
@@ -0,0 +1,13 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/colors';
3
+
4
+ .dataTable {
5
+ display: flex;
6
+ flex-direction: column;
7
+ border: 1px solid colors.$gray-20;
8
+
9
+ & > section {
10
+ position: relative;
11
+ margin-bottom: layout.$spacing-01;
12
+ }
13
+ }
@@ -0,0 +1,72 @@
1
+ import React, { useMemo } from 'react';
2
+ import styles from './list-order-details.scss';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { formatDate, parseDate } from '@openmrs/esm-framework';
5
+ import { ListOrdersDetailsProps } from './grouped-imaging-types';
6
+ import { Tile } from '@carbon/react';
7
+ import { OrderDetail } from './order-detail.component';
8
+ import ActionButton from './action-button/action-button.component';
9
+
10
+ const ListOrderDetails: React.FC<ListOrdersDetailsProps> = (props) => {
11
+ const orders = props.groupedOrders?.orders;
12
+ const { t } = useTranslation();
13
+ const orderRows = useMemo(() => {
14
+ return orders
15
+ ?.filter((item) => item.action === 'NEW')
16
+ .map((entry) => ({
17
+ ...entry,
18
+ id: entry.uuid,
19
+ orderNumber: entry.orderNumber,
20
+ procedure: entry.display,
21
+ status: entry.fulfillerStatus ? entry.fulfillerStatus : '--',
22
+ urgency: entry.urgency,
23
+ orderer: entry.orderer?.display,
24
+ instructions: entry.instructions ? entry.instructions : '--',
25
+ date: <span className={styles['single-line-display']}>{formatDate(parseDate(entry?.dateActivated))}</span>,
26
+ }));
27
+ }, [orders]);
28
+
29
+ return (
30
+ <div className={styles.ordersContainer}>
31
+ {orderRows.map((row, index) => (
32
+ <Tile className={styles.orderTile}>
33
+ {props.showActions && (
34
+ <div className={styles.actionBtns}>
35
+ {props.actions
36
+ .sort((a, b) => {
37
+ // Replace 'property' with the actual property you want to sort by
38
+ if (a.order < b.order) {
39
+ return -1;
40
+ }
41
+ if (a.order > b.order) {
42
+ return 1;
43
+ }
44
+ return 0;
45
+ })
46
+ .map((action) => (
47
+ <ActionButton
48
+ key={action.actionName}
49
+ action={action}
50
+ order={orders.find((order) => order.uuid === row.id)}
51
+ patientUuid={row.patient.uuid}
52
+ />
53
+ ))}
54
+ </div>
55
+ )}
56
+ <div>
57
+ <OrderDetail label={t('date', 'DATE').toUpperCase()} value={row.date} />
58
+ <OrderDetail label={t('orderNumber', 'Order Number').toUpperCase()} value={row.orderNumber} />
59
+ <OrderDetail label={t('procedure', 'procedure').toUpperCase()} value={row.procedure} />
60
+
61
+ {props.showStatus && <OrderDetail label={t('status', 'Status').toUpperCase()} value={row.status} />}
62
+ <OrderDetail label={t('urgency', 'urgency').toUpperCase()} value={row.urgency} />
63
+ <OrderDetail label={t('orderer', 'orderer').toUpperCase()} value={row.orderer} />
64
+ <OrderDetail label={t('instructions', 'Instructions').toUpperCase()} value={row.instructions} />
65
+ </div>
66
+ </Tile>
67
+ ))}
68
+ </div>
69
+ );
70
+ };
71
+
72
+ export default ListOrderDetails;
@@ -0,0 +1,52 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/colors';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .orderTile {
6
+ display: flex;
7
+ flex-direction: row-reverse;
8
+ justify-content: space-between;
9
+ width: 100%;
10
+ }
11
+
12
+ .ordersContainer {
13
+ display: flex;
14
+ flex-direction: column;
15
+ max-width: 100%;
16
+ margin-bottom: 1rem;
17
+
18
+ &:global(.cds--tile) {
19
+ min-height: 3rem !important;
20
+ padding-left: 10px !important;
21
+ }
22
+ }
23
+
24
+ .ordersContainer > :global(.cds--tile) {
25
+ min-height: 3rem !important;
26
+ padding-left: 10px !important;
27
+ margin: auto;
28
+ }
29
+
30
+ .orderPropertyDisplay {
31
+ font-size: 15px !important;
32
+ }
33
+
34
+ .bodyLong01 {
35
+ font-size: 13px !important;
36
+ }
37
+
38
+ .displayValue {
39
+ color: #525252;
40
+ font-weight: bold;
41
+ width: layout.$spacing-05;
42
+ height: layout.$spacing-05;
43
+ }
44
+
45
+ .actionBtns {
46
+ display: flex;
47
+ align-items: flex-end;
48
+ column-gap: layout.$spacing-01;
49
+ & > button {
50
+ max-height: layout.$spacing-07;
51
+ }
52
+ }
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import styles from './order-detail.scss';
3
+
4
+ export const OrderDetail: React.FC<{ label: string; value: string | any }> = ({ label, value }) => {
5
+ return (
6
+ <div>
7
+ <p className={styles.bodyLong01}>
8
+ <span className={styles.label01}>{label}</span>
9
+ {' : '}
10
+ <span className={styles.displayValue}>{value}</span>
11
+ </p>
12
+ </div>
13
+ );
14
+ };