← 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>
  )
}