SPB Git forge

spb/market-atlas

Public
12commits 1branches 0releases
1.1 MBsize
maindefault branch
10 days agolast push
TypeScript 96.7% SQL 1.6% CSS 0.8% JavaScript 0.5%
3.8 KB · 90 lines tsx
Raw Blame History
1"use client";23import Link from "next/link";4import { ConfidenceMeter } from "@/components/ui/confidence";5import { FreshnessLabel } from "@/components/ui/freshness";6import { LivePrice } from "@/components/ui/price";7import { StatusBadge } from "@/components/ui/status-badge";8import { ASSET_CLASS_LABEL } from "@/lib/format";9import { useLiveQuote, useMarketStream } from "@/lib/stream";10import type { Exchange, Instrument, InstrumentCoverage, Quote } from "@/lib/types";11import { CoverageBadge } from "@/components/market/coverage-badge";1213/** Sticky quote header: symbol, name, live price + change, status badge, freshness, confidence. */14export function InstrumentHeader({ instrument: i, quote, exchange, coverage }: { instrument: Instrument; quote: Quote | null; exchange: (Exchange & { status?: Exchange["status"] }) | null; coverage?: InstrumentCoverage }) {15  useMarketStream([`quotes:${i.id}`, `events:${i.id}`]);16  const live = useLiveQuote(i.id);17  const sources = live && quote && live.received >= Date.parse(quote.updated_at) ? live.sources : quote?.source_count;18  const conf = live && quote && live.received >= Date.parse(quote.updated_at) ? live.confidence : quote?.confidence;19  return (20    <div className="sticky top-[var(--header-h)] z-[60] border-b border-rule bg-canvas/95 backdrop-blur md:top-[calc(var(--header-h)+29px)]">21      <div className="mx-auto flex max-w-[1440px] flex-wrap items-end justify-between gap-x-6 gap-y-2 px-3 py-3 sm:px-5">22        <div className="min-w-0">23          <div className="flex flex-wrap items-center gap-2 text-xs text-ink-3">24            <Link href={classHref(i.asset_class)} className="hover:text-ink">25              {ASSET_CLASS_LABEL[i.asset_class] ?? i.asset_class}26            </Link>27            {exchange && (28              <>29                <span>·</span>30                <Link href={`/exchanges/${exchange.id}`} className="hover:text-ink">31                  {exchange.name}32                </Link>33                {exchange.status && <StatusBadge status={exchange.status.state} />}34              </>35            )}36            {i.country && i.country !== "XX" && (37              <>38                <span>·</span>39                <Link href={`/countries/${i.country}`} className="hover:text-ink">40                  {i.country}41                </Link>42              </>43            )}44          </div>45          <h1 className="mt-0.5 flex flex-wrap items-baseline gap-x-3">46            <span className="mono text-2xl font-semibold tracking-tight sm:text-3xl">{i.symbol}</span>47            <span className="truncate text-base text-ink-2 sm:text-lg">{i.name}</span>48          </h1>49        </div>50        <div className="flex flex-col items-start gap-1 sm:items-end">51          <div className="flex items-center gap-3">52            <LivePrice instrumentId={i.id} quote={quote} assetClass={i.asset_class} big />53            <StatusBadge status={quote?.data_status ?? "UNKNOWN"} dot />54          </div>55          <div className="flex flex-wrap items-center gap-3">56            <FreshnessLabel quote={quote} exchangeTz={exchange?.timezone} />57            <ConfidenceMeter confidence={conf} sources={sources} proxies={quote?.proxy_count} validators={quote?.validator_count} />58            {coverage && <CoverageBadge coverage={coverage} compact className="hidden sm:inline-flex" />}59          </div>60        </div>61      </div>62    </div>63  );64}6566function classHref(c: string) {67  switch (c) {68    case "EQUITY":69      return "/stocks";70    case "ETF":71    case "ETN":72      return "/etfs";73    case "INDEX":74      return "/indices";75    case "CRYPTO":76      return "/crypto";77    case "FOREX":78      return "/forex";79    case "TREASURY":80    case "BOND":81    case "INTEREST_RATE":82      return "/rates";83    case "COMMODITY":84    case "FUTURE":85      return "/commodities";86    default:87      return "/markets";88  }89}90