SPB Git

spb/findata

Public
5.8 KB · 129 lines tsx
1"use client";2import Link from "next/link";3import { ArrowLeft } from "lucide-react";4import { api, type DatasetDetail } from "@/lib/api";5import { useAsync, useQueryParam } from "@/lib/hooks";6import { fmtBytes, fmtInt, fmtTs } from "@/lib/format";7import { Card, Empty, ErrorState, KV, PageHeader, Skeleton, StatusBadge } from "@/components/ui";8import { DataTable } from "@/components/DataTable";9import { JsonView } from "@/components/JsonView";1011export default function DatasetDetailPage() {12  const id = useQueryParam("id");13  const { data, error, loading, reload } = useAsync((s) => api.get<DatasetDetail>(`/catalog/datasets/${encodeURIComponent(id!)}`, undefined, s), [id], { enabled: !!id });14  const d = data?.data;1516  return (17    <>18      <Link href="/datasets/" className="mb-2 inline-flex items-center gap-1 text-xs text-ink-2 hover:text-ink-1">19        <ArrowLeft className="h-3.5 w-3.5" /> Datasets20      </Link>21      <PageHeader22        title={d?.name ?? id ?? "Dataset"}23        subtitle={<span className="font-mono">{id}</span>}24        actions={25          d && (26            <>27              <StatusBadge status={d.status} />28              {d.status === "active" && d.kind === "market_bars" && (29                <Link href={`/bars/?interval=${d.interval ?? "1m"}`} className="btn btn-sm">30                  Explore bars31                </Link>32              )}33              {d.status === "active" && (34                <Link href={`/coverage/?dataset_id=${encodeURIComponent(d.id)}`} className="btn btn-sm">35                  Coverage36                </Link>37              )}38            </>39          )40        }41      />42      {!id && <Empty>Missing <code>?id=</code> parameter.</Empty>}43      {error && <ErrorState error={error} retry={reload} />}44      {loading && !data && <Skeleton rows={8} />}45      {d && (46        <div className="grid gap-4 lg:grid-cols-2">47          <Card title="Description">48            <p className="text-sm text-ink-1 whitespace-pre-wrap">{d.description || <span className="text-ink-3">No description.</span>}</p>49            {d.notes && <p className="mt-2 text-xs text-ink-2 whitespace-pre-wrap">{d.notes}</p>}50            <KV51              className="mt-3"52              items={[53                ["Kind", d.kind],54                ["Asset class", d.asset_class ?? "—"],55                ["Interval", d.interval ?? "—"],56                ["Source", `${d.source_name ?? d.source_id ?? "—"}${d.vendor ? ` · ${d.vendor}` : ""}`],57              ]}58            />59          </Card>60          <Card title="Data contract">61            <KV62              items={[63                ["Timestamp convention", d.timestamp_convention ?? "bar_open"],64                ["Storage timezone", d.timezone ?? "UTC"],65                ["Stored adjustment", d.adjustment ?? "raw"],66                ["Current version", d.current_version !== null && d.current_version !== undefined ? `v${d.current_version}` : "—"],67                ["License class", d.license_class],68              ]}69            />70            <p className="mt-3 rounded-md border border-warn/50 bg-warn-soft p-2 text-xs text-warn-ink">{d.license_text || "License not yet determined."}</p>71          </Card>7273          <Card title={`Version history (${data.versions.length})`} className="lg:col-span-2">74            <DataTable75              rows={data.versions}76              rowKey={(v) => v.version}77              mobile="scroll"78              columns={[79                { key: "version", header: "Version", render: (v) => <span className="font-medium tnum">v{v.version}</span> },80                { key: "snapshot_id", header: "Snapshot", mono: true },81                { key: "row_count", header: "Rows", num: true, render: (v) => fmtInt(v.row_count) },82                { key: "file_count", header: "Files", num: true, render: (v) => fmtInt(v.file_count) },83                { key: "bytes", header: "Bytes", num: true, render: (v) => fmtBytes(v.bytes) },84                { key: "created_at", header: "Created", render: (v) => <span className="tnum text-xs">{fmtTs(v.created_at)}</span> },85                { key: "note", header: "Note", render: (v) => <span className="text-xs text-ink-2">{v.note ?? "—"}</span> },86              ]}87              empty="No version published yet."88            />89          </Card>9091          <Card title="Live files (current layout)">92            <DataTable93              rows={data.files}94              rowKey={(f) => f.layout}95              mobile="scroll"96              columns={[97                { key: "layout", header: "Layout", render: (f) => <span className="font-medium">{f.layout}</span> },98                { key: "n", header: "Files", num: true, render: (f) => fmtInt(f.n) },99                { key: "rows", header: "Rows", num: true, render: (f) => fmtInt(f.rows) },100                { key: "bytes", header: "Bytes", num: true, render: (f) => fmtBytes(f.bytes) },101                { key: "d0", header: "From", render: (f) => f.d0 ?? "—" },102                { key: "d1", header: "To", render: (f) => f.d1 ?? "—" },103              ]}104              empty="No files registered."105            />106          </Card>107          <Card title={`Checkpoints (${data.checkpoints.length})`}>108            {data.checkpoints.length === 0 ? (109              <Empty>No checkpoints.</Empty>110            ) : (111              <ul className="flex flex-col gap-2">112                {data.checkpoints.map((c) => (113                  <li key={c.key} className="text-sm">114                    <div className="flex items-center justify-between gap-2">115                      <code className="text-xs">{c.key}</code>116                      <span className="tnum text-xs text-ink-3">{fmtTs(c.updated_at)}</span>117                    </div>118                    <JsonView value={c.value} collapsed />119                  </li>120                ))}121              </ul>122            )}123          </Card>124        </div>125      )}126    </>127  );128}129
Files · 3f3ea623b8a62a60c5a6d2cf207e86171b79b199
Menu

Theme