Skip to content

Commit 7ca6dfe

Browse files
makombedenniskigen
andauthored
(feat) O3-5151: Add past medication widget extension to patient summary (#2810)
* (feat)O3-5151: Add past medication widget extension to patient summary * code reviews * Code review fix --------- Co-authored-by: Dennis Kigen <[email protected]>
1 parent 9ab605c commit 7ca6dfe

File tree

5 files changed

+148
-0
lines changed

5 files changed

+148
-0
lines changed

packages/esm-patient-medications-app/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { configSchema } from './config-schema';
44
import { dashboardMeta, moduleName } from './dashboard.meta';
55
import medicationsSummaryComponent from './medications-summary/medications-summary.component';
66
import activeMedicationsComponent from './active-medications/active-medications.component';
7+
import pastMedicationsComponent from './past-medications/past-medications.component';
78

89
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
910

@@ -20,6 +21,8 @@ export const medicationsSummary = getSyncLifecycle(medicationsSummaryComponent,
2021

2122
export const activeMedications = getSyncLifecycle(activeMedicationsComponent, options);
2223

24+
export const pastMedications = getSyncLifecycle(pastMedicationsComponent, options);
25+
2326
export const drugOrderPanel = getAsyncLifecycle(
2427
() => import('./drug-order-basket-panel/drug-order-basket-panel.extension'),
2528
options,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { DataTableSkeleton } from '@carbon/react';
4+
import { EmptyState, ErrorState } from '@openmrs/esm-patient-common-lib';
5+
import MedicationsDetailsTable from '../components/medications-details-table.component';
6+
import { usePastPatientOrders } from '../api/api';
7+
8+
interface PastMedicationsProps {
9+
patient: fhir.Patient;
10+
}
11+
12+
const PastMedications: React.FC<PastMedicationsProps> = ({ patient }) => {
13+
const { t } = useTranslation();
14+
const headerTitle = t('pastMedicationsHeaderTitle', 'Past medications');
15+
const displayText = t('pastMedicationsDisplayText', 'past medications');
16+
17+
const { data: pastPatientOrders, error, isLoading, isValidating } = usePastPatientOrders(patient?.id);
18+
19+
if (isLoading) {
20+
return <DataTableSkeleton role="progressbar" />;
21+
}
22+
23+
if (error) {
24+
return <ErrorState error={error} headerTitle={headerTitle} />;
25+
}
26+
27+
if (pastPatientOrders?.length) {
28+
return (
29+
<MedicationsDetailsTable
30+
isValidating={isValidating}
31+
title={t('pastMedicationsTableTitle', 'Past Medications')}
32+
medications={pastPatientOrders}
33+
showDiscontinueButton={false}
34+
showModifyButton={false}
35+
showReorderButton={true}
36+
patient={patient}
37+
/>
38+
);
39+
}
40+
41+
return <EmptyState displayText={displayText} headerTitle={headerTitle} />;
42+
};
43+
44+
export default PastMedications;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@use '@carbon/layout';
2+
@use '@carbon/type';
3+
4+
.productiveHeading03 {
5+
@include type.type-style('heading-03');
6+
margin-bottom: layout.$spacing-05;
7+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React from 'react';
2+
import { openmrsFetch, useSession } from '@openmrs/esm-framework';
3+
import { screen, within } from '@testing-library/react';
4+
import { mockPatientDrugOrdersApiData, mockSessionDataResponse } from '__mocks__';
5+
import { mockPatient, renderWithSwr, waitForLoadingToFinish } from 'tools';
6+
import PastMedications from './past-medications.component';
7+
8+
const mockUseSession = jest.mocked(useSession);
9+
const mockOpenmrsFetch = openmrsFetch as jest.Mock;
10+
11+
mockUseSession.mockReturnValue(mockSessionDataResponse.data);
12+
13+
describe('PastMedications', () => {
14+
test('renders an empty state view when there are no past medications to display', async () => {
15+
mockOpenmrsFetch.mockReturnValueOnce({ data: { results: [] } });
16+
mockOpenmrsFetch.mockReturnValueOnce({ data: { results: [] } });
17+
18+
renderWithSwr(<PastMedications patient={mockPatient} />);
19+
20+
await waitForLoadingToFinish();
21+
22+
expect(screen.getByRole('heading', { name: /past medications/i })).toBeInTheDocument();
23+
expect(screen.getByTitle(/empty data illustration/i)).toBeInTheDocument();
24+
expect(screen.getByText(/There are no past medications to display for this patient/i)).toBeInTheDocument();
25+
});
26+
27+
test('renders an error state view if there is a problem fetching medications data', async () => {
28+
const error = {
29+
message: 'You are not logged in',
30+
response: {
31+
status: 401,
32+
statusText: 'Unauthorized',
33+
},
34+
};
35+
36+
mockOpenmrsFetch.mockRejectedValueOnce(error);
37+
38+
renderWithSwr(<PastMedications patient={mockPatient} />);
39+
40+
await waitForLoadingToFinish();
41+
42+
expect(screen.queryByRole('table')).not.toBeInTheDocument();
43+
expect(screen.getByRole('heading', { name: /past medications/i })).toBeInTheDocument();
44+
expect(screen.getByText(/Error 401: Unauthorized/i)).toBeInTheDocument();
45+
expect(screen.getByText(/Sorry, there was a problem displaying this information/i)).toBeInTheDocument();
46+
});
47+
48+
test('renders a tabular overview of the past medications recorded for a patient', async () => {
49+
mockOpenmrsFetch
50+
.mockReturnValueOnce({
51+
data: { results: mockPatientDrugOrdersApiData },
52+
})
53+
.mockReturnValueOnce({
54+
data: { results: [] }, // simulate no active orders so all become "past"
55+
});
56+
57+
renderWithSwr(<PastMedications patient={mockPatient} />);
58+
59+
await waitForLoadingToFinish();
60+
61+
const headingElements = screen.getAllByText(/Past Medications/i);
62+
headingElements.forEach((headingElement) => {
63+
expect(headingElement).toBeInTheDocument();
64+
});
65+
66+
const table = screen.getByRole('table');
67+
expect(table).toBeInTheDocument();
68+
69+
const expectedColumnHeaders = [/start date/i, /details/i];
70+
expectedColumnHeaders.forEach((header) => {
71+
expect(screen.getByRole('columnheader', { name: header })).toBeInTheDocument();
72+
});
73+
74+
const expectedTableRows = [
75+
/14-Aug-2023 Admin User Acetaminophen 325 mg 325mg tablet DOSE 2 tablet oral twice daily indefinite duration take it sometimes INDICATION Bad boo-boo/i,
76+
/14-Aug-2023 Admin User Acetaminophen 325 mg 325mg tablet 14-Aug-2023 DOSE 2 tablet oral twice daily indefinite duration INDICATION No good 0/i,
77+
/14-Aug-2023 Admin User Sulfacetamide 0.1 10% DOSE 1 application for 1 weeks REFILLS 1 apply it INDICATION Pain QUANTITY 8 Application/i,
78+
/14-Aug-2023 Admin User Aspirin 162.5mg 162.5mg tablet DOSE 1 tablet oral once daily for 30 days INDICATION Heart QUANTITY 30 Tablet/i,
79+
];
80+
81+
expectedTableRows.forEach((row) => {
82+
expect(within(table).getByRole('row', { name: row })).toBeInTheDocument();
83+
});
84+
});
85+
});

packages/esm-patient-medications-app/src/routes.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@
3939
},
4040
"order": 4
4141
},
42+
{
43+
"name": "past-medications-widget",
44+
"component": "pastMedications",
45+
"slot": "patient-chart-summary-dashboard-slot",
46+
"meta": {
47+
"fullWidth": false
48+
},
49+
"order": 5
50+
},
4251
{
4352
"name": "medications-summary-dashboard",
4453
"component": "medicationsDashboardLink",

0 commit comments

Comments
 (0)