import Link from 'next/link'; import type { PortfolioSummary, ValuedItem } from '@/lib/account/portfolio'; import type { Display } from '@/lib/account/display'; import { Badge, Card, CardHeader, Delta, Stat } from '@/components/ui/primitives'; import { Donut, Bars } from './charts'; import { cn, confidenceLabel, fmtPct } from '@/lib/format'; export function SummaryStats({ s, d, className }: { s: PortfolioSummary; d: Display; className?: string }) { return (
{s.valuedCount}/{s.itemCount} items valued · confidence {confidenceLabel(s.confidence)} : `${s.itemCount} item${s.itemCount === 1 ? '' : 's'} · no valuation yet`} /> 0 ? d.money(s.costBasisUsd) : '—'} sub={s.costBasisUsd > 0 ? 'purchase prices at acquisition-date FX' : 'add purchase prices to track returns'} /> = 0 ? 'text-gain' : 'text-loss'}>{d.money(s.gainUsd)}} sub={s.gainUsd === null ? 'needs value + cost' : 'items with both value and cost'} /> } sub={s.manualValueUsd > 0 ? `${d.money(s.manualValueUsd)} from manual values` : 'vs cost basis'} />
); } export function AllocationCards({ s, d }: { s: PortfolioSummary; d: Display }) { return (
({ label: b.label, value: b.valueUsd }))} centerValue={s.allocationByCategory.length ? String(s.allocationByCategory.length) : undefined} centerLabel="categories" />
Largest position
{fmtPct(s.concentration.topItemShare, 0, false)}
Top 5 positions
{fmtPct(s.concentration.top5Share, 0, false)}
HHI (0–1)
{s.concentration.hhi === null ? '—' : s.concentration.hhi.toFixed(2)}
Graded share
{fmtPct(s.gradingMix.filter((b) => b.key !== 'raw').reduce((a, b) => a + b.share, 0), 0, false)}
High-liquidity share
{fmtPct(s.liquidityMix.find((b) => b.key === 'high')?.share ?? 0, 0, false)}
High-rarity share
{fmtPct(s.rarityMix.find((b) => b.key === 'high')?.share ?? 0, 0, false)}
{s.best.length ? ( ({ label: i.title, value: (i.gainPct ?? 0) * 100, tone: 'gain' as const })), ...s.worst.slice(0, 3).map((i) => ({ label: i.title, value: (i.gainPct ?? 0) * 100, tone: 'loss' as const }))]} format={(v) => `${v > 0 ? '+' : ''}${v.toFixed(0)}%`} /> ) : (

Add purchase prices to see performers.

)}

Values in {d.currency}; RIV estimates carry a confidence level.

); } export function ValueSourceBadge({ item }: { item: ValuedItem }) { if (item.valueSource === 'variant_riv') return RIV · {item.grader ? `${item.grader.toUpperCase()} ${item.grade ?? ''}`.trim() : 'variant'}; if (item.valueSource === 'asset_riv') return RIV · asset; if (item.valueSource === 'manual') return manual; return no valuation; } export function AssetLink({ slug, title, className }: { slug: string; title: string; className?: string }) { return ( {title} ); }