1"use client";2import Link from "next/link";3import { ArrowLeft } from "lucide-react";4import { api, type InstrumentDetail } from "@/lib/api";5import { useAsync, useQueryParam } from "@/lib/hooks";6import { fmtCompact, fmtInt } 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 SymbolDetailPage() {12 const symbol = useQueryParam("symbol")?.toUpperCase() ?? null;13 const { data, error, loading, reload } = useAsync((s) => api.get<InstrumentDetail>(`/instruments/${encodeURIComponent(symbol!)}`, undefined, s), [symbol], { enabled: !!symbol });14 const inst = data?.instrument;1516 return (17 <>18 <Link href="/symbols/" 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" /> Symbols20 </Link>21 <PageHeader22 title={symbol ?? "Symbol"}23 subtitle={inst?.name ?? (data && !inst ? "Unknown instrument (no master record) — coverage may still exist" : undefined)}24 actions={25 symbol && (26 <>27 {inst && <StatusBadge status={inst.status} />}28 <Link href={`/bars/?symbol=${encodeURIComponent(symbol)}`} className="btn btn-sm btn-primary">29 Bars30 </Link>31 <Link href={`/coverage/?symbol=${encodeURIComponent(symbol)}`} className="btn btn-sm">32 Sessions33 </Link>34 <Link href={`/fundamentals/?ticker=${encodeURIComponent(symbol)}`} className="btn btn-sm">35 Fundamentals36 </Link>37 <Link href={`/playground/?symbol=${encodeURIComponent(symbol)}`} className="btn btn-sm">38 SQL39 </Link>40 </>41 )42 }43 />44 {!symbol && <Empty>Missing <code>?symbol=</code> parameter.</Empty>}45 {error && <ErrorState error={error} retry={reload} />}46 {loading && !data && <Skeleton rows={8} />}47 {data && (48 <div className="grid gap-4 lg:grid-cols-2">49 <Card title="Instrument">50 {inst ? (51 <KV52 items={[53 ["Instrument id", <code key="i" className="text-xs">{inst.instrument_id}</code>],54 ["Asset class", inst.asset_class],55 ["Currency", inst.currency ?? "—"],56 ["First date", inst.first_date ?? "—"],57 ["Last date", inst.last_date ?? "—"],58 ...Object.entries(inst)59 .filter(([k]) => !["instrument_id", "symbol", "name", "asset_class", "status", "first_date", "last_date", "currency"].includes(k))60 .map(([k, v]) => [k, typeof v === "object" && v !== null ? <JsonView key={k} value={v} collapsed /> : String(v ?? "—")] as [string, React.ReactNode]),61 ]}62 />63 ) : (64 <Empty>No master record.</Empty>65 )}66 </Card>67 <Card title="Fundamentals">68 {data.fundamentals && data.fundamentals.facts > 0 ? (69 <KV70 items={[71 ["Facts (current)", fmtInt(data.fundamentals.facts)],72 ["Last fiscal period", data.fundamentals.last_period ?? "—"],73 ["Last filing", data.fundamentals.last_filing ?? "—"],74 ]}75 />76 ) : (77 <Empty>78 No fundamentals ingested for <code>frd:{symbol}</code>.79 </Empty>80 )}81 </Card>82 <Card title={`Coverage per dataset (${data.coverage.length})`} className="lg:col-span-2">83 <DataTable84 rows={data.coverage}85 rowKey={(c) => c.dataset_id}86 mobile="scroll"87 columns={[88 {89 key: "dataset_id",90 header: "Dataset",91 render: (c) => (92 <Link href={`/datasets/detail/?id=${encodeURIComponent(c.dataset_id)}`} className="font-mono text-xs text-accent-ink hover:underline">93 {c.dataset_id}94 </Link>95 ),96 },97 { key: "sessions", header: "Sessions", num: true, render: (c) => fmtInt(c.sessions) },98 { key: "bars", header: "Bars", num: true, render: (c) => <span title={fmtInt(c.bars)}>{fmtCompact(c.bars)}</span> },99 { key: "d0", header: "From" },100 { key: "d1", header: "To" },101 ]}102 empty="No bars for this symbol yet."103 />104 </Card>105 <Card title={`Corporate actions (${data.corporate_actions.length})`}>106 <DataTable107 rows={data.corporate_actions}108 rowKey={(a, i) => `${a.action_type}-${a.ex_date}-${i}`}109 mobile="scroll"110 maxHeight="24rem"111 columns={[112 { key: "ex_date", header: "Ex-date", render: (a) => <span className="tnum">{a.ex_date}</span> },113 { key: "action_type", header: "Type", render: (a) => <StatusBadge status="neutral" label={a.action_type} icon={false} /> },114 { key: "value", header: "Value", render: (a) => <JsonView value={a.value} collapsed /> },115 ]}116 empty="No splits or dividends recorded."117 />118 </Card>119 <Card title={`Aliases (${data.aliases.length})`}>120 {data.aliases.length === 0 ? (121 <Empty>No aliases.</Empty>122 ) : (123 <ul className="flex flex-col gap-1">124 {data.aliases.map((a, i) => (125 <li key={i}>126 <JsonView value={a} collapsed />127 </li>128 ))}129 </ul>130 )}131 </Card>132 </div>133 )}134 </>135 );136}137