← back to Watches
src/components/WatchList.jsx
171 lines
import React, { useState } from 'react';
function WatchList({ watches, onWatchSelect }) {
const [searchTerm, setSearchTerm] = useState('');
const [selectedSeries, setSelectedSeries] = useState('all');
const series = ['all', ...new Set(watches.map(w => w.series))];
const filteredWatches = watches.filter(watch => {
const matchesSearch = watch.model.toLowerCase().includes(searchTerm.toLowerCase()) ||
watch.reference.toLowerCase().includes(searchTerm.toLowerCase());
const matchesSeries = selectedSeries === 'all' || watch.series === selectedSeries;
return matchesSearch && matchesSeries;
});
const calculateAppreciation = (watch) => {
if (watch.priceHistory.length < 2) return 0;
const first = watch.priceHistory[0].price;
const last = watch.priceHistory[watch.priceHistory.length - 1].price;
return (((last - first) / first) * 100).toFixed(2);
};
return (
<div className="space-y-6">
{/* Filters */}
<div className="bg-white rounded-lg shadow-md p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Search Watches
</label>
<input
type="text"
placeholder="Search by model or reference..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-omega-red focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Filter by Series
</label>
<select
value={selectedSeries}
onChange={(e) => setSelectedSeries(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-omega-red focus:border-transparent"
>
{series.map(s => (
<option key={s} value={s}>
{s === 'all' ? 'All Series' : s}
</option>
))}
</select>
</div>
</div>
</div>
{/* Watch Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredWatches.map(watch => {
const currentPrice = watch.priceHistory[watch.priceHistory.length - 1].price;
const originalPrice = watch.priceHistory[0].price;
const appreciation = calculateAppreciation(watch);
return (
<div
key={watch.id}
onClick={() => onWatchSelect(watch)}
className="bg-white rounded-lg shadow-md hover:shadow-xl transition-all cursor-pointer overflow-hidden group"
>
{/* Watch Image */}
{watch.imageUrl && (
<div className="relative h-64 overflow-hidden bg-gray-100">
<img
src={watch.imageUrl}
alt={watch.model}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500"
onError={(e) => {
e.target.src = 'https://images.unsplash.com/photo-1523170335258-f5ed11844a49?w=800';
}}
/>
<div className="absolute top-2 right-2 bg-black bg-opacity-70 text-white px-3 py-1 rounded-full text-xs font-semibold">
{watch.yearIntroduced}
</div>
</div>
)}
<div className="gradient-omega text-white p-4">
<h3 className="font-bold text-lg mb-1">{watch.model}</h3>
<p className="text-sm opacity-90">{watch.series}</p>
</div>
<div className="p-4">
{/* Specifications Preview */}
{watch.specifications && (
<div className="mb-4 bg-gray-50 p-3 rounded-lg">
<div className="grid grid-cols-2 gap-2 text-xs">
<div>
<span className="text-gray-500">Case:</span>
<span className="ml-1 font-semibold">{watch.specifications.caseSize}</span>
</div>
<div>
<span className="text-gray-500">Movement:</span>
<span className="ml-1 font-semibold text-omega-red">
{watch.specifications.movement?.split(' ')[1] || 'Auto'}
</span>
</div>
<div>
<span className="text-gray-500">Material:</span>
<span className="ml-1 font-semibold">
{watch.specifications.caseMaterial?.split(' ')[0]}
</span>
</div>
<div>
<span className="text-gray-500">WR:</span>
<span className="ml-1 font-semibold">{watch.specifications.waterResistance}</span>
</div>
</div>
</div>
)}
<div className="mb-4">
<p className="text-xs text-gray-500 mb-1">Reference</p>
<p className="font-mono text-sm font-semibold">{watch.reference}</p>
</div>
<div className="grid grid-cols-2 gap-4 mb-4">
<div>
<p className="text-xs text-gray-500 mb-1">Original Price</p>
<p className="font-semibold">${originalPrice.toLocaleString()}</p>
</div>
<div>
<p className="text-xs text-gray-500 mb-1">Current Price</p>
<p className="font-semibold text-omega-red">
${currentPrice.toLocaleString()}
</p>
</div>
</div>
<div className="pt-4 border-t">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">Appreciation</span>
<span className={`font-bold text-lg ${
parseFloat(appreciation) > 0 ? 'text-green-600' : 'text-red-600'
}`}>
{appreciation > 0 ? '+' : ''}{appreciation}%
</span>
</div>
</div>
<button className="w-full mt-4 bg-omega-red hover:bg-red-700 text-white font-semibold py-2 rounded-lg transition-colors">
View Full Details →
</button>
</div>
</div>
);
})}
</div>
{filteredWatches.length === 0 && (
<div className="text-center py-12">
<p className="text-gray-500 text-lg">No watches found matching your criteria</p>
</div>
)}
</div>
);
}
export default WatchList;