SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
4.2 KB · 84 lines tsx
Raw Blame History
1import { FEE_SCHEDULE_AS_OF, type FeeSchedule } from '@rareindex/valuation';2import { fmtMoney, fmtPct, cn } from '@/lib/format';3import { Badge, Table, th, td, tdNum } from '@/components/ui/primitives';45export function FeeConfidenceBadge({ confidence, className }: { confidence: FeeSchedule['confidence'] | 'unknown'; className?: string }) {6  const map: Record<string, { tone: 'gain' | 'index' | 'neutral' | 'alert'; label: string; title: string }> = {7    published: { tone: 'gain', label: 'Published', title: `Taken from the house's published terms as of ${FEE_SCHEDULE_AS_OF}` },8    approximate: { tone: 'index', label: '≈ Approximate', title: 'Widely reported schedule not re-verified against the current terms — every all-in figure built on it is labelled as an estimate' },9    none: { tone: 'neutral', label: 'No buyer premium', title: 'The house bills the hammer price; the seller pays the commission' },10    default: { tone: 'alert', label: 'Default 22 %', title: 'House not on file — a typical 22 % premium is applied and labelled' },11    unknown: { tone: 'alert', label: 'Unknown', title: 'No schedule on file' },12  };13  const m = map[confidence] ?? map.unknown!;14  return (15    <Badge tone={m.tone} className={className}>16      <span title={m.title}>{m.label}</span>17    </Badge>18  );19}2021/** Buyer's premium schedule of one house (§35): marginal tiers, minimum/maximum, fixed fee, confidence and source. */22export function FeeScheduleTable({ schedule, className }: { schedule: FeeSchedule; className?: string }) {23  const cur = schedule.currency;24  const tiers = schedule.tiers.map((t, i) => ({ from: i === 0 ? 0 : (schedule.tiers[i - 1]!.upTo ?? 0), to: t.upTo, rate: t.rate }));25  return (26    <div className={cn('space-y-2', className)}>27      <Table>28        <thead>29          <tr>30            <th className={th}>Hammer price ({cur})</th>31            <th className={cn(th, 'text-right')}>Buyer&apos;s premium</th>32          </tr>33        </thead>34        <tbody>35          {tiers.map((t, i) => (36            <tr key={i}>37              <td className={td}>{t.to === null ? `above ${fmtMoney(t.from, cur)}` : t.from === 0 ? `up to ${fmtMoney(t.to, cur)}` : `${fmtMoney(t.from, cur)} – ${fmtMoney(t.to, cur)}`}</td>38              <td className={tdNum}>{fmtPct(t.rate, t.rate * 100 % 1 ? 1 : 0, false)}</td>39            </tr>40          ))}41          {schedule.minimum !== undefined ? (42            <tr>43              <td className={td}>Minimum premium</td>44              <td className={tdNum}>{fmtMoney(schedule.minimum, cur)}</td>45            </tr>46          ) : null}47          {schedule.maximum !== undefined ? (48            <tr>49              <td className={td}>Maximum premium</td>50              <td className={tdNum}>{fmtMoney(schedule.maximum, cur)}</td>51            </tr>52          ) : null}53          {schedule.fixedFee !== undefined ? (54            <tr>55              <td className={td}>Fixed fee per lot</td>56              <td className={tdNum}>{fmtMoney(schedule.fixedFee, cur)}</td>57            </tr>58          ) : null}59        </tbody>60      </Table>61      <p className="text-[11px] text-muted">62        <FeeConfidenceBadge confidence={schedule.confidence} className="mr-1.5" />63        Schedule as of {FEE_SCHEDULE_AS_OF}.{schedule.note ? ` ${schedule.note}` : ''} VAT or sales tax on the premium, import duties and shipping are not included.64        {schedule.source ? (65          <>66            {' '}67            <a href={schedule.source} target="_blank" rel="noopener nofollow" className="underline-offset-2 hover:underline">68              Source ↗69            </a>70          </>71        ) : null}72      </p>73    </div>74  );75}7677/** One-line summary of a schedule for lists: "27 % → 15 % (3 tiers)" or "20 %". */78export function feeScheduleSummary(s: FeeSchedule): string {79  const rates = s.tiers.map((t) => fmtPct(t.rate, t.rate * 100 % 1 ? 1 : 0, false));80  const base = rates.length === 1 ? rates[0]! : `${rates[0]} → ${rates.at(-1)} (${rates.length} tiers)`;81  const extras = [s.minimum !== undefined ? `min ${fmtMoney(s.minimum, s.currency)}` : null, s.maximum !== undefined ? `cap ${fmtMoney(s.maximum, s.currency)}` : null, s.fixedFee !== undefined ? `+ ${fmtMoney(s.fixedFee, s.currency)}/lot` : null].filter(Boolean);82  return extras.length ? `${base} · ${extras.join(' · ')}` : base;83}84