← back to Omega Watches 2

src/components/panels/WatchSelector.tsx

222 lines

'use client';

import { useState, useMemo } from 'react';
import { formatCurrency } from '@/lib/utils';

interface WatchRef {
  id: string;
  brand: string;
  collection: string;
  model_name: string;
  reference_number: string;
  calibre: string;
  case_material: string;
  case_diameter_mm: string;
  dial_color: string;
  year_introduced: number;
  notes?: string;
  event_count: string;
  msrp_count: string;
  latest_msrp: string | null;
  avg_market_price: string | null;
  latest_sale_price: string | null;
  latest_sale_date: string | null;
}

interface WatchSelectorProps {
  references: WatchRef[];
  selected: WatchRef | null;
  compareList: WatchRef[];
  onSelect: (ref: WatchRef) => void;
  onToggleCompare: (ref: WatchRef) => void;
}

const COLLECTION_ICONS: Record<string, string> = {
  Speedmaster: 'S',
  Seamaster: 'W',
  Constellation: 'C',
  'De Ville': 'D',
  Vintage: 'V',
};

function Sparkline({ prices, color = '#c89b3c' }: { prices: number[]; color?: string }) {
  if (prices.length < 2) return null;
  const min = Math.min(...prices);
  const max = Math.max(...prices);
  const range = max - min || 1;
  const w = 60;
  const h = 20;
  const points = prices.map((p, i) => {
    const x = (i / (prices.length - 1)) * w;
    const y = h - ((p - min) / range) * h;
    return `${x},${y}`;
  }).join(' ');

  return (
    <svg width={w} height={h} className="inline-block">
      <polyline
        points={points}
        fill="none"
        stroke={color}
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

export default function WatchSelector({ references, selected, compareList, onSelect, onToggleCompare }: WatchSelectorProps) {
  const [search, setSearch] = useState('');
  const [expandedCollection, setExpandedCollection] = useState<string | null>(null);

  const grouped = useMemo(() => {
    const groups: Record<string, WatchRef[]> = {};
    for (const ref of references) {
      const col = ref.collection || 'Other';
      if (!groups[col]) groups[col] = [];
      groups[col].push(ref);
    }
    return groups;
  }, [references]);

  const filtered = useMemo(() => {
    if (!search) return null;
    const s = search.toLowerCase();
    return references.filter(r =>
      r.reference_number.toLowerCase().includes(s) ||
      r.model_name.toLowerCase().includes(s) ||
      r.collection.toLowerCase().includes(s) ||
      r.dial_color?.toLowerCase().includes(s)
    );
  }, [search, references]);

  const isCompared = (ref: WatchRef) => compareList.some(c => c.id === ref.id);

  const renderRef = (ref: WatchRef) => {
    const msrp = ref.latest_msrp ? parseFloat(ref.latest_msrp) : null;
    const avg = ref.avg_market_price ? parseFloat(ref.avg_market_price) : null;
    const premium = msrp && avg ? ((avg - msrp) / msrp) * 100 : null;
    const isActive = selected?.id === ref.id;

    return (
      <div
        key={ref.id}
        className={`group cursor-pointer rounded-lg border p-2.5 transition-all ${
          isActive
            ? 'border-omega-500/50 bg-omega-500/10'
            : 'border-transparent hover:border-navy-600 hover:bg-navy-800/50'
        }`}
        onClick={() => onSelect(ref)}
      >
        <div className="flex items-start justify-between">
          <div className="min-w-0 flex-1">
            <div className="flex items-center gap-1.5">
              <span className="font-mono text-xs text-omega-400">{ref.reference_number}</span>
              {isCompared(ref) && (
                <span className="rounded bg-blue-500/20 px-1 text-[9px] font-medium text-blue-400">CMP</span>
              )}
            </div>
            <div className="mt-0.5 truncate text-sm font-medium text-gray-200">{ref.model_name}</div>
            <div className="mt-0.5 flex items-center gap-2 text-[11px] text-gray-500">
              <span>{ref.case_diameter_mm}mm</span>
              <span>{ref.dial_color}</span>
              <span>{ref.event_count} events</span>
            </div>
          </div>
          <div className="ml-2 text-right">
            {avg && (
              <div className="text-sm font-medium text-gray-200">{formatCurrency(avg)}</div>
            )}
            {premium !== null && (
              <div className={`text-[11px] font-medium ${premium >= 0 ? 'text-green-400' : 'text-red-400'}`}>
                {premium >= 0 ? '+' : ''}{premium.toFixed(1)}%
              </div>
            )}
          </div>
        </div>

        {/* Compare toggle on hover */}
        <button
          onClick={(e) => { e.stopPropagation(); onToggleCompare(ref); }}
          className={`mt-1.5 w-full rounded py-0.5 text-[10px] font-medium transition-colors ${
            isCompared(ref)
              ? 'bg-blue-500/20 text-blue-400 hover:bg-red-500/20 hover:text-red-400'
              : 'bg-navy-700/50 text-gray-500 opacity-0 group-hover:opacity-100 hover:text-white'
          }`}
        >
          {isCompared(ref) ? 'Remove from compare' : '+ Compare'}
        </button>
      </div>
    );
  };

  return (
    <div className="flex h-full flex-col rounded-xl border border-navy-700 bg-navy-800/30">
      {/* Header */}
      <div className="border-b border-navy-700 p-3">
        <h3 className="mb-2 text-xs font-semibold uppercase tracking-wider text-gray-400">
          Watch Selector
        </h3>
        <input
          type="text"
          placeholder="Search models, refs..."
          value={search}
          onChange={e => setSearch(e.target.value)}
          className="w-full rounded-md border border-navy-600 bg-navy-900 px-3 py-1.5 text-sm text-gray-200 placeholder-gray-500 outline-none focus:border-omega-500"
        />
        {compareList.length > 0 && (
          <div className="mt-2 text-[11px] text-blue-400">
            Comparing {compareList.length} model{compareList.length > 1 ? 's' : ''}
          </div>
        )}
      </div>

      {/* Tree / search results */}
      <div className="flex-1 overflow-y-auto p-2">
        {filtered ? (
          <div className="space-y-1">
            {filtered.length === 0 ? (
              <p className="py-4 text-center text-xs text-gray-500">No matches found</p>
            ) : (
              filtered.map(renderRef)
            )}
          </div>
        ) : (
          <div className="space-y-1">
            {Object.entries(grouped).map(([collection, refs]) => (
              <div key={collection}>
                <button
                  className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs font-medium text-gray-300 hover:bg-navy-800"
                  onClick={() => setExpandedCollection(expandedCollection === collection ? null : collection)}
                >
                  <span className={`flex h-5 w-5 items-center justify-center rounded text-[10px] font-bold ${
                    expandedCollection === collection ? 'bg-omega-500 text-white' : 'bg-navy-700 text-gray-400'
                  }`}>
                    {COLLECTION_ICONS[collection] || collection[0]}
                  </span>
                  <span className="flex-1">{collection}</span>
                  <span className="text-[10px] text-gray-500">{refs.length}</span>
                  <span className="text-gray-500">{expandedCollection === collection ? '▾' : '▸'}</span>
                </button>
                {expandedCollection === collection && (
                  <div className="ml-2 mt-1 space-y-1 border-l border-navy-700 pl-2">
                    {refs.map(renderRef)}
                  </div>
                )}
              </div>
            ))}
          </div>
        )}
      </div>

      {/* Summary footer */}
      <div className="border-t border-navy-700 p-2.5 text-center">
        <div className="text-[11px] text-gray-500">
          {references.length} references across {Object.keys(grouped).length} collections
        </div>
      </div>
    </div>
  );
}