← back to Handbag Auth Nextjs
src/components/EbaySalesChart.tsx
218 lines
'use client'
import { useState, useEffect } from 'react'
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'
interface EbaySalesChartProps {
brand: string
model: string
daysBack?: number
}
export function EbaySalesChart({ brand, model, daysBack = 90 }: EbaySalesChartProps) {
const [loading, setLoading] = useState(true)
const [salesData, setSalesData] = useState<any>(null)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
fetchSalesData()
}, [brand, model, daysBack])
const fetchSalesData = async () => {
setLoading(true)
setError(null)
try {
const params = new URLSearchParams({
brand,
model,
daysBack: daysBack.toString(),
maxResults: '100',
})
const res = await fetch(`/api/ebay-sold?${params}`)
const data = await res.json()
if (data.success) {
setSalesData(data)
} else {
setError(data.error || 'Failed to fetch sales data')
}
} catch (err) {
setError('Network error fetching sales data')
console.error(err)
} finally {
setLoading(false)
}
}
if (loading) {
return (
<div className="bg-white rounded-xl shadow-lg p-6">
<div className="animate-pulse">
<div className="h-6 bg-gray-300 rounded w-1/3 mb-4"></div>
<div className="h-64 bg-gray-300 rounded"></div>
</div>
</div>
)
}
if (error) {
return (
<div className="bg-white rounded-xl shadow-lg p-6">
<h3 className="text-xl font-bold text-gray-900 mb-4">eBay Sales History</h3>
<div className="text-center py-12">
<p className="text-red-600">{error}</p>
<button
onClick={fetchSalesData}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Retry
</button>
</div>
</div>
)
}
if (!salesData || salesData.count === 0) {
return (
<div className="bg-white rounded-xl shadow-lg p-6">
<h3 className="text-xl font-bold text-gray-900 mb-4">eBay Sales History</h3>
<div className="text-center py-12">
<p className="text-gray-500">No sales found for {brand} {model} in the last {daysBack} days</p>
</div>
</div>
)
}
// Prepare chart data - group by date
const chartData = salesData.sold_listings
.map((item: any) => ({
date: new Date(item.end_time).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
fullDate: item.end_time,
price: item.price_usd,
title: item.title,
condition: item.condition,
url: item.url,
}))
.sort((a: any, b: any) => new Date(a.fullDate).getTime() - new Date(b.fullDate).getTime())
const { statistics } = salesData
const CustomTooltip = ({ active, payload }: any) => {
if (active && payload && payload.length) {
const data = payload[0].payload
return (
<div className="bg-white p-4 rounded-lg shadow-xl border border-gray-200">
<p className="text-sm text-gray-600 mb-1">{data.fullDate.split('T')[0]}</p>
<p className="text-lg font-bold text-gray-900">${data.price.toLocaleString()}</p>
<p className="text-xs text-gray-500 mt-1">{data.condition}</p>
<p className="text-xs text-gray-400 mt-1 line-clamp-2">{data.title}</p>
</div>
)
}
return null
}
return (
<div className="bg-white rounded-xl shadow-lg p-6">
<div className="mb-6">
<h3 className="text-2xl font-bold text-gray-900">eBay Sales History</h3>
<p className="text-sm text-gray-600 mt-1">
Real sold listings for {brand} {model} (Last {daysBack} days)
</p>
</div>
{/* Statistics */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6">
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg p-4">
<p className="text-xs text-blue-600 font-medium uppercase tracking-wide">Sales Count</p>
<p className="text-2xl font-bold text-blue-900 mt-1">{statistics.count}</p>
<p className="text-xs text-blue-600 mt-1">Sold items</p>
</div>
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-lg p-4">
<p className="text-xs text-green-600 font-medium uppercase tracking-wide">Average</p>
<p className="text-2xl font-bold text-green-900 mt-1">${Math.round(statistics.avg_price).toLocaleString()}</p>
<p className="text-xs text-green-600 mt-1">Mean price</p>
</div>
<div className="bg-gradient-to-br from-purple-50 to-purple-100 rounded-lg p-4">
<p className="text-xs text-purple-600 font-medium uppercase tracking-wide">Median</p>
<p className="text-2xl font-bold text-purple-900 mt-1">${Math.round(statistics.median_price).toLocaleString()}</p>
<p className="text-xs text-purple-600 mt-1">Mid price</p>
</div>
<div className="bg-gradient-to-br from-orange-50 to-orange-100 rounded-lg p-4">
<p className="text-xs text-orange-600 font-medium uppercase tracking-wide">Lowest</p>
<p className="text-2xl font-bold text-orange-900 mt-1">${Math.round(statistics.min_price).toLocaleString()}</p>
<p className="text-xs text-orange-600 mt-1">Best deal</p>
</div>
<div className="bg-gradient-to-br from-red-50 to-red-100 rounded-lg p-4">
<p className="text-xs text-red-600 font-medium uppercase tracking-wide">Highest</p>
<p className="text-2xl font-bold text-red-900 mt-1">${Math.round(statistics.max_price).toLocaleString()}</p>
<p className="text-xs text-red-600 mt-1">Peak price</p>
</div>
</div>
{/* Chart */}
<div className="h-80 w-full">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={chartData}>
<defs>
<linearGradient id="colorEbaySales" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.3}/>
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0}/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
<XAxis
dataKey="date"
stroke="#6b7280"
style={{ fontSize: '12px' }}
/>
<YAxis
stroke="#6b7280"
style={{ fontSize: '12px' }}
tickFormatter={(value) => `$${value.toLocaleString()}`}
/>
<Tooltip content={<CustomTooltip />} />
<Area
type="monotone"
dataKey="price"
stroke="#3b82f6"
strokeWidth={2}
fill="url(#colorEbaySales)"
/>
</AreaChart>
</ResponsiveContainer>
</div>
{/* Recent Sales List */}
<div className="mt-6">
<h4 className="text-lg font-bold text-gray-900 mb-3">Recent Sales</h4>
<div className="space-y-2 max-h-60 overflow-y-auto">
{salesData.sold_listings.slice(0, 10).map((item: any) => (
<a
key={item.item_id}
href={item.url}
target="_blank"
rel="noopener noreferrer"
className="block p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"
>
<div className="flex items-center justify-between">
<div className="flex-1">
<p className="text-sm font-medium text-gray-900 line-clamp-1">{item.title}</p>
<p className="text-xs text-gray-500">{item.condition} • {new Date(item.end_time).toLocaleDateString()}</p>
</div>
<p className="text-lg font-bold text-blue-600 ml-4">${item.price_usd.toLocaleString()}</p>
</div>
</a>
))}
</div>
</div>
</div>
)
}