feat: wire currency conversion into price display
useFormatters().price() now accepts an optional sourceCurrency param. When showConversions is enabled and the source differs from the user's currency, it converts via ECB rates and shows dual format: "€200.00 (~$218.00)". ItemCard and CollectionView pass priceCurrency through from API data. Setup detail items also pass priceCurrency. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -235,6 +235,7 @@ export function CollectionView() {
|
||||
cropZoom={item.cropZoom}
|
||||
cropX={item.cropX}
|
||||
cropY={item.cropY}
|
||||
priceCurrency={item.priceCurrency}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,7 @@ interface ItemCardProps {
|
||||
classification?: string;
|
||||
onClassificationCycle?: () => void;
|
||||
linkTo?: string | null;
|
||||
priceCurrency?: string | null;
|
||||
}
|
||||
|
||||
export function ItemCard({
|
||||
@@ -48,6 +49,7 @@ export function ItemCard({
|
||||
classification,
|
||||
onClassificationCycle,
|
||||
linkTo,
|
||||
priceCurrency,
|
||||
}: ItemCardProps) {
|
||||
const { weight, price } = useFormatters();
|
||||
const navigate = useNavigate();
|
||||
@@ -232,7 +234,7 @@ export function ItemCard({
|
||||
)}
|
||||
{priceCents != null && (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-50 text-green-500">
|
||||
{price(priceCents)}
|
||||
{price(priceCents, priceCurrency)}
|
||||
</span>
|
||||
)}
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-50 text-gray-600">
|
||||
|
||||
@@ -1,15 +1,47 @@
|
||||
import { formatPrice, formatWeight } from "../lib/formatters";
|
||||
import {
|
||||
type Currency,
|
||||
formatDualPrice,
|
||||
formatPrice,
|
||||
formatWeight,
|
||||
} from "../lib/formatters";
|
||||
import { useCurrency } from "./useCurrency";
|
||||
import { convertClientPrice, useExchangeRates } from "./useExchangeRates";
|
||||
import { useLanguage } from "./useLanguage";
|
||||
import { useWeightUnit } from "./useWeightUnit";
|
||||
|
||||
export function useFormatters() {
|
||||
const unit = useWeightUnit();
|
||||
const { currency } = useCurrency();
|
||||
const { currency, showConversions } = useCurrency();
|
||||
const locale = useLanguage();
|
||||
const { data: ratesData } = useExchangeRates();
|
||||
const rates = ratesData?.rates;
|
||||
|
||||
return {
|
||||
weight: (grams: number | null) => formatWeight(grams, unit, locale),
|
||||
price: (cents: number | null) => formatPrice(cents, currency, locale),
|
||||
/**
|
||||
* Format a price. If sourceCurrency differs from user's currency and
|
||||
* showConversions is enabled, returns dual format: "€200.00 (~$218.00)".
|
||||
* Otherwise returns price in source currency (or user's currency if no source given).
|
||||
*/
|
||||
price: (
|
||||
cents: number | null,
|
||||
sourceCurrency?: Currency | string | null,
|
||||
) => {
|
||||
if (cents == null) return "--";
|
||||
const source = (sourceCurrency as Currency) || currency;
|
||||
if (showConversions && rates && source !== currency) {
|
||||
const converted = convertClientPrice(cents, source, currency, rates);
|
||||
const dual = formatDualPrice({
|
||||
sourceCents: cents,
|
||||
sourceCurrency: source as Currency,
|
||||
targetCurrency: currency,
|
||||
convertedCents: converted,
|
||||
locale,
|
||||
});
|
||||
return `${dual.source} (${dual.converted})`;
|
||||
}
|
||||
return formatPrice(cents, source as Currency, locale);
|
||||
},
|
||||
unit,
|
||||
currency,
|
||||
locale,
|
||||
|
||||
@@ -358,6 +358,7 @@ function SetupDetailPage() {
|
||||
name={item.name}
|
||||
weightGrams={item.weightGrams}
|
||||
priceCents={item.priceCents}
|
||||
priceCurrency={item.priceCurrency}
|
||||
quantity={item.quantity}
|
||||
categoryName={categoryName}
|
||||
categoryIcon={categoryIcon}
|
||||
|
||||
Reference in New Issue
Block a user