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}
);
}