Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) O3-2825: Flag discontinued medications with a tag #2037

Merged
merged 35 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
03d3de2
fix/03-2825: Medications that have been discontinued should have a D…
Sep 26, 2024
9f69d69
Merge branch 'main' of https://github.com/openmrs/openmrs-esm-patient…
Sep 26, 2024
5ffaf90
fix build error
Sep 27, 2024
9487c7f
DISCONTINUED' should be a grey Carbon Tag
Sep 30, 2024
3eb9c80
Add margin on Discontinued Label
Sep 30, 2024
eb76988
Code refactor
Sep 30, 2024
e0a51a9
Remove the black borders on Tag
Sep 30, 2024
ab98f8b
Flag discontinued requests with a tooltip
Oct 1, 2024
754f57b
Discontinued date appears in the tooltip
Oct 1, 2024
9ee1057
Display Discontinued date in the tooltip
Oct 1, 2024
023d267
Add the Discontinued Date Label Inside the Tooltip
Oct 2, 2024
e9ee19d
Only discontinued medication to display the tag
Oct 7, 2024
467c87c
Active medication to be displayed on all the Active tables
Oct 8, 2024
2feca97
fix build error
Oct 9, 2024
0c59911
Fix Active medications conditions
Oct 9, 2024
39aded7
Merge branch 'main' of https://github.com/openmrs/openmrs-esm-patient…
Oct 9, 2024
dadbc44
Merge branch 'main' of https://github.com/openmrs/openmrs-esm-patient…
Oct 23, 2024
7229ede
Add hooks separate active patient orders and past patient orders
Oct 24, 2024
6ddfcdd
Remove filtering logic in the MedicationsSummary component
Oct 24, 2024
9b0e271
Ensure past medication is displayed
Oct 24, 2024
e61f67b
fix exclude discontinue orders parameter
Oct 24, 2024
53159c7
Remove redundant conditions
Oct 28, 2024
7398ec6
Code refactor
Oct 28, 2024
e35e912
Merge branch 'main' of https://github.com/openmrs/openmrs-esm-patient…
Oct 28, 2024
8d54ea2
Revert changes
Oct 29, 2024
9be84f9
Refactor hooks
Oct 29, 2024
d06ca5e
Code refactor
Oct 29, 2024
d5ad9c7
Code refactor
Oct 29, 2024
db0638d
Updates the hook from usePatientOrders to useActivePatientOrders
Oct 29, 2024
b430dbb
Display Discontinued tag
Oct 29, 2024
ad14f95
Display Discontinued tag on past medication
Oct 30, 2024
41d835b
Code refactor
Oct 31, 2024
d51ef14
fix the failing test
Oct 31, 2024
e242b33
Fix the tooltip position
Oct 31, 2024
b53eede
Merge branch 'main' of https://github.com/openmrs/openmrs-esm-patient…
Oct 31, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ActiveMedications: React.FC<ActiveMedicationsProps> = ({ patient }) => {
const displayText = t('activeMedicationsDisplayText', 'Active medications');
const headerTitle = t('activeMedicationsHeaderTitle', 'active medications');

const { data: activePatientOrders, error, isLoading, isValidating } = usePatientOrders(patient?.id, 'ACTIVE');
const { data: activePatientOrders, error, isLoading, isValidating } = usePatientOrders(patient?.id);
denniskigen marked this conversation as resolved.
Show resolved Hide resolved

const launchAddDrugWorkspace = useLaunchWorkspaceRequiringVisit('add-drug-order');

Expand All @@ -35,6 +35,7 @@ const ActiveMedications: React.FC<ActiveMedicationsProps> = ({ patient }) => {
/>
);
}

