Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d61410d
(feat): add configurable pending review workflow with conditional UI …
its-kios09 Sep 10, 2025
b07f98c
Merge branch 'main' into O3-5020
its-kios09 Sep 15, 2025
b8f05b4
(fix) Build issues
its-kios09 Sep 15, 2025
bc13edd
(fix) Fixed the Build issue
its-kios09 Sep 15, 2025
3e378dd
Merge branch 'main' into O3-5020
its-kios09 Sep 19, 2025
9b66255
(refactor) Updated the yarn.lock
its-kios09 Sep 19, 2025
1e6eff7
Merge branch 'main' into O3-5020
its-kios09 Oct 3, 2025
7ac9f90
(fix) Build issues
its-kios09 Oct 3, 2025
5c33940
fix: remove invalid tsconfigRootDir from ESLint config
its-kios09 Oct 3, 2025
17e9ba9
chore: upgrade TypeScript to v5.0 for const type parameter support
its-kios09 Oct 3, 2025
eb2cb67
fix: ensure Playwright browsers are always installed in CI
its-kios09 Oct 3, 2025
227fefe
(refactor) Updated the PR suggestions
its-kios09 Oct 16, 2025
9fcb1f3
Merge branch 'main' into O3-5020
its-kios09 Oct 16, 2025
e64d1a6
(refactor) Updated the PR suggestions
its-kios09 Oct 16, 2025
669f916
Merge branch 'O3-5020' of github.com:its-kios09/openmrs-esm-laborator…
its-kios09 Oct 16, 2025
ba7905e
Merge remote changes
its-kios09 Oct 16, 2025
a02377b
Merge branch 'main' into O3-5020
its-kios09 Oct 20, 2025
ba863a8
(refactor) fix the yarn.lock
its-kios09 Oct 20, 2025
db95f63
(refactor) fix the yarn.lock
its-kios09 Oct 20, 2025
a804286
(refactor) fix the yarn.lock
its-kios09 Oct 20, 2025
a8d465d
(refactor) fix the yarn.lock
its-kios09 Oct 20, 2025
4d04674
(refactor) fix the yarn.lock
its-kios09 Oct 20, 2025
0146797
(refactor) refactor the type
its-kios09 Oct 20, 2025
fbe9899
Merge branch 'main' into O3-5020
its-kios09 Oct 22, 2025
afdc98c
Merge branch 'main' into O3-5020
its-kios09 Oct 26, 2025
9bbf4df
(fix) Fix build issues
its-kios09 Oct 28, 2025
5bd470a
(fix) Fix build issues
its-kios09 Oct 28, 2025
c29e9eb
(fix) Fix build issues
its-kios09 Oct 28, 2025
96bfd0d
(fix) Fix build issues
its-kios09 Oct 28, 2025
8484f36
(fix) Fix build issues
its-kios09 Oct 28, 2025
df211d1
(fix) Fix build issues
its-kios09 Oct 28, 2025
e3dbda6
Merge branch 'main' into O3-5020
its-kios09 Oct 28, 2025
55c5b7d
chore: update yarn.lock
its-kios09 Oct 28, 2025
05a1f30
(refactor) Address PR suggestion
its-kios09 Oct 29, 2025
c1dea02
(refactor) Address PR suggestion
its-kios09 Oct 29, 2025
44ec442
Merge branch 'main' into O3-5020
its-kios09 Oct 29, 2025
08ccb36
(refactor) Address PR suggestion
its-kios09 Oct 29, 2025
ba2644e
Merge branch 'main' into O3-5020
its-kios09 Oct 30, 2025
d79cb97
(fix) fixed e2e yml playwright issues installation
its-kios09 Oct 30, 2025
bd3edb6
Merge branch 'main' into O3-5020
pirupius Oct 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@
"react-hooks/exhaustive-deps": "warn",
"react-hooks/rules-of-hooks": "error"
}
}
}
6 changes: 2 additions & 4 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ jobs:
key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }}

- name: 🎭 Install Playwright Browsers
run: npx playwright install chromium --with-deps
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: yarn playwright install chromium --with-deps

- name: 🚀 Setup local cache server for Turborepo
uses: felixmosh/turborepo-gh-artifacts@v3
Expand Down Expand Up @@ -100,5 +99,4 @@ jobs:
name: server-logs
path: './logs'
retention-days: 2
overwrite: true

