← back to Watches
src/components/RecentSales.jsx
142 lines
import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';
/**
* Format price with currency
*/
function formatPrice(price, currency = 'USD') {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(price);
}
/**
* Format date to readable string
*/
function formatDate(dateStr) {
const date = new Date(dateStr);
return new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
}).format(date);
}
/**
* Get condition badge color
*/
function getConditionColor(condition) {
const colors = {
'Like New': 'bg-green-900/50 text-green-300 border-green-700/50',
'Excellent': 'bg-blue-900/50 text-blue-300 border-blue-700/50',
'Very Good': 'bg-amber-900/50 text-amber-300 border-amber-700/50',
'Good': 'bg-gray-700/50 text-gray-300 border-gray-600/50',
'Fair': 'bg-orange-900/50 text-orange-300 border-orange-700/50'
};
return colors[condition] || colors['Good'];
}
/**
* RecentSales - Shows recent sales history
* @param {Array} sales - Array of sale objects { date, price, condition, source }
* @param {number} limit - Max number of sales to show
* @param {string} className - Additional CSS classes
*/
export default function RecentSales({ sales = [], limit = 5, className = '' }) {
// Handle empty state
if (!sales || sales.length === 0) {
return (
<div className={`text-gray-500 text-center py-4 ${className}`}>
<svg
className="w-8 h-8 mx-auto mb-2 opacity-50"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
/>
</svg>
<p className="text-sm">No recent sales data</p>
</div>
);
}
// Sort by date (most recent first) and limit
const sortedSales = [...sales]
.sort((a, b) => new Date(b.date) - new Date(a.date))
.slice(0, limit);
return (
<motion.div
className={`${className}`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.4 }}
>
<h4 className="text-gray-400 text-sm font-medium mb-3">Recent Sales</h4>
<div className="space-y-2">
<AnimatePresence mode="popLayout">
{sortedSales.map((sale, index) => (
<motion.div
key={`${sale.date}-${sale.price}-${index}`}
className="bg-gray-800/30 border border-gray-700/50 rounded-lg p-3 hover:border-amber-500/30 transition-colors"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
transition={{ delay: index * 0.05, duration: 0.3 }}
layout
>
{/* Mobile: Stack layout */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<div className="flex items-center justify-between sm:justify-start gap-3">
<span className="text-amber-400 font-semibold">
{formatPrice(sale.price)}
</span>
<span className={`px-2 py-0.5 text-xs rounded-full border ${getConditionColor(sale.condition)}`}>
{sale.condition}
</span>
</div>
<div className="flex items-center justify-between sm:justify-end gap-3 text-xs text-gray-500">
<span>{formatDate(sale.date)}</span>
<span className="text-gray-600">•</span>
{sale.href ? (
<a
href={sale.href}
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 hover:text-blue-300 hover:underline flex items-center gap-1"
onClick={(e) => e.stopPropagation()}
>
{sale.source}
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
) : (
<span>{sale.source}</span>
)}
</div>
</div>
</motion.div>
))}
</AnimatePresence>
</div>
{sales.length > limit && (
<p className="text-gray-500 text-xs mt-2 text-center">
Showing {limit} of {sales.length} sales
</p>
)}
</motion.div>
);
}