feat: wire currency conversion into price display
All checks were successful
CI / ci (push) Successful in 1m22s
CI / e2e (push) Has been skipped
CI / deploy (push) Successful in 14s

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:
2026-04-13 21:44:32 +02:00
parent c4ddc573d4
commit 28dfef555c
4 changed files with 40 additions and 4 deletions

View File

@@ -235,6 +235,7 @@ export function CollectionView() {
cropZoom={item.cropZoom}
cropX={item.cropX}
cropY={item.cropY}
priceCurrency={item.priceCurrency}
/>
))}
</div>

View File

@@ -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">

View File

@@ -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,

View File

@@ -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}