SPB Git forge
28commits 1branches 0releases
7.7 MBsize
maindefault branch
10 days agolast push
Python 66.3% TypeScript 22.7% JavaScript 8.6% HTML 1.4% CSS 0.7%
2.4 KB · 42 lines tsx
Raw Blame History
1import Link from 'next/link';2import { Sparkline } from '@/components/charts/sparkline';3import { cn } from '@/lib/cn';4import { fmtPctSigned, fmtScore, num } from '@/lib/format';5import { METRIC_LABELS } from '@/lib/site';6import type { CompanyDetail, Metric, MetricDetail } from '@/lib/types';78const TILES: { metric: Metric; spark?: 'activity_30d' | 'hiring_90d'; pct?: boolean }[] = [9  { metric: 'activity_score', spark: 'activity_30d' },10  { metric: 'hiring_momentum_30d', spark: 'hiring_90d', pct: true },11  { metric: 'product_velocity' },12  { metric: 'ai_adoption' },13  { metric: 'corporate_change_index' },14];1516/**17 * Metric tiles with sparklines and confidence. A metric with no value is shown as "not enough evidence" rather than 018 * (API omits metrics without inputs — spec §165). Each tile links to the methodology.19 */20export function MetricTiles({ c, className }: { c: CompanyDetail; className?: string }) {21  const detail = new Map<string, MetricDetail>((c.metrics_detail ?? []).map((m) => [m.metric, m]));22  return (23    <div className={cn('grid grid-cols-2 gap-x-6 border-y border-rule md:grid-cols-5 [&>*]:border-b [&>*]:border-rule md:[&>*]:border-b-0', className)} data-metric-tiles>24      {TILES.map((t) => {25        const v = num(c.metrics[t.metric]);26        const d = detail.get(t.metric);27        const spark = t.spark ? c.sparklines?.[t.spark] : undefined;28        return (29          <Link key={t.metric} href={`/methodology#${t.metric}`} className="group block min-w-0 py-3 hover:text-accent md:py-4">30            <p className="eyebrow">{METRIC_LABELS[t.metric]}</p>31            <div className="mt-1 flex items-end justify-between gap-2">32              <p className={cn('tnum whitespace-nowrap text-[22px] font-semibold leading-none tracking-tight md:text-[30px]', v === null && 'text-ink-3', t.pct && v !== null && (v > 0 ? 'text-positive' : v < 0 ? 'text-danger' : ''))}>{v === null ? '—' : t.pct ? fmtPctSigned(v) : fmtScore(v)}</p>33              {spark && spark.length > 1 && <Sparkline values={spark} width={64} height={22} tone={t.pct ? 'auto' : 'accent'} className="max-[400px]:hidden" />}34            </div>35            <p className="mt-1.5 text-[11px] text-ink-3">{v === null ? 'not enough monitored evidence' : d ? `confidence ${Math.round(d.confidence * 100)} %${d.formula_version ? ` · ${d.formula_version}` : ''}` : t.pct ? 'vs 30 days ago' : '0–100'}</p>36          </Link>37        );38      })}39    </div>40  );41}42