spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
1"use client";23import { useEffect, useRef, useState } from "react";4import { clientApi } from "@/lib/client-api";5import { cx, formatCompact, formatDuration, formatInt } from "@/lib/format";6import type { Stats } from "@/lib/types";78function Counter({ value, format }: { value: number; format: (n: number) => string }) {9 const [display, setDisplay] = useState(value);10 const from = useRef(value);11 useEffect(() => {12 const start = from.current;13 const delta = value - start;14 if (!delta) return;15 const t0 = performance.now();16 let raf = 0;17 const step = (t: number) => {18 const k = Math.min(1, (t - t0) / 600);19 setDisplay(start + delta * (1 - (1 - k) ** 3));20 if (k < 1) raf = requestAnimationFrame(step);21 else from.current = value;22 };23 raf = requestAnimationFrame(step);24 return () => cancelAnimationFrame(raf);25 }, [value]);26 return <span suppressHydrationWarning>{format(display)}</span>;27}2829/** Homepage telemetry — every number comes from GET /v1/stats (refreshed every 5 s). */30export function TelemetryStrip({ initial, className }: { initial: Stats; className?: string }) {31 const [s, setS] = useState(initial);32 useEffect(() => {33 let alive = true;34 const t = setInterval(() => clientApi<Stats>("/v1/stats").then((x) => alive && setS(x)).catch(() => {}), 5000);35 return () => {36 alive = false;37 clearInterval(t);38 };39 }, []);40 const items: Array<{ label: string; value: number; format: (n: number) => string; sub?: string }> = [41 { label: "Instruments", value: s.instruments_total, format: formatInt, sub: `${formatInt(s.instruments_quoted)} quoted · ${formatInt(s.instruments_live)} live` },42 { label: "Exchanges", value: s.exchanges_total, format: formatInt, sub: `${s.exchanges_open} open now` },43 { label: "Active connectors", value: s.connectors_healthy, format: formatInt, sub: `${s.connectors_total} connectors · ${s.sources_total} sources` },44 { label: "Observations today", value: s.observations_today, format: (n) => formatCompact(n, 2), sub: `${s.observations_per_sec.toFixed(1)} / sec` },45 { label: "Events · 24h", value: s.events_24h, format: formatInt, sub: `${s.filings_24h} filings` },46 { label: "Median freshness", value: s.median_freshness_ms ?? 0, format: (n) => (s.median_freshness_ms == null ? "—" : formatDuration(n)), sub: "real-time quotes" },47 ];48 return (49 <div className={cx("grid grid-cols-2 gap-x-4 gap-y-4 rounded-md border border-rule bg-surface px-4 py-4 sm:grid-cols-3 lg:grid-cols-6", className)}>50 {items.map((it) => (51 <div key={it.label} className="min-w-0">52 <div className="text-[10.5px] font-medium uppercase tracking-wide text-ink-3">{it.label}</div>53 <div className="mono mt-0.5 text-xl font-semibold tracking-tight tnum sm:text-2xl">54 <Counter value={it.value} format={it.format} />55 </div>56 {it.sub && <div className="mt-0.5 truncate text-[11px] text-ink-3">{it.sub}</div>}57 </div>58 ))}59 </div>60 );61}62