← back to Omega Watches 2

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

111 lines

"use client";

import { useEffect, useState } from "react";
import { api } from "@/lib/api-client";
import { SourceReliabilityChart } from "@/components/charts";

export default function SourceCatalogPage() {
  const [sources, setSources] = useState<any[]>([]);
  const [selected, setSelected] = useState<any>(null);
  const [detail, setDetail] = useState<any>(null);
  const [error, setError] = useState("");

  useEffect(() => {
    api.getSources().then(setSources).catch((e) => setError(e.message));
  }, []);

  async function loadDetail(name: string) {
    try {
      const d = await api.getSourceStatus(name);
      setDetail(d);
    } catch (e: any) {
      setError(e.message);
    }
  }

  return (
    <div className="space-y-6">
      <h1 className="text-xl font-bold text-gray-100">Source Catalog</h1>
      {error && <div className="rounded bg-red-900/30 p-3 text-red-300">{error}</div>}

      {sources.length > 0 && (
        <SourceReliabilityChart
          data={sources
            .filter((s: any) => s.total_runs > 0)
            .map((s: any) => ({
              name: s.display_name,
              success_rate: parseFloat(s.success_rate_pct) || 0,
              total_runs: parseInt(s.total_runs) || 0,
              records: parseInt(s.total_records_inserted) || 0,
            }))
            .sort((a: any, b: any) => b.success_rate - a.success_rate)}
          title="Source Reliability Comparison"
        />
      )}

      <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
        {sources.map((s) => (
          <div
            key={s.id}
            onClick={() => { setSelected(s); loadDetail(s.name); }}
            className={`card cursor-pointer border-navy-700 transition-colors hover:border-omega-500/50 ${selected?.id === s.id ? "ring-1 ring-omega-500" : ""}`}
          >
            <div className="flex items-start justify-between">
              <div>
                <div className="font-medium text-gray-200">{s.display_name}</div>
                <div className="text-xs text-gray-500">{s.source_type} | {s.access_method}</div>
              </div>
              <span className={`badge ${s.is_active ? "badge-success" : "badge-error"}`}>
                {s.is_active ? "Active" : "Off"}
              </span>
            </div>
            <div className="mt-3 flex items-center gap-1">
              {[1, 2, 3, 4, 5].map((n) => (
                <span key={n} className={`inline-block h-1.5 w-4 rounded-full ${n <= s.reliability_score ? "bg-omega-400" : "bg-navy-700"}`} />
              ))}
              <span className="ml-2 text-xs text-gray-500">{s.reliability_score}/5</span>
            </div>
            {s.tos_notes && (
              <div className="mt-2 text-xs text-gray-500 line-clamp-2">{s.tos_notes}</div>
            )}
            <div className="mt-2 text-xs text-gray-600">
              Coverage: {s.coverage_years_from || "?"} - {s.coverage_years_to || "?"}
            </div>
          </div>
        ))}
      </div>

      {detail && (
        <div className="card border-navy-700">
          <h2 className="mb-3 text-lg font-semibold text-gray-200">
            {detail.source.display_name} — Detail
          </h2>
          <div className="grid gap-4 md:grid-cols-3">
            <div>
              <div className="text-xs text-gray-500">Base URL</div>
              <div className="text-sm text-gray-300">{detail.source.base_url || "N/A"}</div>
            </div>
            <div>
              <div className="text-xs text-gray-500">Rate Limit</div>
              <div className="text-sm text-gray-300">{detail.source.rate_limit_rpm || "None"} rpm</div>
            </div>
            <div>
              <div className="text-xs text-gray-500">Current Parser</div>
              <div className="text-sm text-gray-300">{detail.current_parser?.version || "None"}</div>
            </div>
          </div>
          <div className="mt-4">
            <div className="text-xs text-gray-500">Event Counts</div>
            <div className="mt-1 flex gap-4 text-sm">
              <span className="text-gray-300">Total: {detail.event_counts?.total_events || 0}</span>
              <span className="text-green-400">Auctions: {detail.event_counts?.auction_count || 0}</span>
              <span className="text-blue-400">Marketplace: {detail.event_counts?.marketplace_count || 0}</span>
              <span className="text-yellow-400">Quarantined: {detail.event_counts?.quarantined_count || 0}</span>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}