overwrite: true
17 changes: 16 additions & 1 deletion src/components/orders-table/list-order-details.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const ListOrderDetails: React.FC<ListOrdersDetailsProps> = ({ groupedOrders }) =
)}
</StructuredListBody>
</StructuredListWrapper>
{order.fulfillerStatus === 'COMPLETED' && (
{(order.fulfillerStatus === 'COMPLETED' || order.fulfillerStatus === 'DRAFT') && (
<Accordion>
<AccordionItem
title={<span className={styles.accordionTitle}>{t('viewTestResults', 'View test results')}</span>}
Expand Down Expand Up @@ -116,6 +116,21 @@ const ListOrderDetails: React.FC<ListOrdersDetailsProps> = ({ groupedOrders }) =
/>
</div>
</>
) : order.fulfillerStatus === 'DRAFT' ? (
<>
<div className={styles.testsOrderedActions}>
<ExtensionSlot
className={styles.menuLink}
state={{ order: order }}
name="amended-ordered-actions-slot"
/>
<ExtensionSlot
className={styles.menuLink}
state={{ order: order }}
name="approved-ordered-actions-slot"
/>
</div>
</>
) : null}
</div>
</div>
Expand Down
3 changes: 1 addition & 2 deletions src/components/orders-table/orders-data-table.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ import {
usePagination,
} from '@openmrs/esm-framework';
import { useTranslation } from 'react-i18next';
import { type Order, type FulfillerStatus } from '@openmrs/esm-patient-common-lib';
import { type FlattenedOrder, type OrderAction } from '../../types';
import { type FulfillerStatus, type FlattenedOrder, type OrderAction, type Order } from '../../types';
import { useLabOrders } from '../../laboratory-resource';
import { OrdersDateRangePicker } from './orders-date-range-picker.component';
import ListOrderDetails from './list-order-details.component';
Expand Down
8 changes: 7 additions & 1 deletion src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@ export const configSchema = {
_default: '05a29f94-c0ed-11e2-94be-8c13b969e334',
_description: 'Needed if the "id" column of "labTableColumns" is used. Is the OpenMRS ID by default.',
},
enableReviewingLabResultsBeforeApproval: {
_type: Type.Boolean,
_default: true,
_description:
'Enable reviewing lab results before final approval. When enabled, lab results will be submitted for review before being approved and finalized.',
},
};

export type Config = {
laboratoryOrderTypeUuid: string;
encounterTypeUuid: string;
Expand All @@ -52,4 +57,5 @@ export type Config = {
};
labTableColumns: Array<LabTableColumnName>;
patientIdIdentifierTypeUuid: string;
enableReviewingLabResultsBeforeApproval: boolean;
};
27 changes: 27 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export const pickupLabRequestModal = getAsyncLifecycle(
options,
);

export const approvalLabResultsModal = getAsyncLifecycle(
() => import('./lab-tabs/modals/approval-lab-results-modal.component'),
options,
);

export const rejectLabRequestModal = getAsyncLifecycle(
() => import('./lab-tabs/modals/reject-lab-request-modal.component'),
options,
Expand Down Expand Up @@ -61,12 +66,24 @@ export const declinedLabRequestsTable = getAsyncLifecycle(
options,
);

// t('pending review tests', 'pending review tests')
export const pendingReviewLabRequestsTable = getAsyncLifecycle(
() => import('./lab-tabs/data-table-extensions/pending-review-lab-request-table.extension'),
options,
);

// t('Worklist', 'Worklist')
export const worklistTile = getAsyncLifecycle(
() => import('./lab-tiles/in-progress-lab-requests-tile.component'),
options,
);

// t('Pending review tests', 'Pending review tests')
export const pendingReviewListTile = getAsyncLifecycle(
() => import('./lab-tiles/pending-review-lab-results-tile.component'),
options,
);

// t("Referred tests", "Referred tests")
export const completedTile = getAsyncLifecycle(
() => import('./lab-tiles/completed-lab-requests-tile.component'),
Expand All @@ -93,6 +110,16 @@ export const rejectLabRequestAction = getAsyncLifecycle(
options,
);

export const approveLabResultsAction = getAsyncLifecycle(
() => import('./lab-tabs/actions/approve-lab-results-action.component'),
options,
);

export const amendLabResultsAction = getAsyncLifecycle(
() => import('./lab-tabs/actions/amend-lab-results-action.component'),
options,
);

export function startupApp() {
defineConfigSchema(moduleName, configSchema);
}
41 changes: 41 additions & 0 deletions src/lab-tabs/actions/amend-lab-results-action.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@carbon/react';
import { showModal } from '@openmrs/esm-framework';
import { type Order } from '@openmrs/esm-patient-common-lib';
import styles from './actions.scss';

interface AmendLabResultsActionMenuProps {
order: Order;
orders?: Order[];
}

const AmendLabResultsAction: React.FC<AmendLabResultsActionMenuProps> = ({ order, orders }) => {
const { t } = useTranslation();
const unsupportedStatuses = ['DECLINED', 'IN_PROGRESS', 'NEW'];

const handleLaunchModal = () => {
const editableOrders = orders
? orders.filter((order) => ['COMPLETED', 'ON_HOLD'].includes(order.fulfillerStatus))
: [order].filter((order) => ['COMPLETED', 'ON_HOLD'].includes(order.fulfillerStatus));

const dispose = showModal('edit-lab-results-modal', {
closeModal: () => dispose(),
orders: editableOrders,
});
};

return (
<Button
className={styles.actionButton}
disabled={unsupportedStatuses.includes(order.fulfillerStatus)}
size="sm"
kind="danger"
onClick={handleLaunchModal}
>
{t('amendLabResults', 'Amend lab results')}
</Button>
);
};

export default AmendLabResultsAction;
37 changes: 37 additions & 0 deletions src/lab-tabs/actions/approve-lab-results-action.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@carbon/react';
import { showModal } from '@openmrs/esm-framework';
import { type Order } from '@openmrs/esm-patient-common-lib';
import styles from './actions.scss';

interface ApproveLabRequestActionMenuProps {
order: Order;
}

const ApproveLabRequestAction: React.FC<ApproveLabRequestActionMenuProps> = ({ order }) => {
const { t } = useTranslation();
const unsupportedStatuses = ['COMPLETED', 'DECLINED', 'IN_PROGRESS'];

const launchModal = useCallback(() => {
const dispose = showModal('approval-lab-results-modal', {
closeModal: () => dispose(),
order,
});
}, [order]);

return (
<Button
className={styles.actionButton}
disabled={unsupportedStatuses.includes(order.fulfillerStatus)}
size="sm"
kind="primary"
key={order.uuid}
onClick={launchModal}
>
{t('approveLabResults', 'Approve lab results')}
</Button>
);
};

export default ApproveLabRequestAction;
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface PickLabRequestActionMenuProps {

const PickupLabRequestAction: React.FC<PickLabRequestActionMenuProps> = ({ order }) => {
const { t } = useTranslation();
const unsupportedStatuses = ['COMPLETED', 'DECLINED', 'IN_PROGRESS'];
const unsupportedStatuses = ['COMPLETED', 'DECLINED', 'IN_PROGRESS', 'ON_HOLD'];

const launchModal = useCallback(() => {
const dispose = showModal('pickup-lab-request-modal', {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import OrdersDataTable from '../../components/orders-table/orders-data-table.component';

const PendingReviewLabRequestsTable: React.FC = () => {
return (
<OrdersDataTable
excludeColumns={[]}
fulfillerStatus="DRAFT"
useFilter={false}
excludeCanceledAndDiscontinuedOrders={false}
actions={[]}
/>
);
};

export default PendingReviewLabRequestsTable;
87 changes: 47 additions & 40 deletions src/lab-tabs/laboratory-tabs.component.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,72 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@carbon/react';
import { type AssignedExtension, Extension, useAssignedExtensions } from '@openmrs/esm-framework';
import { type AssignedExtension, Extension, useAssignedExtensions, useConfig } from '@openmrs/esm-framework';
import { ComponentContext } from '@openmrs/esm-framework/src/internal';
import styles from './laboratory-tabs.scss';
import { type Config } from '../config-schema';

const labPanelSlot = 'lab-panels-slot';

const LaboratoryOrdersTabs: React.FC = () => {
const { t } = useTranslation();
const { enableReviewingLabResultsBeforeApproval } = useConfig<Config>();
const [selectedTab, setSelectedTab] = useState(0);
const tabExtensions = useAssignedExtensions(labPanelSlot) as AssignedExtension[];

const filteredExtensions = tabExtensions
.filter((extension) => Object.keys(extension.meta).length > 0)
.filter((extension) => {
if (extension.meta.name === 'pendingReviewPanel') {
return enableReviewingLabResultsBeforeApproval === true;
}
return true;
});

return (
<main>
<section>
<div className={styles.tabs}>
<Tabs selectedIndex={selectedTab} onChange={({ selectedIndex }) => setSelectedTab(selectedIndex)}>
<TabList style={{ paddingLeft: '1rem' }} aria-label="Laboratory tabs" contained>
{tabExtensions
.filter((extension) => Object.keys(extension.meta).length > 0)
.map((extension, index) => {
const { name, title } = extension.meta;
{filteredExtensions.map((extension, index) => {
const { name, title } = extension.meta;

if (name && title) {
return (
<Tab key={index} className={styles.tab} id={`${title || index}-tab`}>
{t(title, {
ns: extension.moduleName,
defaultValue: title,
})}
</Tab>
);
} else {
return null;
}
})}
</TabList>
<TabPanels>
{tabExtensions
.filter((extension) => Object.keys(extension.meta).length > 0)
.map((extension, index) => {
if (name && title) {
return (
<TabPanel key={`${extension.meta.title}-tab-${index}`}>
<ComponentContext.Provider
key={extension.id}
value={{
moduleName: extension.moduleName,
featureName: 'laboratory',
extension: {
extensionId: extension.id,
extensionSlotName: labPanelSlot,
extensionSlotModuleName: extension.moduleName,
},
}}
>
<Extension />
</ComponentContext.Provider>
</TabPanel>
<Tab key={index} className={styles.tab} id={`${title || index}-tab`}>
{t(title, {
ns: extension.moduleName,
defaultValue: title,
})}
</Tab>
);
})}
} else {
return null;
}
})}
</TabList>
<TabPanels>
{filteredExtensions.map((extension, index) => {
return (
<TabPanel key={`${extension.meta.title}-tab-${index}`}>
<ComponentContext.Provider
key={extension.id}
value={{
moduleName: extension.moduleName,
featureName: 'laboratory',
extension: {
extensionId: extension.id,
extensionSlotName: labPanelSlot,
extensionSlotModuleName: extension.moduleName,
},
}}
>
<Extension />
</ComponentContext.Provider>
</TabPanel>
);
})}
</TabPanels>
</Tabs>
</div>
Expand Down
Loading