← back to Omega Watches 2

src/app/(dashboard)/terminal/page.tsx

214 lines

'use client';

import { useEffect, useState, useMemo } from 'react';
import { api } from '@/lib/api-client';
import { formatCurrency, formatDate } from '@/lib/utils';
import dynamic from 'next/dynamic';

const StockChart = dynamic(() => import('@/components/charts/StockChart'), { ssr: false });
const WatchSelector = dynamic(() => import('@/components/panels/WatchSelector'), { ssr: false });
const WilliamPanel = dynamic(() => import('@/components/panels/WilliamPanel'), { ssr: false });

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;
}

export default function TerminalPage() {
  const [references, setReferences] = useState<WatchRef[]>([]);
  const [selected, setSelected] = useState<WatchRef | null>(null);
  const [compareList, setCompareList] = useState<WatchRef[]>([]);
  const [detail, setDetail] = useState<any>(null);
  const [loading, setLoading] = useState(true);
  const [chartLoading, setChartLoading] = useState(false);

  useEffect(() => {
    loadReferences();
  }, []);

  async function loadReferences() {
    try {
      const token = localStorage.getItem('omega_token');
      const res = await fetch('/api/references', {
        headers: { Authorization: `Bearer ${token}` },
      });
      const json = await res.json();
      if (json.success) {
        setReferences(json.data);
      }
    } catch {
      // Handle silently
    } finally {
      setLoading(false);
    }
  }

  async function handleSelect(ref: WatchRef) {
    setSelected(ref);
    setChartLoading(true);
    try {
      const [msrp, ts] = await Promise.all([
        api.getMSRP(ref.reference_number),
        api.getTimeseries(ref.reference_number, { include_asks: 'true' }),
      ]);
      setDetail({ msrp, timeseries: ts });
    } catch {
      setDetail(null);
    } finally {
      setChartLoading(false);
    }
  }

  function handleToggleCompare(ref: WatchRef) {
    setCompareList(prev =>
      prev.some(c => c.id === ref.id)
        ? prev.filter(c => c.id !== ref.id)
        : [...prev, ref]
    );
  }

  // Build chart data from detail
  const chartData = useMemo(() => {
    if (!detail) return [];
    const points: any[] = [];
    (detail.timeseries?.msrp_history || []).forEach((m: any) => {
      points.push({ date: m.date, msrp: parseFloat(m.value), source: 'MSRP' });
    });
    (detail.timeseries?.market_events || []).forEach((e: any) => {
      points.push({
        date: e.date,
        price: parseFloat(e.value),
        event_type: e.event_type,
        source: e.source,
      });
    });
    points.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
    const merged: any[] = [];
    let lastMsrp: number | undefined;
    for (const p of points) {
      if (p.msrp) lastMsrp = p.msrp;
      merged.push({ ...p, msrp: p.msrp || lastMsrp });
    }
    return merged;
  }, [detail]);

  // Stats
  const stats = useMemo(() => {
    if (!selected || !detail) return null;
    const msrp = selected.latest_msrp ? parseFloat(selected.latest_msrp) : null;
    const avg = selected.avg_market_price ? parseFloat(selected.avg_market_price) : null;
    const lastSale = selected.latest_sale_price ? parseFloat(selected.latest_sale_price) : null;
    const premium = msrp && avg ? ((avg - msrp) / msrp) * 100 : null;
    const events = parseInt(selected.event_count) || 0;
    const high = chartData.length > 0 ? Math.max(...chartData.filter(d => d.price).map(d => d.price!)) : null;
    const low = chartData.length > 0 ? Math.min(...chartData.filter(d => d.price).map(d => d.price!)) : null;
    return { msrp, avg, lastSale, premium, events, high, low };
  }, [selected, detail, chartData]);

  if (loading) {
    return (
      <div className="flex h-screen items-center justify-center">
        <div className="text-gray-400">Loading terminal...</div>
      </div>
    );
  }

  return (
    <div className="flex h-[calc(100vh-2rem)] gap-3">
      {/* Left — Watch Selector */}
      <div className="w-72 flex-shrink-0">
        <WatchSelector
          references={references}
          selected={selected}
          compareList={compareList}
          onSelect={handleSelect}
          onToggleCompare={handleToggleCompare}
        />
      </div>

      {/* Center — Chart + Stats */}
      <div className="flex min-w-0 flex-1 flex-col gap-3">
        {/* Stats bar */}
        {stats && selected && (
          <div className="grid grid-cols-7 gap-2">
            {[
              { label: 'MSRP', value: stats.msrp ? formatCurrency(stats.msrp) : '—', color: 'text-red-400' },
              { label: 'Avg Market', value: stats.avg ? formatCurrency(stats.avg) : '—', color: 'text-omega-400' },
              { label: 'Last Sale', value: stats.lastSale ? formatCurrency(stats.lastSale) : '—', color: 'text-green-400' },
              { label: 'Premium', value: stats.premium !== null ? `${stats.premium >= 0 ? '+' : ''}${stats.premium.toFixed(1)}%` : '—', color: stats.premium && stats.premium >= 0 ? 'text-green-400' : 'text-red-400' },
              { label: 'High', value: stats.high ? formatCurrency(stats.high) : '—', color: 'text-gray-200' },
              { label: 'Low', value: stats.low ? formatCurrency(stats.low) : '—', color: 'text-gray-200' },
              { label: 'Events', value: `${stats.events}`, color: 'text-blue-400' },
            ].map((s, i) => (
              <div key={i} className="rounded-lg border border-navy-700 bg-navy-800/40 px-3 py-2 text-center">
                <div className="text-[10px] uppercase text-gray-500">{s.label}</div>
                <div className={`mt-0.5 text-sm font-bold ${s.color}`}>{s.value}</div>
              </div>
            ))}
          </div>
        )}

        {/* Chart */}
        <div className="flex-1">
          {chartLoading ? (
            <div className="flex h-full items-center justify-center rounded-xl border border-navy-700 bg-navy-800/30">
              <div className="text-gray-400">Loading chart data...</div>
            </div>
          ) : (
            <StockChart
              data={chartData}
              title={selected ? selected.model_name : undefined}
              reference={selected?.reference_number}
            />
          )}
        </div>

        {/* Bottom stats — recent events */}
        {detail?.timeseries?.market_events?.length > 0 && (
          <div className="rounded-xl border border-navy-700 bg-navy-800/30 p-3">
            <h4 className="mb-2 text-xs font-semibold uppercase text-gray-400">Recent Transactions</h4>
            <div className="flex gap-2 overflow-x-auto">
              {detail.timeseries.market_events.slice(-8).reverse().map((ev: any, i: number) => (
                <div key={i} className="flex-shrink-0 rounded-lg border border-navy-700 bg-navy-900 px-3 py-2 text-center">
                  <div className="text-[10px] text-gray-500">{ev.source}</div>
                  <div className="mt-0.5 font-mono text-sm text-gray-200">
                    {ev.value ? formatCurrency(parseFloat(ev.value)) : '—'}
                  </div>
                  <div className="text-[10px] text-gray-500">{ev.date ? formatDate(ev.date) : ''}</div>
                  <span className={`mt-0.5 inline-block rounded px-1.5 py-0.5 text-[9px] font-medium ${
                    ev.event_type === 'auction_realized' ? 'bg-green-900/30 text-green-400' :
                    ev.event_type === 'dealer_ask' ? 'bg-amber-900/30 text-amber-400' :
                    'bg-blue-900/30 text-blue-400'
                  }`}>
                    {ev.event_type?.replace(/_/g, ' ')}
                  </span>
                </div>
              ))}
            </div>
          </div>
        )}
      </div>

      {/* Right — William Panel */}
      <div className="w-72 flex-shrink-0">
        <WilliamPanel />
      </div>
    </div>
  );
}