← back to Watches
src/components/ComparisonView.jsx
259 lines
import React, { useState, useEffect, useRef } from 'react';
import { Chart, registerables } from 'chart.js';
Chart.register(...registerables);
function ComparisonView({ watches }) {
const [selectedWatches, setSelectedWatches] = useState([]);
const chartRef = useRef(null);
const chartInstance = useRef(null);
useEffect(() => {
if (selectedWatches.length > 0 && chartRef.current) {
renderComparisonChart();
}
return () => {
if (chartInstance.current) {
chartInstance.current.destroy();
}
};
}, [selectedWatches]);
const toggleWatch = (watchId) => {
if (selectedWatches.includes(watchId)) {
setSelectedWatches(selectedWatches.filter(id => id !== watchId));
} else if (selectedWatches.length < 5) {
setSelectedWatches([...selectedWatches, watchId]);
}
};
const renderComparisonChart = () => {
if (!chartRef.current) return;
const ctx = chartRef.current.getContext('2d');
if (chartInstance.current) {
chartInstance.current.destroy();
}
const colors = [
'#C8102E', // Omega Red
'#1A1A2E', // Omega Navy
'#FFD700', // Gold
'#4CAF50', // Green
'#2196F3' // Blue
];
const datasets = selectedWatches.map((watchId, index) => {
const watch = watches.find(w => w.id === watchId);
if (!watch) return null;
return {
label: watch.model,
data: watch.priceHistory.map(p => ({ x: p.year, y: p.price })),
borderColor: colors[index],
backgroundColor: colors[index] + '20',
borderWidth: 2,
fill: false,
tension: 0.4,
pointRadius: 4,
pointHoverRadius: 6
};
}).filter(Boolean);
chartInstance.current = new Chart(ctx, {
type: 'line',
data: { datasets },
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'top',
labels: {
font: { size: 12, weight: 'bold' },
usePointStyle: true,
padding: 15
}
},
tooltip: {
backgroundColor: '#1A1A2E',
titleFont: { size: 14 },
bodyFont: { size: 13 },
padding: 12,
callbacks: {
label: function(context) {
return `${context.dataset.label}: $${context.parsed.y.toLocaleString()}`;
}
}
}
},
scales: {
y: {
beginAtZero: false,
ticks: {
callback: value => '$' + value.toLocaleString(),
font: { size: 11 }
},
grid: { color: 'rgba(0, 0, 0, 0.05)' }
},
x: {
type: 'linear',
ticks: {
font: { size: 11 },
callback: value => Math.round(value)
},
grid: { display: false }
}
}
}
});
};
const calculateComparison = () => {
return selectedWatches.map(watchId => {
const watch = watches.find(w => w.id === watchId);
if (!watch || watch.priceHistory.length < 2) return null;
const firstPrice = watch.priceHistory[0].price;
const lastPrice = watch.priceHistory[watch.priceHistory.length - 1].price;
const appreciation = ((lastPrice - firstPrice) / firstPrice * 100).toFixed(2);
return {
id: watch.id,
model: watch.model,
series: watch.series,
originalPrice: firstPrice,
currentPrice: lastPrice,
appreciation,
gain: lastPrice - firstPrice
};
}).filter(Boolean);
};
const comparisonData = calculateComparison();
return (
<div className="space-y-6">
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-2xl font-bold text-gray-800 mb-4">
📊 Compare Watch Price Performance
</h2>
<p className="text-gray-600 mb-4">
Select up to 5 watches to compare their price history side-by-side
</p>
</div>
{/* Watch Selection */}
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-bold text-gray-800 mb-4">
Select Watches ({selectedWatches.length}/5)
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
{watches.map(watch => {
const isSelected = selectedWatches.includes(watch.id);
const canSelect = selectedWatches.length < 5 || isSelected;
return (
<button
key={watch.id}
onClick={() => canSelect && toggleWatch(watch.id)}
disabled={!canSelect}
className={`p-4 rounded-lg border-2 text-left transition-all ${
isSelected
? 'border-omega-red bg-red-50'
: canSelect
? 'border-gray-200 hover:border-omega-red hover:bg-gray-50'
: 'border-gray-100 bg-gray-50 opacity-50 cursor-not-allowed'
}`}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="font-semibold text-gray-800 text-sm">{watch.model}</p>
<p className="text-xs text-gray-500 mt-1">{watch.series}</p>
</div>
{isSelected && (
<span className="text-omega-red text-xl">✓</span>
)}
</div>
</button>
);
})}
</div>
</div>
{/* Comparison Chart */}
{selectedWatches.length > 0 && (
<>
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-xl font-bold text-gray-800 mb-4">Price Comparison Chart</h3>
<div className="h-96">
<canvas ref={chartRef}></canvas>
</div>
</div>
{/* Comparison Table */}
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-xl font-bold text-gray-800 mb-4">Performance Comparison</h3>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b-2 border-gray-300">
<th className="text-left py-3 px-4">Watch Model</th>
<th className="text-left py-3 px-4">Series</th>
<th className="text-right py-3 px-4">Original Price</th>
<th className="text-right py-3 px-4">Current Price</th>
<th className="text-right py-3 px-4">Total Gain</th>
<th className="text-right py-3 px-4">Appreciation</th>
</tr>
</thead>
<tbody>
{comparisonData
.sort((a, b) => parseFloat(b.appreciation) - parseFloat(a.appreciation))
.map((watch, index) => (
<tr key={watch.id} className="border-b hover:bg-gray-50">
<td className="py-3 px-4 font-semibold">{watch.model}</td>
<td className="py-3 px-4 text-gray-600">{watch.series}</td>
<td className="py-3 px-4 text-right">
${watch.originalPrice.toLocaleString()}
</td>
<td className="py-3 px-4 text-right font-semibold text-omega-red">
${watch.currentPrice.toLocaleString()}
</td>
<td className="py-3 px-4 text-right font-semibold text-green-600">
+${watch.gain.toLocaleString()}
</td>
<td className="py-3 px-4 text-right">
<span className={`inline-block px-3 py-1 rounded-full font-bold ${
index === 0
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-800'
}`}>
+{watch.appreciation}%
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</>
)}
{selectedWatches.length === 0 && (
<div className="bg-gray-50 rounded-lg p-12 text-center">
<div className="text-6xl mb-4">📈</div>
<p className="text-gray-500 text-lg">
Select watches above to start comparing their price performance
</p>
</div>
)}
</div>
);
}
export default ComparisonView;