← back to Wine Finder Next

src/components/ListingsTable.tsx

283 lines

'use client';

import { useMemo, useState } from 'react';
import { WineListing, ArbitrageOpportunity } from '../types/wine';

interface ListingsTableProps {
  listings: WineListing[];
}

export default function ListingsTable({ listings }: ListingsTableProps) {
  const [activeTab, setActiveTab] = useState<'all' | 'auction' | 'retail' | 'arbitrage'>('all');

  // Calculate arbitrage opportunities
  const arbitrageOpportunities = useMemo(() => {
    const retail = listings.filter((l) => l.provider === 'retail');
    const auction = listings.filter((l) => l.provider === 'auction');

    const opportunities: ArbitrageOpportunity[] = [];

    auction.forEach((auctionListing) => {
      retail.forEach((retailListing) => {
        // Fuzzy match wine names
        const nameMatch = auctionListing.name.toLowerCase().includes(retailListing.name.toLowerCase()) ||
                         retailListing.name.toLowerCase().includes(auctionListing.name.toLowerCase());

        if (nameMatch && auctionListing.currentPrice.amount > retailListing.currentPrice.amount) {
          const spread = auctionListing.currentPrice.amount - retailListing.currentPrice.amount;
          const spreadPercentage = (spread / retailListing.currentPrice.amount) * 100;

          if (spread > 10) { // Only show significant arbitrage (>$10)
            opportunities.push({
              wine: {
                id: retailListing.id,
                name: retailListing.name,
                vintage: retailListing.vintage,
                region: retailListing.region,
                rating: retailListing.rating,
              },
              retailListing,
              auctionListing,
              spread,
              spreadPercentage,
            });
          }
        }
      });
    });

    return opportunities.sort((a, b) => b.spread - a.spread);
  }, [listings]);

  // Best deal of the day (highest $ saved)
  const bestDeal = useMemo(() => {
    if (arbitrageOpportunities.length === 0) return null;
    return arbitrageOpportunities[0];
  }, [arbitrageOpportunities]);

  // Filter listings by tab
  const filteredListings = useMemo(() => {
    if (activeTab === 'auction') return listings.filter(l => l.provider === 'auction');
    if (activeTab === 'retail') return listings.filter(l => l.provider === 'retail');
    if (activeTab === 'arbitrage') return []; // Show arbitrage table instead
    return listings;
  }, [listings, activeTab]);

  return (
    <div className="w-full max-w-7xl mx-auto">
      {/* Best Deal Hero Banner */}
      {bestDeal && (
        <div className="mb-8 bg-gradient-to-r from-red-600 via-rose-600 to-pink-600 text-white rounded-xl p-8 shadow-2xl">
          <div className="text-center mb-4">
            <div className="inline-flex items-center gap-3 bg-white text-red-600 px-6 py-2 rounded-full font-black text-xl">
              🔥 BEST ARBITRAGE OPPORTUNITY 🔥
            </div>
          </div>
          <div className="grid md:grid-cols-3 gap-6 text-center">
            <div>
              <div className="text-sm opacity-90 mb-2">Wine</div>
              <div className="font-bold text-2xl">{bestDeal.wine.name}</div>
              {bestDeal.wine.vintage && <div className="text-lg opacity-90">{bestDeal.wine.vintage}</div>}
            </div>
            <div>
              <div className="text-sm opacity-90 mb-2">Buy Retail</div>
              <div className="font-black text-4xl text-yellow-300">${bestDeal.retailListing.currentPrice.amount.toFixed(2)}</div>
              <div className="text-sm opacity-90">{bestDeal.retailListing.providerName}</div>
            </div>
            <div>
              <div className="text-sm opacity-90 mb-2">Sell at Auction</div>
              <div className="font-black text-4xl text-green-300">${bestDeal.auctionListing.currentPrice.amount.toFixed(2)}</div>
              <div className="text-sm opacity-90">{bestDeal.auctionListing.providerName}</div>
            </div>
          </div>
          <div className="mt-6 text-center">
            <div className="text-3xl font-black text-green-300 drop-shadow-lg">
              💰 PROFIT: ${bestDeal.spread.toFixed(2)} ({bestDeal.spreadPercentage.toFixed(1)}%)
            </div>
          </div>
        </div>
      )}

      {/* Tabs */}
      <div className="flex gap-2 mb-6 border-b-2 border-gray-200">
        <button
          onClick={() => setActiveTab('all')}
          className={`px-6 py-3 font-semibold transition-all ${
            activeTab === 'all'
              ? 'border-b-4 border-purple-600 text-purple-600'
              : 'text-gray-500 hover:text-gray-700'
          }`}
        >
          All Listings ({listings.length})
        </button>
        <button
          onClick={() => setActiveTab('auction')}
          className={`px-6 py-3 font-semibold transition-all ${
            activeTab === 'auction'
              ? 'border-b-4 border-purple-600 text-purple-600'
              : 'text-gray-500 hover:text-gray-700'
          }`}
        >
          Auctions ({listings.filter(l => l.provider === 'auction').length})
        </button>
        <button
          onClick={() => setActiveTab('retail')}
          className={`px-6 py-3 font-semibold transition-all ${
            activeTab === 'retail'
              ? 'border-b-4 border-purple-600 text-purple-600'
              : 'text-gray-500 hover:text-gray-700'
          }`}
        >
          Retail ({listings.filter(l => l.provider === 'retail').length})
        </button>
        <button
          onClick={() => setActiveTab('arbitrage')}
          className={`px-6 py-3 font-semibold transition-all ${
            activeTab === 'arbitrage'
              ? 'border-b-4 border-red-600 text-red-600'
              : 'text-gray-500 hover:text-gray-700'
          }`}
        >
          🔥 Arbitrage ({arbitrageOpportunities.length})
        </button>
      </div>

      {/* Arbitrage Opportunities Table */}
      {activeTab === 'arbitrage' && (
        <div className="bg-white rounded-lg shadow-lg overflow-hidden">
          {arbitrageOpportunities.length === 0 ? (
            <div className="p-12 text-center text-gray-500">
              <div className="text-6xl mb-4">🔍</div>
              <div className="text-xl font-semibold">No arbitrage opportunities found</div>
              <div className="text-sm mt-2">Try searching for premium wines that appear on both retail and auction sites</div>
            </div>
          ) : (
            <table className="w-full">
              <thead className="bg-gradient-to-r from-red-600 to-pink-600 text-white">
                <tr>
                  <th className="px-6 py-4 text-left font-bold">Wine</th>
                  <th className="px-6 py-4 text-right font-bold">Buy (Retail)</th>
                  <th className="px-6 py-4 text-right font-bold">Sell (Auction)</th>
                  <th className="px-6 py-4 text-right font-bold">Last Sale</th>
                  <th className="px-6 py-4 text-right font-bold">Profit ($)</th>
                  <th className="px-6 py-4 text-right font-bold">Profit (%)</th>
                </tr>
              </thead>
              <tbody>
                {arbitrageOpportunities.map((opp, idx) => (
                  <tr key={idx} className="border-b border-gray-200 hover:bg-pink-50 transition-colors">
                    <td className="px-6 py-4">
                      <div className="font-semibold text-gray-900">{opp.wine.name}</div>
                      {opp.wine.vintage && <div className="text-sm text-gray-600">{opp.wine.vintage}</div>}
                      {opp.wine.region && <div className="text-xs text-gray-500">{opp.wine.region}</div>}
                    </td>
                    <td className="px-6 py-4 text-right">
                      <div className="font-bold text-green-600">${opp.retailListing.currentPrice.amount.toFixed(2)}</div>
                      <div className="text-xs text-gray-500">{opp.retailListing.providerName}</div>
                    </td>
                    <td className="px-6 py-4 text-right">
                      <div className="font-bold text-blue-600">${opp.auctionListing.currentPrice.amount.toFixed(2)}</div>
                      <div className="text-xs text-gray-500">{opp.auctionListing.providerName}</div>
                      {opp.auctionListing.bidCount && (
                        <div className="text-xs text-gray-400">{opp.auctionListing.bidCount} bids</div>
                      )}
                    </td>
                    <td className="px-6 py-4 text-right">
                      {(opp.auctionListing as any).lastSale ? (
                        <>
                          <div className="font-semibold text-purple-600">${(opp.auctionListing as any).lastSale.price.toFixed(2)}</div>
                          <div className="text-xs text-gray-500">{(opp.auctionListing as any).lastSale.source}</div>
                          <div className="text-xs text-gray-400">{(opp.auctionListing as any).lastSale.daysAgo}d ago</div>
                        </>
                      ) : (
                        <div className="text-xs text-gray-400">No data</div>
                      )}
                    </td>
                    <td className="px-6 py-4 text-right">
                      <div className="font-black text-xl text-red-600">${opp.spread.toFixed(2)}</div>
                    </td>
                    <td className="px-6 py-4 text-right">
                      <div className="font-bold text-red-600">{opp.spreadPercentage.toFixed(1)}%</div>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      )}

      {/* Regular Listings Table */}
      {activeTab !== 'arbitrage' && (
        <div className="bg-white rounded-lg shadow-lg overflow-hidden">
          {filteredListings.length === 0 ? (
            <div className="p-12 text-center text-gray-500">
              <div className="text-6xl mb-4">🍷</div>
              <div className="text-xl font-semibold">No wines found</div>
              <div className="text-sm mt-2">Try a different search query</div>
            </div>
          ) : (
            <table className="w-full">
              <thead className="bg-gradient-to-r from-purple-600 to-pink-600 text-white">
                <tr>
                  <th className="px-6 py-4 text-left font-bold">Wine</th>
                  <th className="px-6 py-4 text-left font-bold">Type</th>
                  <th className="px-6 py-4 text-left font-bold">Source</th>
                  <th className="px-6 py-4 text-right font-bold">Price</th>
                  <th className="px-6 py-4 text-center font-bold">Rating</th>
                  <th className="px-6 py-4 text-center font-bold">Link</th>
                </tr>
              </thead>
              <tbody>
                {filteredListings.map((wine) => (
                  <tr key={wine.id} className="border-b border-gray-200 hover:bg-purple-50 transition-colors">
                    <td className="px-6 py-4">
                      <div className="font-semibold text-gray-900">{wine.name}</div>
                      {wine.vintage && <div className="text-sm text-gray-600">{wine.vintage}</div>}
                      {wine.region && <div className="text-xs text-gray-500">{wine.region}</div>}
                    </td>
                    <td className="px-6 py-4">
                      <span className={`px-3 py-1 rounded-full text-xs font-semibold ${
                        wine.provider === 'auction'
                          ? 'bg-blue-100 text-blue-800'
                          : 'bg-green-100 text-green-800'
                      }`}>
                        {wine.provider === 'auction' ? '🔨 Auction' : '🛒 Retail'}
                      </span>
                    </td>
                    <td className="px-6 py-4 text-sm text-gray-700">{wine.providerName}</td>
                    <td className="px-6 py-4 text-right">
                      <div className="font-bold text-lg text-purple-600">${wine.currentPrice.amount.toFixed(2)}</div>
                      {wine.bidCount !== undefined && (
                        <div className="text-xs text-gray-500">{wine.bidCount} bids</div>
                      )}
                    </td>
                    <td className="px-6 py-4 text-center">
                      {wine.rating && (
                        <div className="flex items-center justify-center gap-1">
                          <span className="text-yellow-500">⭐</span>
                          <span className="font-semibold">{wine.rating}</span>
                        </div>
                      )}
                    </td>
                    <td className="px-6 py-4 text-center">
                      <a
                        href={wine.url}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="text-purple-600 hover:text-purple-800 font-semibold hover:underline"
                      >
                        View →
                      </a>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      )}
    </div>
  );
}