SPB Git

spb/findata

Public
4.5 KB · 95 lines tsx
1"use client";2import Link from "next/link";3import { useMemo, useState } from "react";4import clsx from "clsx";5import { api, type Dataset } from "@/lib/api";6import { useAsync } from "@/lib/hooks";7import { fmtBytes, fmtCompact, fmtInt } from "@/lib/format";8import { Card, ErrorState, PageHeader, Skeleton, StatusBadge, Tabs } from "@/components/ui";910const ORDER = ["active", "paused", "pending_configuration", "planned", "unsupported"];1112export default function DatasetsPage() {13  const { data, error, loading, reload } = useAsync((s) => api.get<{ data: Dataset[] }>("/catalog/datasets", undefined, s), []);14  const [filter, setFilter] = useState<"all" | "active" | "other">("all");15  const rows = useMemo(() => {16    const r = (data?.data ?? []).slice().sort((a, b) => ORDER.indexOf(a.status) - ORDER.indexOf(b.status) || a.id.localeCompare(b.id));17    if (filter === "active") return r.filter((d) => d.status === "active");18    if (filter === "other") return r.filter((d) => d.status !== "active");19    return r;20  }, [data, filter]);2122  return (23    <>24      <PageHeader25        title="Datasets"26        subtitle="Catalog of curated datasets, their contract and current version"27        actions={28          <Tabs29            value={filter}30            onChange={setFilter}31            items={[32              { value: "all", label: `All (${data?.data.length ?? 0})` },33              { value: "active", label: "Active" },34              { value: "other", label: "Planned / other" },35            ]}36          />37        }38      />39      {error && <ErrorState error={error} retry={reload} />}40      {loading && !data && <Skeleton rows={6} />}41      <div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">42        {rows.map((d) => (43          <Link key={d.id} href={`/datasets/detail/?id=${encodeURIComponent(d.id)}`} className={clsx("card flex flex-col gap-2 transition hover:border-accent/60", d.status !== "active" && "opacity-90")}>44            <div className="flex items-start justify-between gap-2">45              <div className="min-w-0">46                <div className="truncate font-semibold">{d.name}</div>47                <div className="truncate font-mono text-xs text-ink-3">{d.id}</div>48              </div>49              <StatusBadge status={d.status} />50            </div>51            {d.description && <p className="line-clamp-2 text-xs text-ink-2">{d.description}</p>}52            <div className="flex flex-wrap gap-1 text-[11px]">53              <span className="pill bg-surface-2 text-ink-2">{d.kind}</span>54              {d.asset_class && <span className="pill bg-surface-2 text-ink-2">{d.asset_class}</span>}55              {d.interval && <span className="pill bg-surface-2 text-ink-2">{d.interval}</span>}56              {d.adjustment && <span className="pill bg-surface-2 text-ink-2">{d.adjustment}</span>}57              <span className={clsx("pill", d.license_class === "public-domain" ? "bg-ok-soft text-ok-ink" : d.license_class === "pending" ? "bg-warn-soft text-warn-ink" : "bg-accent-soft text-accent-ink")} title={d.license_text}>58                {d.license_class}59              </span>60            </div>61            {d.status === "active" && (62              <dl className="mt-1 grid grid-cols-3 gap-2 text-xs">63                <div>64                  <dt className="text-ink-3">Version</dt>65                  <dd className="tnum">v{d.current_version ?? "—"}</dd>66                </div>67                <div>68                  <dt className="text-ink-3">Rows</dt>69                  <dd className="tnum" title={fmtInt(d.row_count)}>70                    {fmtCompact(d.row_count)}71                  </dd>72                </div>73                <div>74                  <dt className="text-ink-3">Size</dt>75                  <dd className="tnum">{fmtBytes(d.bytes)}</dd>76                </div>77                <div>78                  <dt className="text-ink-3">Symbols</dt>79                  <dd className="tnum">{fmtInt(d.symbols)}</dd>80                </div>81                <div className="col-span-2">82                  <dt className="text-ink-3">Sessions</dt>83                  <dd className="tnum">{d.first_session && d.last_session ? `${d.first_session} → ${d.last_session}` : "—"}</dd>84                </div>85              </dl>86            )}87            {d.source_name && <div className="text-[11px] text-ink-3">Source: {d.source_name}{d.vendor ? ` (${d.vendor})` : ""}</div>}88          </Link>89        ))}90      </div>91      {data && rows.length === 0 && <Card>No dataset matches this filter.</Card>}92    </>93  );94}95
Files · f85ef07ccaee074d0f21ee15a0887b71bc6ad458
Menu

Theme