Skip to content

Commit

Permalink
Merge branch 'main' into chore/update-transifex
Browse files Browse the repository at this point in the history
  • Loading branch information
denniskigen authored Dec 3, 2024
2 parents 79c70ae + 9748aab commit c4dfa95
Show file tree
Hide file tree
Showing 26 changed files with 1,068 additions and 99 deletions.
2 changes: 2 additions & 0 deletions __mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export * from './location.mock';
export * from './medication.mock';
export * from './mockBasePanel.mock';
export * from './mockDeceasedPatient';
export * from './order-stock-data.mock';
export * from './order-price-data.mock';
export * from './patient-flags.mock';
export * from './programs.mock';
export * from './relationships.mock';
Expand Down
40 changes: 40 additions & 0 deletions __mocks__/order-price-data.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { OrderPriceData } from '@openmrs/esm-patient-orders-app/src/types/order';

export const mockOrderPriceData: OrderPriceData = {
resourceType: 'Bundle',
id: 'test-id',
meta: {
lastUpdated: '2024-01-01T00:00:00Z',
},
type: 'searchset',
link: [
{
relation: 'self',
url: 'test-url',
},
],
entry: [
{
resource: {
resourceType: 'ChargeItemDefinition',
id: 'test-resource-id',
name: 'Test Item',
status: 'active',
date: '2024-01-01',
propertyGroup: [
{
priceComponent: [
{
type: 'base',
amount: {
value: 99.99,
currency: 'USD',
},
},
],
},
],
},
},
],
};
46 changes: 46 additions & 0 deletions __mocks__/order-stock-data.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export const mockOrderStockData = {
resourceType: 'Bundle',
id: 'test-id',
meta: {
lastUpdated: '2024-01-01T00:00:00Z',
},
type: 'searchset',
link: [
{
relation: 'self',
url: 'test-url',
},
],
entry: [
{
resource: {
resourceType: 'InventoryItem',
id: 'test-resource-id',
meta: {
profile: ['test-profile'],
},
status: 'active',
code: [
{
coding: [
{
system: 'test-system',
code: 'test-code',
display: 'Test Item',
},
],
},
],
name: [
{
name: 'Test Item',
},
],
netContent: {
value: 10,
unit: 'units',
},
},
},
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
flex-direction: column;
justify-content: space-between;
border: 1px solid $grey-2;

&:not(:last-of-type) {
margin-bottom: layout.$spacing-03;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { type ComponentProps, useRef } from 'react';
import React, { useMemo, useRef } from 'react';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Button, ClickableTile, Tile } from '@carbon/react';
import { TrashCanIcon, useLayoutType, WarningIcon } from '@openmrs/esm-framework';
import { ClickableTile, IconButton, Tile } from '@carbon/react';
import { ExtensionSlot, TrashCanIcon, useLayoutType, WarningIcon } from '@openmrs/esm-framework';
import { type DrugOrderBasketItem } from '../types';
import styles from './order-basket-item-tile.scss';

Expand All @@ -24,70 +24,84 @@ export default function OrderBasketItemTile({ orderBasketItem, onItemClick, onRe
// Hence, we manually prevent the handleClick callback from being invoked as soon as the button is pressed once.
const shouldOnClickBeCalled = useRef(true);

const additionalInfoSlotState = useMemo(
() => ({
orderItemUuid: orderBasketItem.drug.uuid,
}),
[orderBasketItem],
);

const tileContent = (
<div className={styles.orderBasketItemTile}>
<div className={styles.clipTextWithEllipsis}>
<OrderActionLabel orderBasketItem={orderBasketItem} />
{orderBasketItem.isFreeTextDosage ? (
<div>
<span className={styles.drugName}>{orderBasketItem.drug?.display}</span>
{orderBasketItem.freeTextDosage && (
<span className={styles.dosageInfo}> &mdash; {orderBasketItem.freeTextDosage}</span>
)}
</div>
) : (
<div>
<span className={styles.drugName}>{orderBasketItem.drug?.display}</span>
<div>
<div className={styles.orderBasketItemTile}>
<div className={styles.clipTextWithEllipsis}>
<OrderActionLabel orderBasketItem={orderBasketItem} />
{orderBasketItem.isFreeTextDosage ? (
<div>
<span className={styles.drugName}>{orderBasketItem.drug?.display}</span>
{orderBasketItem.freeTextDosage && (
<span className={styles.dosageInfo}> &mdash; {orderBasketItem.freeTextDosage}</span>
)}
</div>
) : (
<div>
<span className={styles.drugName}>{orderBasketItem.drug?.display}</span>
<span className={styles.dosageInfo}>
{' '}
{orderBasketItem.drug?.strength && <>&mdash; {orderBasketItem.drug?.strength}</>}{' '}
{orderBasketItem.drug?.dosageForm?.display && <>&mdash; {orderBasketItem.drug.dosageForm?.display}</>}
</span>
</div>
)}
<span className={styles.label01}>
<span className={styles.doseCaption}>{t('dose', 'Dose').toUpperCase()}</span>{' '}
<span className={styles.dosageLabel}>
{orderBasketItem.dosage} {orderBasketItem.unit?.value}
</span>{' '}
<span className={styles.dosageInfo}>
{' '}
{orderBasketItem.drug?.strength && <>&mdash; {orderBasketItem.drug?.strength}</>}{' '}
{orderBasketItem.drug?.dosageForm?.display && <>&mdash; {orderBasketItem.drug.dosageForm?.display}</>}
&mdash; {orderBasketItem.route?.value ? <>{orderBasketItem.route.value} &mdash; </> : null}
{orderBasketItem.frequency?.value ? <>{orderBasketItem.frequency.value} &mdash; </> : null}
{t('refills', 'Refills').toUpperCase()} {orderBasketItem.numRefills}{' '}
{t('quantity', 'Quantity').toUpperCase()}{' '}
{`${orderBasketItem.pillsDispensed} ${orderBasketItem.quantityUnits?.value?.toLowerCase() ?? ''}`}
{orderBasketItem.patientInstructions && <>&mdash; {orderBasketItem.patientInstructions}</>}
</span>
</div>
)}
<span className={styles.label01}>
<span className={styles.doseCaption}>{t('dose', 'Dose').toUpperCase()}</span>{' '}
<span className={styles.dosageLabel}>
{orderBasketItem.dosage} {orderBasketItem.unit?.value}
</span>{' '}
<span className={styles.dosageInfo}>
&mdash; {orderBasketItem.route?.value ? <>{orderBasketItem.route.value} &mdash; </> : null}
{orderBasketItem.frequency?.value ? <>{orderBasketItem.frequency.value} &mdash; </> : null}
{t('refills', 'Refills').toUpperCase()} {orderBasketItem.numRefills}{' '}
{t('quantity', 'Quantity').toUpperCase()}{' '}
{`${orderBasketItem.pillsDispensed} ${orderBasketItem.quantityUnits?.value?.toLowerCase() ?? ''}`}
{orderBasketItem.patientInstructions && <>&mdash; {orderBasketItem.patientInstructions}</>}
</span>
</span>
<br />
<span className={styles.label01}>
<span className={styles.indicationLabel}>{t('indication', 'Indication').toUpperCase()}</span>{' '}
<span className={styles.dosageInfo}>
{!!orderBasketItem.indication ? orderBasketItem.indication : <i>{t('none', 'None')}</i>}
<br />
<span className={styles.label01}>
<span className={styles.indicationLabel}>{t('indication', 'Indication').toUpperCase()}</span>{' '}
<span className={styles.dosageInfo}>
{!!orderBasketItem.indication ? orderBasketItem.indication : <i>{t('none', 'None')}</i>}
</span>
{!!orderBasketItem.orderError && (
<>
<br />
<span className={styles.orderErrorText}>
<WarningIcon size={16} /> &nbsp;{' '}
<span className={styles.label01}>{t('error', 'Error').toUpperCase()}</span> &nbsp;
{orderBasketItem.orderError.responseBody?.error?.message ?? orderBasketItem.orderError.message}
</span>
</>
)}
</span>
{!!orderBasketItem.orderError && (
<>
<br />
<span className={styles.orderErrorText}>
<WarningIcon size={16} /> &nbsp;{' '}
<span className={styles.label01}>{t('error', 'Error').toUpperCase()}</span> &nbsp;
{orderBasketItem.orderError.responseBody?.error?.message ?? orderBasketItem.orderError.message}
</span>
</>
)}
</span>
</div>
<IconButton
kind="ghost"
align="left"
size={isTablet ? 'lg' : 'sm'}
label={t('removeFromBasket', 'Remove from basket')}
onClick={() => {
shouldOnClickBeCalled.current = false;
onRemoveClick();
}}
>
<TrashCanIcon size={16} className={styles.removeButton} />
</IconButton>
</div>
<Button
className={styles.removeButton}
kind="ghost"
hasIconOnly={true}
renderIcon={(props: ComponentProps<typeof TrashCanIcon>) => <TrashCanIcon size={16} {...props} />}
iconDescription={t('removeFromBasket', 'Remove from basket')}
onClick={() => {
shouldOnClickBeCalled.current = false;
onRemoveClick();
}}
tooltipPosition="left"
<ExtensionSlot
name="order-item-additional-info-slot"
state={additionalInfoSlotState}
className={styles.additionalInfoContainer}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@
}

.removeButton {
svg {
fill: $danger;
}
fill: $danger;
}

.label01 {
Expand All @@ -99,3 +97,16 @@
overflow: hidden;
white-space: nowrap;
}

.additionalInfoContainer {
padding: layout.$spacing-05 0;
display: flex;
flex-flow: row;
justify-content: flex-start;
align-items: center;
gap: layout.$spacing-05;
}

.additionalInfoContainer:not(:has(div:not(:empty))) {
padding: 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useMemo } from 'react';
import { useOrderPrice } from '../hooks/useOrderPrice';
import styles from './order-price-details.scss';
import { SkeletonText, Tooltip } from '@carbon/react';
import { useTranslation } from 'react-i18next';
import { getLocale, InformationIcon } from '@openmrs/esm-framework';

interface OrderPriceDetailsComponentProps {
orderItemUuid: string;
}

const OrderPriceDetailsComponent: React.FC<OrderPriceDetailsComponentProps> = ({ orderItemUuid }) => {
const { t } = useTranslation();
const locale = getLocale();
const { data: priceData, isLoading, error } = useOrderPrice(orderItemUuid);

const amount = useMemo(() => {
if (!priceData || priceData.entry.length === 0) {
return null;
}
return priceData.entry[0].resource.propertyGroup[0]?.priceComponent[0]?.amount;
}, [priceData]);

const formattedPrice = useMemo((): string => {
if (!amount) return '';
try {
new Intl.NumberFormat(locale, {
style: 'currency',
currency: amount.currency,
});

return new Intl.NumberFormat(locale, {
style: 'currency',
currency: amount.currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount.value);
} catch (error) {
console.error(`Invalid currency code: ${amount.currency}. Error: ${error.message}`);
return `${new Intl.NumberFormat(locale, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount.value)} ${amount.currency}`;
}
}, [locale, amount]);

if (isLoading) {
return <SkeletonText width="100px" role="progressbar" />;
}

if (!priceData || !amount || error) {
return null;
}

return (
<div className={styles.priceDetailsContainer}>
<span className={styles.priceLabel}>{t('price', 'Price')}:</span>
{formattedPrice}
<Tooltip
align="bottom-left"
className={styles.priceToolTip}
label={t(
'priceDisclaimer',
'This price is indicative and may not reflect final costs, which could vary due to discounts, insurance coverage, or other pricing rules',
)}
>
<button className={styles.priceToolTipTrigger} type="button">
<InformationIcon size={16} />
</button>
</Tooltip>
</div>
);
};

export default OrderPriceDetailsComponent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@use '@carbon/type';
@use '@carbon/layout';

.priceDetailsContainer {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}

.priceLabel {
@include type.type-style('heading-compact-01');
padding-inline-end: layout.$spacing-02;
}

.priceToolTip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.priceToolTipTrigger {
display: flex;
padding: 0 0 0 layout.$spacing-02;
border: none;
outline: none;
}
Loading

0 comments on commit c4dfa95

Please sign in to comment.