← back to Watches

src/components/ComparisonView.jsx

259 lines

import React, { useState, useEffect, useRef } from 'react';
import { Chart, registerables } from 'chart.js';

Chart.register(...registerables);

function ComparisonView({ watches }) {
  const [selectedWatches, setSelectedWatches] = useState([]);
  const chartRef = useRef(null);
  const chartInstance = useRef(null);

  useEffect(() => {
    if (selectedWatches.length > 0 && chartRef.current) {
      renderComparisonChart();
    }

    return () => {
      if (chartInstance.current) {
        chartInstance.current.destroy();
      }
    };
  }, [selectedWatches]);

  const toggleWatch = (watchId) => {
    if (selectedWatches.includes(watchId)) {
      setSelectedWatches(selectedWatches.filter(id => id !== watchId));
    } else if (selectedWatches.length < 5) {
      setSelectedWatches([...selectedWatches, watchId]);
    }
  };

  const renderComparisonChart = () => {
    if (!chartRef.current) return;

    const ctx = chartRef.current.getContext('2d');

    if (chartInstance.current) {
      chartInstance.current.destroy();
    }

    const colors = [
      '#C8102E', // Omega Red
      '#1A1A2E', // Omega Navy
      '#FFD700', // Gold
      '#4CAF50', // Green
      '#2196F3'  // Blue
    ];

    const datasets = selectedWatches.map((watchId, index) => {
      const watch = watches.find(w => w.id === watchId);
      if (!watch) return null;

      return {
        label: watch.model,
        data: watch.priceHistory.map(p => ({ x: p.year, y: p.price })),
        borderColor: colors[index],
        backgroundColor: colors[index] + '20',
        borderWidth: 2,
        fill: false,
        tension: 0.4,
        pointRadius: 4,
        pointHoverRadius: 6
      };
    }).filter(Boolean);

    chartInstance.current = new Chart(ctx, {
      type: 'line',
      data: { datasets },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        plugins: {
          legend: {
            display: true,
            position: 'top',
            labels: {
              font: { size: 12, weight: 'bold' },
              usePointStyle: true,
              padding: 15
            }
          },
          tooltip: {
            backgroundColor: '#1A1A2E',
            titleFont: { size: 14 },
            bodyFont: { size: 13 },
            padding: 12,
            callbacks: {
              label: function(context) {
                return `${context.dataset.label}: $${context.parsed.y.toLocaleString()}`;
              }
            }
          }
        },
        scales: {
          y: {
            beginAtZero: false,
            ticks: {
              callback: value => '$' + value.toLocaleString(),
              font: { size: 11 }
            },
            grid: { color: 'rgba(0, 0, 0, 0.05)' }
          },
          x: {
            type: 'linear',
            ticks: {
              font: { size: 11 },
              callback: value => Math.round(value)
            },
            grid: { display: false }
          }
        }
      }
    });
  };

  const calculateComparison = () => {
    return selectedWatches.map(watchId => {
      const watch = watches.find(w => w.id === watchId);
      if (!watch || watch.priceHistory.length < 2) return null;

      const firstPrice = watch.priceHistory[0].price;
      const lastPrice = watch.priceHistory[watch.priceHistory.length - 1].price;
      const appreciation = ((lastPrice - firstPrice) / firstPrice * 100).toFixed(2);

      return {
        id: watch.id,
        model: watch.model,
        series: watch.series,
        originalPrice: firstPrice,
        currentPrice: lastPrice,
        appreciation,
        gain: lastPrice - firstPrice
      };
    }).filter(Boolean);
  };

  const comparisonData = calculateComparison();

  return (
    <div className="space-y-6">
      <div className="bg-white rounded-lg shadow-md p-6">
        <h2 className="text-2xl font-bold text-gray-800 mb-4">
          📊 Compare Watch Price Performance
        </h2>
        <p className="text-gray-600 mb-4">
          Select up to 5 watches to compare their price history side-by-side
        </p>
      </div>

      {/* Watch Selection */}
      <div className="bg-white rounded-lg shadow-md p-6">
        <h3 className="text-lg font-bold text-gray-800 mb-4">
          Select Watches ({selectedWatches.length}/5)
        </h3>
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
          {watches.map(watch => {
            const isSelected = selectedWatches.includes(watch.id);
            const canSelect = selectedWatches.length < 5 || isSelected;

            return (
              <button
                key={watch.id}
                onClick={() => canSelect && toggleWatch(watch.id)}
                disabled={!canSelect}
                className={`p-4 rounded-lg border-2 text-left transition-all ${
                  isSelected
                    ? 'border-omega-red bg-red-50'
                    : canSelect
                    ? 'border-gray-200 hover:border-omega-red hover:bg-gray-50'
                    : 'border-gray-100 bg-gray-50 opacity-50 cursor-not-allowed'
                }`}
              >
                <div className="flex items-start justify-between">
                  <div className="flex-1">
                    <p className="font-semibold text-gray-800 text-sm">{watch.model}</p>
                    <p className="text-xs text-gray-500 mt-1">{watch.series}</p>
                  </div>
                  {isSelected && (
                    <span className="text-omega-red text-xl">✓</span>
                  )}
                </div>
              </button>
            );
          })}
        </div>
      </div>

      {/* Comparison Chart */}
      {selectedWatches.length > 0 && (
        <>
          <div className="bg-white rounded-lg shadow-md p-6">
            <h3 className="text-xl font-bold text-gray-800 mb-4">Price Comparison Chart</h3>
            <div className="h-96">
              <canvas ref={chartRef}></canvas>
            </div>
          </div>

          {/* Comparison Table */}
          <div className="bg-white rounded-lg shadow-md p-6">
            <h3 className="text-xl font-bold text-gray-800 mb-4">Performance Comparison</h3>
            <div className="overflow-x-auto">
              <table className="w-full">
                <thead>
                  <tr className="border-b-2 border-gray-300">
                    <th className="text-left py-3 px-4">Watch Model</th>
                    <th className="text-left py-3 px-4">Series</th>
                    <th className="text-right py-3 px-4">Original Price</th>
                    <th className="text-right py-3 px-4">Current Price</th>
                    <th className="text-right py-3 px-4">Total Gain</th>
                    <th className="text-right py-3 px-4">Appreciation</th>
                  </tr>
                </thead>
                <tbody>
                  {comparisonData
                    .sort((a, b) => parseFloat(b.appreciation) - parseFloat(a.appreciation))
                    .map((watch, index) => (
                      <tr key={watch.id} className="border-b hover:bg-gray-50">
                        <td className="py-3 px-4 font-semibold">{watch.model}</td>
                        <td className="py-3 px-4 text-gray-600">{watch.series}</td>
                        <td className="py-3 px-4 text-right">
                          ${watch.originalPrice.toLocaleString()}
                        </td>
                        <td className="py-3 px-4 text-right font-semibold text-omega-red">
                          ${watch.currentPrice.toLocaleString()}
                        </td>
                        <td className="py-3 px-4 text-right font-semibold text-green-600">
                          +${watch.gain.toLocaleString()}
                        </td>
                        <td className="py-3 px-4 text-right">
                          <span className={`inline-block px-3 py-1 rounded-full font-bold ${
                            index === 0
                              ? 'bg-green-100 text-green-800'
                              : 'bg-gray-100 text-gray-800'
                          }`}>
                            +{watch.appreciation}%
                          </span>
                        </td>
                      </tr>
                    ))}
                </tbody>
              </table>
            </div>
          </div>
        </>
      )}

      {selectedWatches.length === 0 && (
        <div className="bg-gray-50 rounded-lg p-12 text-center">
          <div className="text-6xl mb-4">📈</div>
          <p className="text-gray-500 text-lg">
            Select watches above to start comparing their price performance
          </p>
        </div>
      )}
    </div>
  );
}

export default ComparisonView;