Fix price formatting for PostgreSQL DECIMAL type

PostgreSQL returns DECIMAL as strings, not numbers.
Updated all price formatting functions to handle both.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
clucraft 2026-01-20 14:22:40 -05:00
parent 5263ac93a9
commit a2b0c2cc65
3 changed files with 29 additions and 15 deletions

View file

@ -41,13 +41,15 @@ export default function PriceChart({
const chartData = prices.map((p) => ({
date: new Date(p.recorded_at).getTime(),
price: parseFloat(p.price.toString()),
price: typeof p.price === 'string' ? parseFloat(p.price) : p.price,
}));
const minPrice = Math.min(...chartData.map((d) => d.price));
const maxPrice = Math.max(...chartData.map((d) => d.price));
const avgPrice =
chartData.reduce((sum, d) => sum + d.price, 0) / chartData.length;
const priceValues = chartData.map((d) => d.price).filter((p) => !isNaN(p));
const minPrice = priceValues.length > 0 ? Math.min(...priceValues) : 0;
const maxPrice = priceValues.length > 0 ? Math.max(...priceValues) : 0;
const avgPrice = priceValues.length > 0
? priceValues.reduce((sum, p) => sum + p, 0) / priceValues.length
: 0;
const formatDate = (timestamp: number) => {
const date = new Date(timestamp);
@ -55,6 +57,7 @@ export default function PriceChart({
};
const formatPrice = (value: number) => {
if (value === null || value === undefined || isNaN(value)) return 'N/A';
return `${currencySymbol}${value.toFixed(2)}`;
};

View file

@ -7,11 +7,13 @@ interface ProductCardProps {
}
export default function ProductCard({ product, onDelete }: ProductCardProps) {
const formatPrice = (price: number | null, currency: string | null) => {
if (price === null) return 'N/A';
const formatPrice = (price: number | string | null, currency: string | null) => {
if (price === null || price === undefined) return 'N/A';
const numPrice = typeof price === 'string' ? parseFloat(price) : price;
if (isNaN(numPrice)) return 'N/A';
const currencySymbol =
currency === 'EUR' ? '€' : currency === 'GBP' ? '£' : '$';
return `${currencySymbol}${price.toFixed(2)}`;
return `${currencySymbol}${numPrice.toFixed(2)}`;
};
const formatDate = (dateStr: string | null) => {

View file

@ -71,11 +71,13 @@ export default function ProductDetail() {
fetchData(days);
};
const formatPrice = (price: number | null, currency: string | null) => {
if (price === null) return 'N/A';
const formatPrice = (price: number | string | null, currency: string | null) => {
if (price === null || price === undefined) return 'N/A';
const numPrice = typeof price === 'string' ? parseFloat(price) : price;
if (isNaN(numPrice)) return 'N/A';
const currencySymbol =
currency === 'EUR' ? '€' : currency === 'GBP' ? '£' : '$';
return `${currencySymbol}${price.toFixed(2)}`;
return `${currencySymbol}${numPrice.toFixed(2)}`;
};
if (isLoading) {
@ -105,10 +107,17 @@ export default function ProductDetail() {
);
}
const priceChange =
product.stats && prices.length > 1
? ((product.current_price || 0) - prices[0].price) / prices[0].price
: null;
const priceChange = (() => {
if (!product.stats || prices.length < 1) return null;
const currentPrice = typeof product.current_price === 'string'
? parseFloat(product.current_price)
: (product.current_price || 0);
const firstPrice = typeof prices[0].price === 'string'
? parseFloat(prices[0].price)
: prices[0].price;
if (firstPrice === 0) return null;
return (currentPrice - firstPrice) / firstPrice;
})();
return (
<Layout>