Skip to content

Commit

Permalink
(feat) O3-3676 Implement ability to add patient to a queue from lab a…
Browse files Browse the repository at this point in the history
…pp (#111)

* (feat) ability to patient to new queue from laboratory

* (feat) added jest unit
  • Loading branch information
its-kios09 authored Jan 8, 2025
1 parent 9dc548f commit ef73f5f
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 67 deletions.
109 changes: 44 additions & 65 deletions src/components/orders-table/orders-data-table.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type { FulfillerStatus, OrdersDataTableProps } from '../../types';
import { OrdersDateRangePicker } from './orders-date-range-picker.component';
import ListOrderDetails from './list-order-details.component';
import styles from './orders-data-table.scss';
import TransitionLatestQueueEntryButton from '../../lab-tabs/actions/transition-patient-to-new-queue/transition-patient-to-new-queue.component';

const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
const { t } = useTranslation();
Expand Down Expand Up @@ -63,7 +64,6 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
return acc;
}, {});

// Convert the result to an array of objects with patientId and orders
return Object.keys(groupedOrders).map((patientId) => ({
patientId: patientId,
orders: groupedOrders[patientId],
Expand All @@ -72,52 +72,34 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
return [];
}
}

const groupedOrdersByPatient = groupOrdersById(flattenedLabOrders);

const searchResults = useSearchGroupedResults(groupedOrdersByPatient, searchString);

const orderStatuses = [
{
value: null,
display: t('all', 'All'),
},
{
value: 'NEW',
display: t('newStatus', 'NEW'),
},
{
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'),
},
{ value: null, display: t('all', 'All') },
{ value: 'NEW', display: t('newStatus', 'NEW') },
{ 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 [
const baseColumns = [
{ id: 0, header: t('patient', 'Patient'), key: 'patientName' },
{ id: 1, header: t('age', 'Age'), key: 'patientAge' },
{ id: 2, header: t('totalOrders', 'Total Orders'), key: 'totalOrders' },
{ id: 2, header: t('gender', 'Gender'), key: 'patientGender' },
{ id: 3, header: t('totalOrders', 'Total Orders'), key: 'totalOrders' },
];
}, [t]);

const showActionColumn = flattenedLabOrders.some((order) => order.fulfillerStatus === 'COMPLETED');

return showActionColumn ? [...baseColumns, { id: 4, header: t('action', 'Action'), key: 'action' }] : baseColumns;
}, [t, flattenedLabOrders]);

const pageSizes = [10, 20, 30, 40, 50];
const [currentPageSize, setPageSize] = useState(10);
Expand All @@ -128,16 +110,21 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
const tableRows = useMemo(() => {
return paginatedLabOrders.map((order) => ({
id: order.patientId,
patientName: order.orders[0].patient?.display?.split('-')[1],
patientName: order.orders[0]?.patient?.display?.split('-')[1],
orders: order.orders,
totalOrders: order.orders?.length,
patientAge: order.orders[0].patient?.person?.age,
patientAge: order.orders[0]?.patient?.person?.age,
patientGender: order.orders[0]?.patient?.person?.gender,
action: order.orders.some((o) => o.fulfillerStatus === 'COMPLETED') ? (
<TransitionLatestQueueEntryButton patientUuid={order.patientId} />
) : null,
}));
}, [paginatedLabOrders]);

if (isLoading) {
return <DataTableSkeleton className={styles.loader} role="progressbar" showHeader={false} showToolbar={false} />;
}

