deckhearth/components/card-detail/CardDetailPriceHistoryTab.js

111 lines
3.9 KiB
JavaScript
Raw Permalink Normal View History

function buildChartGeometry(points, width, height, padding) {
if (!points.length) return null;
const amounts = points.map((point) => point.amount);
const min = Math.min(...amounts);
const max = Math.max(...amounts);
const range = max - min || 1;
const innerWidth = width - padding * 2;
const innerHeight = height - padding * 2;
const coordinates = points.map((point, index) => {
const x = padding + (index / Math.max(points.length - 1, 1)) * innerWidth;
const y = padding + innerHeight - ((point.amount - min) / range) * innerHeight;
return { x, y };
});
const polyline = coordinates.map(({ x, y }) => `${x},${y}`).join(' ');
const area = [
`${coordinates[0].x},${height - padding}`,
...coordinates.map(({ x, y }) => `${x},${y}`),
`${coordinates[coordinates.length - 1].x},${height - padding}`,
].join(' ');
return { polyline, area, min, max };
}
export default function CardDetailPriceHistoryTab({
points = [],
priceHistoryLoading,
formatCurrency,
}) {
const chart = buildChartGeometry(points, 640, 220, 24);
return (
<div className="space-y-6">
<h3 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
Price History
</h3>
<div className="glass-panel p-6 rounded-xl">
<h4 className="text-lg font-semibold mb-6" style={{ color: 'var(--text-primary)' }}>
Price Trend
</h4>
{priceHistoryLoading ? (
<div className="h-64 flex items-center justify-center">
<p style={{ color: 'var(--text-secondary)' }} aria-live="polite">Loading price history…</p>
</div>
) : points.length === 0 ? (
<div className="h-64 flex items-center justify-center text-center px-6">
<p style={{ color: 'var(--text-secondary)' }}>
No price history yet. Snapshots are recorded during catalog sync — check back after the next update.
</p>
</div>
) : (
<div className="space-y-4">
<svg
viewBox="0 0 640 220"
className="w-full h-56"
role="img"
aria-label="Card price history chart"
>
<defs>
<linearGradient id="price-history-fill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="var(--accent-flame)" stopOpacity="0.35" />
<stop offset="100%" stopColor="var(--accent-flame)" stopOpacity="0" />
</linearGradient>
</defs>
<polygon
points={chart.area}
fill="url(#price-history-fill)"
/>
<polyline
points={chart.polyline}
fill="none"
stroke="var(--accent-flame)"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<div className="flex justify-between text-sm" style={{ color: 'var(--text-secondary)' }}>
<span>{points[0]?.date}</span>
<span>
Low {formatCurrency(chart.min)} · High {formatCurrency(chart.max)}
</span>
<span>{points[points.length - 1]?.date}</span>
</div>
</div>
)}
</div>
{points.length > 0 && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="glass-panel p-6 rounded-xl">
<div className="text-center">
<div className="text-2xl mb-2" aria-hidden="true">💰</div>
<h5 className="font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
Latest Price
</h5>
<div className="text-xl font-bold gradient-text-gold">
{formatCurrency(points[points.length - 1].amount)}
</div>
</div>
</div>
</div>
)}
</div>
);
}