Skip to content

Commit c9bed81

Browse files
Merge branch 'openmrs:main' into O3-4274-visit-form-use-use-ref-instead-of-use-state-for-use-visit-form-callbacks
2 parents 5827260 + 3f3da57 commit c9bed81

File tree

7 files changed

+310
-151
lines changed

7 files changed

+310
-151
lines changed

packages/esm-patient-medications-app/src/add-drug-order/drug-order-form.component.tsx

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -131,17 +131,19 @@ export function DrugOrderForm({
131131
allowSelectingDrug,
132132
}: DrugOrderFormProps) {
133133
const { t } = useTranslation();
134-
const config = useConfig<ConfigObject>();
134+
const { daysDurationUnit, prescriberProviderRoles } = useConfig<ConfigObject>();
135135
const isTablet = useLayoutType() === 'tablet';
136136
const { orderConfigObject, error: errorFetchingOrderConfig } = useOrderConfig();
137137

138138
const isProviderManagementModuleInstalled = useFeatureFlag('providermanagement-module');
139139
const allowAndSupportSelectingPrescribingClinician =
140140
isProviderManagementModuleInstalled && allowSelectingPrescribingClinician;
141141

142-
const { data: providers, isLoading: isLoadingProviders } = useProviders(
143-
allowAndSupportSelectingPrescribingClinician ? config.prescriberProviderRoles : null,
144-
);
142+
const {
143+
data: providers,
144+
isLoading: isLoadingProviders,
145+
error: errorLoadingProviders,
146+
} = useProviders(allowAndSupportSelectingPrescribingClinician ? prescriberProviderRoles : null);
145147
const [isSaving, setIsSaving] = useState(false);
146148

147149
const { currentProvider } = useSession();
@@ -234,11 +236,11 @@ export function DrugOrderForm({
234236
() =>
235237
orderConfigObject?.durationUnits ?? [
236238
{
237-
valueCoded: config?.daysDurationUnit?.uuid,
238-
value: config?.daysDurationUnit?.display,
239+
valueCoded: daysDurationUnit?.uuid,
240+
value: daysDurationUnit?.display,
239241
},
240242
],
241-
[orderConfigObject, config?.daysDurationUnit],
243+
[orderConfigObject, daysDurationUnit],
242244
);
243245

244246
const orderFrequencies: Array<MedicationFrequency> = useMemo(() => {
@@ -329,32 +331,41 @@ export function DrugOrderForm({
329331
/>
330332
</InputWrapper>
331333
)}
332-
{allowAndSupportSelectingPrescribingClinician && !isLoadingProviders && providers.length === 0 && (
333-
<InlineNotification
334-
kind="warning"
335-
lowContrast
336-
className={styles.inlineNotification}
337-
title={t('noCliniciansFound', 'No clinicians found')}
338-
subtitle={t(
339-
'noCliniciansFoundDescription',
340-
'Cannot select prescribing clinician because no clinicians with appropriate roles are found. Check configuration.',
341-
)}
342-
/>
343-
)}
344-
{allowAndSupportSelectingPrescribingClinician && !isLoadingProviders && (
345-
<ControlledFieldInput
346-
control={control}
347-
name="orderer"
348-
type="comboBox"
349-
getValues={getValues}
350-
id="orderer"
351-
shouldFilterItem={filterItemsByProviderName}
352-
placeholder={t('prescribingClinician', 'Prescribing Clinician')}
353-
titleText={t('prescribingClinician', 'Prescribing Clinician')}
354-
items={providers}
355-
itemToString={(item: Provider) => item?.person?.display}
356-
/>
357-
)}
334+
{allowAndSupportSelectingPrescribingClinician &&
335+
!isLoadingProviders &&
336+
(providers?.length > 0 ? (
337+
<ControlledFieldInput
338+
control={control}
339+
name="orderer"
340+
type="comboBox"
341+
getValues={getValues}
342+
id="orderer"
343+
shouldFilterItem={filterItemsByProviderName}
344+
placeholder={t('prescribingClinician', 'Prescribing Clinician')}
345+
titleText={t('prescribingClinician', 'Prescribing Clinician')}
346+
items={providers}
347+
itemToString={(item: Provider) => item?.person?.display}
348+
/>
349+
) : errorLoadingProviders ? (
350+
<InlineNotification
351+
kind="warning"
352+
lowContrast
353+
className={styles.inlineNotification}
354+
title={t('errorLoadingProviders', 'Error loading clinicians list')}
355+
subtitle={t('tryReopeningTheForm', 'Please try launching the form again')}
356+
/>
357+
) : (
358+
<InlineNotification
359+
kind="warning"
360+
lowContrast
361+
className={styles.inlineNotification}
362+
title={t('noCliniciansFound', 'No clinicians found')}
363+
subtitle={t(
364+
'noCliniciansFoundDescription',
365+
'Cannot select prescribing clinician because no clinicians with appropriate roles are found. Check configuration.',
366+
)}
367+
/>
368+
))}
358369
</Stack>
359370
</section>
360371
)}

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)