import React, { useMemo, useState } from 'react'; import { DataTable, DataTableSkeleton, Dropdown, Layer, OverflowMenu, OverflowMenuItem, Pagination, Table, TableBody, TableCell, TableContainer, TableExpandedRow, TableExpandHeader, TableExpandRow, TableHead, TableHeader, TableRow, TableToolbar, TableToolbarContent, TableToolbarSearch, Tile, } from '@carbon/react'; import { ExtensionSlot, formatDate, parseDate, showModal, useConfig, usePagination } from '@openmrs/esm-framework'; import { useTranslation } from 'react-i18next'; import { type FulfillerStatus, type FlattenedOrder, type Order } from '../../types'; import { type Config } from '../../config-schema'; import { useLabOrders } from '../../laboratory.resource'; import { OrdersDateRangePicker } from './orders-date-range-picker.component'; import ListOrderDetails from './list-order-details.component'; import styles from './orders-data-table.scss'; const labTableColumnSpec = { name: { // t('patient', 'Patient') headerLabelKey: 'patient', headerLabelDefault: 'Patient', key: 'patientName', }, age: { // t('age', 'Age') headerLabelKey: 'age', headerLabelDefault: 'Age', key: 'patientAge', }, dob: { // t('dateOfBirth', 'Date of Birth') headerLabelKey: 'dob', headerLabelDefault: 'Date of Birth', key: 'patientDob', }, sex: { // t('sex', 'Sex') headerLabelKey: 'sex', headerLabelDefault: 'Sex', key: 'patientSex', }, totalOrders: { // t('totalOrders', 'Total Orders') headerLabelKey: 'totalOrders', headerLabelDefault: 'Total Orders', key: 'totalOrders', }, action: { // t('action', 'Action') headerLabelKey: 'action', headerLabelDefault: 'Action', key: 'action', }, patientId: { // t('patientId', 'Patient ID') headerLabelKey: 'patientId', headerLabelDefault: 'Patient ID', key: 'patientId', }, }; export interface OrdersDataTableProps { /* Whether the data table should include a status filter dropdown */ useFilter?: boolean; excludeColumns?: Array; fulfillerStatus?: FulfillerStatus; newOrdersOnly?: boolean; excludeCanceledAndDiscontinuedOrders?: boolean; } const OrdersDataTable: React.FC = (props) => { const { t } = useTranslation(); const [filter, setFilter] = useState(null); const [searchString, setSearchString] = useState(''); const { labTableColumns, patientIdIdentifierTypeUuid } = useConfig(); const { labOrders, isLoading } = useLabOrders({ status: props.useFilter ? filter : props.fulfillerStatus, newOrdersOnly: props.newOrdersOnly, excludeCanceled: props.excludeCanceledAndDiscontinuedOrders, includePatientId: labTableColumns.includes('patientId'), }); const flattenedLabOrders: Array = useMemo(() => { return ( labOrders?.map((order) => { return { id: order.uuid, patientUuid: order.patient.uuid, orderNumber: order.orderNumber, dateActivated: formatDate(parseDate(order.dateActivated)), fulfillerStatus: order.fulfillerStatus, urgency: order.urgency, orderer: order.orderer?.display, instructions: order.instructions, fulfillerComment: order.fulfillerComment, display: order.display, }; }) ?? [] ); }, [labOrders]); const groupedOrdersByPatient = useMemo(() => { if (labOrders && labOrders.length > 0) { const patientUuids = [...new Set(labOrders.map((order) => order.patient.uuid))]; return patientUuids.map((patientUuid) => { const labOrdersForPatient = labOrders.filter((order) => order.patient.uuid === patientUuid); const patient = labOrdersForPatient[0]?.patient; const flattenedLabOrdersForPatient = flattenedLabOrders.filter((order) => order.patientUuid === patientUuid); return { patientId: patient?.identifiers?.find( (identifier) => identifier.preferred && !identifier.voided && identifier.identifierType.uuid === patientIdIdentifierTypeUuid, )?.identifier, patientUuid: patientUuid, patientName: patient?.person?.display, patientAge: patient?.person?.age, patientDob: patient?.person?.birthdate ? formatDate(parseDate(patient.person.birthdate)) : undefined, patientSex: patient?.person?.gender, totalOrders: flattenedLabOrdersForPatient.length, orders: flattenedLabOrdersForPatient, originalOrders: labOrdersForPatient, }; }); } else { return []; } }, [flattenedLabOrders, labOrders, patientIdIdentifierTypeUuid]); const searchResults = useMemo(() => { if (searchString && searchString.trim() !== '') { // Normalize the search string to lowercase const lowerSearchString = searchString.toLowerCase(); return groupedOrdersByPatient.filter( (orderGroup) => (labTableColumns.includes('name') && orderGroup.patientName?.toLowerCase().includes(lowerSearchString)) || (labTableColumns.includes('patientId') && orderGroup.patientId?.toLowerCase().includes(lowerSearchString)) || orderGroup.orders.some((order) => order.orderNumber?.toLowerCase().includes(lowerSearchString)), ); } return groupedOrdersByPatient; }, [searchString, groupedOrdersByPatient, labTableColumns]); const orderStatuses = [ { value: null, display: t('all', 'All') }, { value: 'RECEIVED', display: t('receivedStatus', 'RECEIVED') }, { value: 'IN_PROGRESS', display: t('inProgressStatus', 'IN_PROGRESS') }, { value: 'COMPLETED', display: t('completedStatus', 'COMPLETED') }, { value: 'EXCEPTION', display: t('exceptionStatus', 'EXCEPTION') }, { value: 'ON_HOLD', display: t('onHoldStatus', 'ON_HOLD') }, { value: 'DECLINED', display: t('declinedStatus', 'DECLINED') }, ]; const columns = useMemo(() => { return labTableColumns .map((column) => { const spec = labTableColumnSpec[column]; if (!spec) { throw new Error(`Lab table has been configured with an invalid column: ${column}`); } if (spec.key === 'action') { const showActionColumn = flattenedLabOrders.some((order) => order.fulfillerStatus === 'COMPLETED'); if (!showActionColumn) { return null; } } return { header: t(spec.headerLabelKey, spec.headerLabelDefault), key: spec.key }; }) .filter(Boolean) .map((column) => ({ ...column, id: column.key })); }, [t, flattenedLabOrders, labTableColumns]); const pageSizes = [10, 20, 30, 40, 50]; const [currentPageSize, setPageSize] = useState(10); const { goTo, results: paginatedLabOrders, currentPage } = usePagination(searchResults, currentPageSize); const handleOrderStatusChange = ({ selectedItem }: { selectedItem: { value: FulfillerStatus; display: string } }) => setFilter(selectedItem.value); const handlePrintModal = (orders: Array) => { const completedOrders = orders.filter((order) => order.fulfillerStatus === 'COMPLETED'); const dispose = showModal('print-lab-results-modal', { closeModal: () => dispose(), orders: completedOrders, }); }; const handleLaunchModal = (orders: Array) => { const completedOrders = orders.filter((order) => order.fulfillerStatus === 'COMPLETED'); const dispose = showModal('edit-lab-results-modal', { orders: completedOrders, closeModal: () => dispose(), patient: completedOrders[0]?.patient, workspaceName: 'lab-app-test-results-form-workspace', }); }; const tableRows = useMemo(() => { return paginatedLabOrders.map((groupedOrder) => ({ ...groupedOrder, id: groupedOrder.patientUuid, action: groupedOrder.orders.some((o) => o.fulfillerStatus === 'COMPLETED') ? (
handleLaunchModal(groupedOrder.originalOrders)} /> handlePrintModal(groupedOrder.originalOrders)} />
) : null, })); }, [paginatedLabOrders, t]); if (isLoading) { return ; } return ( {({ getExpandHeaderProps, getHeaderProps, getRowProps, getTableProps, headers, rows }) => ( {props.useFilter && ( status.value === filter) : orderStatuses[0] } items={orderStatuses} itemToString={(item) => item?.display} label="" onChange={handleOrderStatusChange} titleText={t('filterOrdersByStatus', 'Filter orders by status') + ':'} type="inline" /> )} ) => setSearchString(e.target.value)} placeholder={t('searchThisList', 'Search this list')} size="sm" /> {headers.map((header) => ( {header.header} ))} {rows.map((row) => ( {row.cells.map((cell) => ( {cell.value?.content ?? cell.value} ))} {row.isExpanded ? ( item.patientUuid === row.id)} /> ) : ( )} ))}
{rows.length === 0 ? (

{t('noLabRequestsFound', 'No lab requests found')}

{t('checkFilters', 'Please check the filters above and try again')}

) : null} {rows.length > 0 && ( { if (pageSize !== currentPageSize) setPageSize(pageSize); if (page !== currentPage) goTo(page); }} /> )}
)}
); }; export default OrdersDataTable;