return (
<DataTable rows={tableRows} headers={columns} useZebraStyles>
{({ getExpandHeaderProps, getHeaderProps, getRowProps, getTableProps, headers, rows }) => (
Expand Down Expand Up @@ -180,27 +167,23 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => {
return (
<React.Fragment key={row.id}>
<TableExpandRow {...getRowProps({ row })} key={row.id}>
{row.cells.map((cell) => (
<TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>
))}
</TableExpandRow>
{row.isExpanded ? (
<TableExpandedRow colSpan={headers.length + 1}>
<ListOrderDetails
actions={props.actions}
groupedOrders={groupedOrdersByPatient.find((item) => item.patientId === row.id)}
/>
</TableExpandedRow>
) : (
<TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
)}
</React.Fragment>
);
})}
{rows.map((row) => (
<React.Fragment key={row.id}>
<TableExpandRow {...getRowProps({ row })} key={row.id}>
{row.cells.map((cell) => (
<TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>
))}
</TableExpandRow>
{row.isExpanded ? (
<TableExpandedRow colSpan={headers.length + 1}>
<ListOrderDetails
actions={props.actions}
groupedOrders={groupedOrdersByPatient.find((item) => item.patientId === row.id)}
/>
</TableExpandedRow>
) : null}
</React.Fragment>
))}
</TableBody>
</Table>
{rows.length === 0 ? (
Expand All @@ -225,12 +208,8 @@ const OrdersDataTable: React.FC<OrdersDataTableProps> = (props) => {
totalItems={groupedOrdersByPatient?.length}
className={styles.pagination}
onChange={({ pageSize, page }) => {
if (pageSize !== currentPageSize) {
setPageSize(pageSize);
}
if (page !== currentPage) {
goTo(page);
}
if (pageSize !== currentPageSize) setPageSize(pageSize);
if (page !== currentPage) goTo(page);
}}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import styles from './transition-patient-to-new-queue.scss';
import { showModal } from '@openmrs/esm-framework';
import { Button } from '@carbon/react';
import { AirlineManageGates } from '@carbon/react/icons';

interface TransitionLatestQueueEntryButtonProps {
patientUuid: string;
}

const TransitionLatestQueueEntryButton: React.FC<TransitionLatestQueueEntryButtonProps> = ({ patientUuid }) => {
const { t } = useTranslation();

const launchModal = () => {
const dispose = showModal('transition-patient-to-latest-queue-modal', {
closeModal: () => dispose(),
patientUuid,
});
};

return (
<Button
kind="tertiary"
className={styles.addPatientToQueue}
onClick={launchModal}
size="sm"
renderIcon={() => <AirlineManageGates size={18} />}
>
{t('transition', 'Transition')}
</Button>
);
};

export default TransitionLatestQueueEntryButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@use '@carbon/layout';
@use '@carbon/styles/scss/type';

.addPatientToQueue {
--cds-layout-size-height-context: var(--cds-layout-size-height-sm, 2rem);
--cds-layout-size-height: var(--cds-layout-size-height-context);
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 0 layout.$spacing-04;
gap: layout.$spacing-05;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { useTranslation } from 'react-i18next';
import { showModal } from '@openmrs/esm-framework';
import TransitionLatestQueueEntryButton from './transition-patient-to-new-queue.component';

jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));

jest.mock('@openmrs/esm-framework', () => ({
showModal: jest.fn(),
}));

jest.mock('@carbon/react', () => ({
Button: ({ children, onClick, renderIcon }: any) => (
<button onClick={onClick} data-testid="transition-button">
{renderIcon && renderIcon()}
{children}
</button>
),
}));

jest.mock('@carbon/react/icons', () => ({
AirlineManageGates: () => <svg data-testid="airline-icon" />,
}));

describe('TransitionLatestQueueEntryButton', () => {
const patientUuid = '1234-uuid';

beforeEach(() => {
jest.clearAllMocks();
});

it('renders the button with correct text and icon', () => {
render(<TransitionLatestQueueEntryButton patientUuid={patientUuid} />);

const button = screen.getByTestId('transition-button');
expect(button).toBeInTheDocument();
expect(button).toHaveTextContent('transition');

const icon = screen.getByTestId('airline-icon');
expect(icon).toBeInTheDocument();
});

it('calls the showModal function when clicked', () => {
render(<TransitionLatestQueueEntryButton patientUuid={patientUuid} />);

const button = screen.getByTestId('transition-button');
fireEvent.click(button);

expect(showModal).toHaveBeenCalledTimes(1);
expect(showModal).toHaveBeenCalledWith(
'transition-patient-to-latest-queue-modal',
expect.objectContaining({
closeModal: expect.any(Function),
patientUuid,
}),
);
});
});
3 changes: 2 additions & 1 deletion src/lab-tiles/all-lab-requests-tile.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import LabSummaryTile from '../components/summary-tile/lab-summary-tile.componen

const AllLabRequestsTile = () => {
const { t } = useTranslation();
const { labOrders } = useLabOrders();

const { labOrders } = useLabOrders('NEW');

return (
<LabSummaryTile
Expand Down
2 changes: 1 addition & 1 deletion src/laboratory-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function useLabOrders(status: 'NEW' | FulfillerStatus = null, excludeCanc
const fulfillerStatus = useMemo(() => (status === 'NEW' ? null : status), [status]);
const newOrdersOnly = status === 'NEW';
const customRepresentation =
'custom:(uuid,orderNumber,patient:(uuid,display,person:(uuid,display,age)),concept:(uuid,display),action,careSetting:(uuid,display,description,careSettingType,display),previousOrder,dateActivated,scheduledDate,dateStopped,autoExpireDate,encounter:(uuid,display),orderer:(uuid,display),orderReason,orderReasonNonCoded,orderType:(uuid,display,name,description,conceptClasses,parent),urgency,instructions,commentToFulfiller,display,fulfillerStatus,fulfillerComment,specimenSource,laterality,clinicalHistory,frequency,numberOfRepeats)';
'custom:(uuid,orderNumber,patient:(uuid,display,person:(uuid,display,age,gender)),concept:(uuid,display),action,careSetting:(uuid,display,description,careSettingType,display),previousOrder,dateActivated,scheduledDate,dateStopped,autoExpireDate,encounter:(uuid,display),orderer:(uuid,display),orderReason,orderReasonNonCoded,orderType:(uuid,display,name,description,conceptClasses,parent),urgency,instructions,commentToFulfiller,display,fulfillerStatus,fulfillerComment,specimenSource,laterality,clinicalHistory,frequency,numberOfRepeats)';
let url = `${restBaseUrl}/order?orderTypes=${laboratoryOrderTypeUuid}&v=${customRepresentation}`;
url = fulfillerStatus ? url + `&fulfillerStatus=${fulfillerStatus}` : url;
url = excludeCanceled ? `${url}&excludeCanceledAndExpired=true&excludeDiscontinueOrders=true` : url;
Expand Down
3 changes: 3 additions & 0 deletions translations/en.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"action": "Action",
"addLabResult": "Add lab results",
"age": "Age",
"all": "All",
Expand All @@ -16,6 +17,7 @@
"exceptionStatus": "EXCEPTION",
"filterOrdersByStatus": "Filter orders by status",
"fulfillerComment": "Fulfiller comment",
"gender": "Gender",
"In progress": "In progress",
"inProgress": "In progress",
"inProgressStatus": "IN_PROGRESS",
Expand Down Expand Up @@ -58,6 +60,7 @@
"testsOrdered": "Tests ordered",
"testType": "Test type",
"totalOrders": "Total orders",
"transition": "Transition",
"urgencyStatus": "Urgency: ",
"viewTestResults": "View test results",
"worklist": "Worklist",
Expand Down

0 comments on commit ef73f5f

Please sign in to comment.