return <EmptyState displayText={displayText} headerTitle={headerTitle} launchForm={() => launchAddDrugWorkspace()} />;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ describe('ActiveMedications', () => {

const expectedTableRows = [
/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/,
/14-Aug-2023 Admin User Acetaminophen 325 mg — 325mg — tablet DOSE 2 tablet — oral — twice daily — indefinite duration INDICATION No good — DISCONTINUED DATE 14-Aug-2023/,
/14-Aug-2023 Admin User Acetaminophen 325 mg — 325mg — tablet DOSE 2 tablets — oral — twice daily — indefinite duration INDICATION Not specified — DISCONTINUED Tag Tooltip DATE 14-Aug-2023 DISCONTINUED/,
denniskigen marked this conversation as resolved.
Show resolved Hide resolved
/14-Aug-2023 Admin User Sulfacetamide 0.1 — 10% DOSE 1 application — for {{duration}} weeks — REFILLS 1 — apply it INDICATION Pain — QUANTITY 8 Application/,
/14-Aug-2023 Admin User Aspirin 162.5mg — 162.5mg — tablet DOSE 1 tablet — oral — once daily — for {{duration}} days INDICATION Heart — QUANTITY 30 Tablet/,
];

expectedTableRows.map((row) =>
expectedTableRows.forEach((row) =>
expect(within(table).getByRole('row', { name: new RegExp(row, 'i') })).toBeInTheDocument(),
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const DrugSearchResultItem: React.FC<DrugSearchResultItemProps> = ({ drug, openO
const isTablet = useLayoutType() === 'tablet';
const { orders, setOrders } = useOrderBasket<DrugOrderBasketItem>('medications', prepMedicationOrderPostData);
const patient = usePatient();
const { data: activeOrders, isLoading: isLoadingActiveOrders } = usePatientOrders(patient.patientUuid, 'ACTIVE');
const { data: activeOrders, isLoading: isLoadingActiveOrders } = usePatientOrders(patient.patientUuid);
const drugAlreadyInBasket = useMemo(
() => orders?.some((order) => ordersEqual(order, getTemplateOrderBasketItem(drug))),
[orders, drug],
Expand Down
105 changes: 86 additions & 19 deletions packages/esm-patient-medications-app/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
import { useCallback, useMemo } from 'react';
import useSWR, { mutate } from 'swr';
import useSWRImmutable from 'swr/immutable';
import { openmrsFetch, restBaseUrl, useConfig, type FetchResponse } from '@openmrs/esm-framework';
import { type ConfigObject } from '../config-schema';
import { type FetchResponse, openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework';
import { type OrderPost, type PatientOrderFetchResponse } from '@openmrs/esm-patient-common-lib';
import { type DrugOrderBasketItem } from '../types';
import useSWRImmutable from 'swr/immutable';

export const careSettingUuid = '6f0c9a92-6f24-11e3-af88-005056821db0';

const customRepresentation =
'custom:(uuid,dosingType,orderNumber,accessionNumber,' +
'patient:ref,action,careSetting:ref,previousOrder:ref,dateActivated,scheduledDate,dateStopped,autoExpireDate,' +
'orderType:ref,encounter:ref,orderer:(uuid,display,person:(display)),orderReason,orderReasonNonCoded,orderType,urgency,instructions,' +
'commentToFulfiller,drug:(uuid,display,strength,dosageForm:(display,uuid),concept),dose,doseUnits:ref,' +
'frequency:ref,asNeeded,asNeededCondition,quantity,quantityUnits:ref,numRefills,dosingInstructions,' +
'duration,durationUnits:ref,route:ref,brandName,dispenseAsWritten)';

/**
denniskigen marked this conversation as resolved.
Show resolved Hide resolved
* Sorts orders by date activated in descending order.
*
* @param orders The orders to sort.
* @returns The sorted orders.
*/
function sortOrdersByDateActivated(orders: any[]) {
return orders?.sort(
(order1, order2) => new Date(order2.dateActivated).getTime() - new Date(order1.dateActivated).getTime(),
);
}

/**
* SWR-based data fetcher for patient orders.
*
* @param patientUuid The UUID of the patient whose orders should be fetched.
* @param status Allows fetching either all orders or only active orders.
*/
export function usePatientOrders(patientUuid: string, status: 'ACTIVE' | 'any') {
export function usePatientOrders(patientUuid: string) {
const { drugOrderTypeUUID } = useConfig() as ConfigObject;
denniskigen marked this conversation as resolved.
Show resolved Hide resolved
const customRepresentation =
'custom:(uuid,dosingType,orderNumber,accessionNumber,' +
'patient:ref,action,careSetting:ref,previousOrder:ref,dateActivated,scheduledDate,dateStopped,autoExpireDate,' +
'orderType:ref,encounter:ref,orderer:(uuid,display,person:(display)),orderReason,orderReasonNonCoded,orderType,urgency,instructions,' +
'commentToFulfiller,drug:(uuid,display,strength,dosageForm:(display,uuid),concept),dose,doseUnits:ref,' +
'frequency:ref,asNeeded,asNeededCondition,quantity,quantityUnits:ref,numRefills,dosingInstructions,' +
'duration,durationUnits:ref,route:ref,brandName,dispenseAsWritten)';
const ordersUrl = `${restBaseUrl}/order?patient=${patientUuid}&careSetting=${careSettingUuid}&status=${status}&orderType=${drugOrderTypeUUID}&v=${customRepresentation}`;

const ordersUrl = `${restBaseUrl}/order?patient=${patientUuid}&careSetting=${careSettingUuid}&orderTypes=${drugOrderTypeUUID}&v=${customRepresentation}&excludeDiscontinueOrders=false`;
denniskigen marked this conversation as resolved.
Show resolved Hide resolved

const { data, error, isLoading, isValidating } = useSWR<FetchResponse<PatientOrderFetchResponse>, Error>(
patientUuid ? ordersUrl : null,
Expand All @@ -35,22 +48,76 @@ export function usePatientOrders(patientUuid: string, status: 'ACTIVE' | 'any')
[patientUuid],
);

const drugOrders = useMemo(
const drugOrders = useMemo(() => (data?.data?.results ? sortOrdersByDateActivated(data.data.results) : null), [data]);
denniskigen marked this conversation as resolved.
Show resolved Hide resolved

return {
data: data ? drugOrders : null,
error,
isLoading,
isValidating,
mutate: mutateOrders,
};
}

/**
* Hook to get active patient orders.
*
* @param patientUuid The UUID of the patient whose active orders should be fetched.
*/
export function useActivePatientOrders(patientUuid: string) {
const { drugOrderTypeUUID } = useConfig() as ConfigObject;
denniskigen marked this conversation as resolved.
Show resolved Hide resolved
const ordersUrl = useMemo(
() =>
data?.data?.results
? data.data.results
.filter((order) => order.orderType.display === 'Drug Order')
?.sort((order1, order2) => (order2.dateActivated > order1.dateActivated ? 1 : -1))
patientUuid
? `${restBaseUrl}/order?patient=${patientUuid}&careSetting=${careSettingUuid}&orderTypes=${drugOrderTypeUUID}&excludeCanceledAndExpired=true&v=${customRepresentation}`
: null,
[patientUuid, drugOrderTypeUUID],
);
const { data, error, isLoading, isValidating } = useSWR<FetchResponse<PatientOrderFetchResponse>, Error>(
ordersUrl,
openmrsFetch,
);

const activeOrders = useMemo(
() => (data?.data?.results ? sortOrdersByDateActivated(data.data.results) : null),
denniskigen marked this conversation as resolved.
Show resolved Hide resolved
[data],
);

return {
data: data ? drugOrders : null,
data: activeOrders,
error,
isLoading,
isValidating,
mutate: mutateOrders,
mutate,
};
}

/**
* Hook to get past patient orders.
*
* @param patientUuid The UUID of the patient whose past orders should be fetched.
*/
export function usePastPatientOrders(patientUuid: string) {
const { data: allOrders, error, isLoading, isValidating, mutate } = usePatientOrders(patientUuid);
const { data: activeOrders } = useActivePatientOrders(patientUuid);

const pastOrders = useMemo(() => {
if (!allOrders || !activeOrders) {
return [];
}

const filteredDrugOrders = allOrders.filter(
(order) => !activeOrders.some((activeOrder) => activeOrder.uuid === order.uuid),
);
return sortOrdersByDateActivated(filteredDrugOrders);
}, [allOrders, activeOrders]);

return {
data: pastOrders,
error,
isLoading,
isValidating,
mutate,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
TableHead,
TableHeader,
TableRow,
Tag,
Tooltip,
} from '@carbon/react';
import {
CardHeader,
Expand Down Expand Up @@ -102,6 +104,21 @@ const MedicationsDetailsTable: React.FC<ActiveMedicationsProps> = ({
<strong>{capitalize(medication.drug?.display)}</strong>{' '}
{medication.drug?.strength && <>&mdash; {medication.drug?.strength.toLowerCase()}</>}{' '}
{medication.drug?.dosageForm?.display && <>&mdash; {medication.drug.dosageForm.display.toLowerCase()}</>}
{(medication.dateStopped || medication.autoExpireDate) && medication.action === 'DISCONTINUE' && (
<Tooltip
align="top"
label={
<>
{t('discontinuedDate', 'Discontinued date').toUpperCase()} &mdash;
denniskigen marked this conversation as resolved.
Show resolved Hide resolved
{formatDate(new Date(medication.dateStopped || medication.autoExpireDate))}
</>
}
>
<Tag type="gray" className={styles.tag}>
{t('discontinued', 'Discontinued')}
</Tag>
</Tooltip>
)}
</p>
<p className={styles.bodyLong01}>
<span className={styles.label01}>{t('dose', 'Dose').toUpperCase()}</span>{' '}
Expand All @@ -128,26 +145,18 @@ const MedicationsDetailsTable: React.FC<ActiveMedicationsProps> = ({
</p>
</div>
<p className={styles.bodyLong01}>
{medication.orderReasonNonCoded ? (
{medication.orderReasonNonCoded && (
<span>
<span className={styles.label01}>{t('indication', 'Indication').toUpperCase()}</span>{' '}
{medication.orderReasonNonCoded}
</span>
) : null}
{medication.quantity ? (
)}
{medication.quantity && (
<span>
<span className={styles.label01}> &mdash; {t('quantity', 'Quantity').toUpperCase()}</span>{' '}
{medication.quantity} {medication?.quantityUnits?.display}
</span>
) : null}
{medication.dateStopped ? (
<span>
<span className={styles.label01}>
&mdash; {t('discontinuedDate', 'Discontinued date').toUpperCase()}
</span>
denniskigen marked this conversation as resolved.
Show resolved Hide resolved
{formatDate(new Date(medication.dateStopped))}
</span>
) : null}
)}
</p>
</div>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,10 @@
border-bottom: none !important;
}
}

.tag {
margin: 0 !important;
margin-left: 0.5rem !important;
denniskigen marked this conversation as resolved.
Show resolved Hide resolved
border: none;
text-transform: uppercase;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React, { useMemo } from 'react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { DataTableSkeleton } from '@carbon/react';
import { parseDate } from '@openmrs/esm-framework';
import { EmptyState, ErrorState, useLaunchWorkspaceRequiringVisit, type Order } from '@openmrs/esm-patient-common-lib';
import { usePatientOrders } from '../api';
import { useActivePatientOrders, usePastPatientOrders } from '../api';
import MedicationsDetailsTable from '../components/medications-details-table.component';
import { type AddDrugOrderWorkspaceAdditionalProps } from '../add-drug-order/add-drug-order.workspace';

Expand All @@ -17,48 +16,34 @@ export default function MedicationsSummary({ patient }: MedicationsSummaryProps)
useLaunchWorkspaceRequiringVisit<AddDrugOrderWorkspaceAdditionalProps>('add-drug-order');

const {
data: allOrders,
error: error,
isLoading: isLoading,
isValidating: isValidating,
} = usePatientOrders(patient?.id, 'any');
data: activeOrders,
error: activeOrdersError,
isLoading: isActiveLoading,
isValidating: isActiveValidating,
} = useActivePatientOrders(patient?.id);
denniskigen marked this conversation as resolved.
Show resolved Hide resolved

const [pastOrders, activeOrders] = useMemo(() => {
const currentDate = new Date();
const pastOrders: Array<Order> = [];
const activeOrders: Array<Order> = [];

if (allOrders) {
for (let i = 0; i < allOrders.length; i++) {
const order = allOrders[i];
if (order.autoExpireDate && parseDate(order.autoExpireDate) < currentDate) {
pastOrders.push(order);
} else if (order.dateStopped && parseDate(order.dateStopped) < currentDate) {
pastOrders.push(order);
} else {
activeOrders.push(order);
}
}
}

return [pastOrders, activeOrders];
}, [allOrders]);
const {
data: pastOrders,
error: pastOrdersError,
isLoading: isPastLoading,
isValidating: isPastValidating,
} = usePastPatientOrders(patient?.id);
denniskigen marked this conversation as resolved.
Show resolved Hide resolved

return (
<div>
<div style={{ marginBottom: '1.5rem' }}>
{(() => {
const displayText = t('activeMedicationsDisplayText', 'Active medications');
const headerTitle = t('activeMedicationsHeaderTitle', 'active medications');
const headerTitle = t('activeMedicationsHeaderTitle', 'Active Medications');

denniskigen marked this conversation as resolved.
Show resolved Hide resolved
if (isLoading) return <DataTableSkeleton role="progressbar" />;
if (isActiveLoading) return <DataTableSkeleton role="progressbar" />;

if (error) return <ErrorState error={error} headerTitle={headerTitle} />;
if (activeOrdersError) return <ErrorState error={activeOrdersError} headerTitle={headerTitle} />;

if (activeOrders?.length) {
return (
<MedicationsDetailsTable
isValidating={isValidating}
isValidating={isActiveValidating}
title={t('activeMedicationsTableTitle', 'Active Medications')}
medications={activeOrders}
showDiscontinueButton={true}
Expand All @@ -75,16 +60,16 @@ export default function MedicationsSummary({ patient }: MedicationsSummaryProps)
<div>
{(() => {
const displayText = t('pastMedicationsDisplayText', 'Past medications');
const headerTitle = t('pastMedicationsHeaderTitle', 'past medications');
const headerTitle = t('pastMedicationsHeaderTitle', 'Past Medications');

denniskigen marked this conversation as resolved.
Show resolved Hide resolved
if (isLoading) return <DataTableSkeleton role="progressbar" />;
if (isPastLoading) return <DataTableSkeleton role="progressbar" />;

if (error) return <ErrorState error={error} headerTitle={headerTitle} />;
if (pastOrdersError) return <ErrorState error={pastOrdersError} headerTitle={headerTitle} />;

if (pastOrders?.length) {
return (
<MedicationsDetailsTable
isValidating={isValidating}
isValidating={isPastValidating}
title={t('pastMedicationsTableTitle', 'Past Medications')}
medications={pastOrders}
showDiscontinueButton={false}
Expand Down
2 changes: 2 additions & 0 deletions packages/esm-patient-medications-app/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"directlyAddToBasket": "Add to basket",
"discard": "Discard",
"discontinue": "Discontinue",
"discontinued": "Discontinued",
"discontinued_Caps": "DISCONTINUED",
denniskigen marked this conversation as resolved.
Show resolved Hide resolved
"discontinuedDate": "Discontinued date",
"dispensingInformation": "3. Dispensing instructions",
"dosageInstructions": "1. Dosage instructions",
Expand Down
Loading