import Link from 'next/link'; import { Sparkline } from '@/components/charts/sparkline'; import { CompanyTierBadge, CountryChip } from '@/components/ui/badges'; import { LiveAgo } from '@/components/ui/live'; import { cn } from '@/lib/cn'; import { fmtInt, fmtPctSigned, fmtScore, num } from '@/lib/format'; import { descriptionOf, logoCandidates } from '@/lib/profile'; import { routes } from '@/lib/site'; import type { CompanyCard } from '@/lib/types'; import { CompanyLogo } from './company-logo'; /** * Directory rows: dense table on ≥ md (logo · name + domain + one-line description · country · industry · activity · * hiring 30d · AI · events · sensors · sparkline · last event) and stacked cards below. Missing metrics render as a dash, * never 0. Logos are lazy with a monogram fallback; the description is the attributed profile paragraph when present. */ export function CompanyTable({ items, className, rank = false, showSparkline = true, showDescription = true, valueLabel, formatValue, formatDelta }: { items: (CompanyCard & { rank?: number; value?: number; delta?: number | null })[]; className?: string; rank?: boolean; showSparkline?: boolean; showDescription?: boolean; valueLabel?: string; formatValue?: (c: CompanyCard & { value?: number }) => string; formatDelta?: (c: CompanyCard & { delta?: number | null }) => string | null }) { if (!items.length) return

No companies match these filters yet.

; return (
{/* desktop */}
{rank && } {valueLabel && } {valueLabel && formatDelta && } {showSparkline && } {items.map((c) => { const h = num(c.metrics.hiring_momentum_30d); const d = formatDelta?.(c) ?? null; const desc = showDescription ? descriptionOf(c)?.text : null; return ( {rank && } {valueLabel && } {valueLabel && formatDelta && } {showSparkline && ( )} ); })}
#Company Country Industry{valueLabel}ΔActivity Hiring 30d AI Events Sensors30 dLast event
{c.rank} {c.display_name} {c.canonical_domain} {desc && ( · {desc} )} {c.industry_primary ? {c.industry_primary.replace(/-/g, ' ')} : '—'}{formatValue ? formatValue(c) : fmtScore(c.value)}{d ?? '—'}{fmtScore(c.metrics.activity_score)} 0 ? 'text-positive' : h < 0 ? 'text-danger' : ''))}>{h === null ? '—' : fmtPctSigned(h)} {fmtScore(c.metrics.ai_adoption)} {fmtInt(c.counts.events)} {fmtInt(c.counts.sensors)}
{/* mobile cards */}
); } /** Compact list of companies (logo · name · country · one metric) for homepage sections and side panels. */ export function CompanyMiniList({ items, metric = 'activity_score', label, format, className, sparkline = false }: { items: CompanyCard[]; metric?: keyof CompanyCard['metrics']; label?: string; format?: (v: number | null) => string; className?: string; sparkline?: boolean }) { if (!items.length) return

No monitored evidence available yet.

; const fmt = format ?? ((v: number | null) => (v === null ? '—' : metric.startsWith('hiring') ? fmtPctSigned(v) : fmtScore(v))); return (
    {items.map((c, i) => { const v = num(c.metrics[metric]); return (
  1. {i + 1} {c.display_name} {c.canonical_domain} {c.country ? ` · ${c.country}` : ''} {sparkline && } 0 ? 'text-positive' : v < 0 ? 'text-danger' : ''))}>{fmt(v)} {label && {label}}
  2. ); })}
); }