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