import Link from 'next/link'; import { Search } from 'lucide-react'; import type { CategoryNode } from '@rareindex/taxonomy'; import { AssetList } from '@/components/market/asset-list'; import { Pagination } from '@/components/ui/pagination'; import { Card, CardHeader } from '@/components/ui/primitives'; import { attachGuidePrices, exploreAssets, getScopeCounts, categoryScope, type ExploreSort, type HasFilter } from '@/lib/queries/assets'; import { fmtNum, cn } from '@/lib/format'; import { sp1, spEnum, spInt, withParams, type SP } from '@/lib/search-params'; /** * Paginated, filterable browser over canonical assets for a market, set or brand. Lists every * catalogued asset — with or without valuation — and states plainly how many are priced yet. */ export interface BrowserScope { category?: string | null; set?: string | null; brand?: string | null; } const SORTS: ExploreSort[] = ['relevance', 'riv', 'sales', 'latest_sale', 'change30d', 'newest', 'number', 'name']; const SORT_LABEL: Record = { relevance: 'Most data', riv: 'Valuation', sales: 'Sales', latest_sale: 'Last sale', change30d: '30D change', newest: 'Newest', number: 'Set · number', name: 'Name' }; const HAS: Array<{ id: HasFilter | ''; label: string }> = [ { id: '', label: 'All' }, { id: 'sales', label: 'With sales' }, { id: 'valuation', label: 'With valuation' }, { id: 'listings', label: 'With listings' }, { id: 'observations', label: 'With guide price' }, { id: 'images', label: 'With image' }, ]; export const BROWSER_KEYS = ['sort', 'has', 'sub', 'bq', 'view', 'page']; export async function AssetBrowser({ scope, sp, basePath, title = 'Assets in this market', subcategories = [], pageSize = 48, anchor = 'assets' }: { scope: BrowserScope; sp: SP; basePath: string; title?: string; subcategories?: CategoryNode[]; pageSize?: number; anchor?: string }) { const sort = spEnum(sp, 'sort', SORTS, 'relevance'); const has = (sp1(sp, 'has') as HasFilter | undefined) ?? null; const sub = sp1(sp, 'sub') ?? null; const q = sp1(sp, 'bq') ?? null; const page = spInt(sp, 'page'); const view = spEnum(sp, 'view', ['table', 'grid'] as const, 'table'); const categorySlugs = sub && subcategories.some((c) => c.slug === sub) ? categoryScope(sub) : scope.category ? categoryScope(scope.category) : null; const filters = { scope: categorySlugs, set: scope.set ?? null, brand: scope.brand ?? null, has: HAS.some((h) => h.id === has) ? has : null, q, sort, page, pageSize }; const [res, counts] = await Promise.all([exploreAssets(filters), getScopeCounts({ scope: scope.category ? categoryScope(scope.category) : null, set: scope.set ?? null, brand: scope.brand ?? null })]); await attachGuidePrices(res.items); const params: Record = { sort: sort === 'relevance' ? undefined : sort, has: has ?? undefined, sub: sub ?? undefined, bq: q ?? undefined, view: view === 'table' ? undefined : view }; const link = (patch: Record) => `${withParams(basePath, { ...params, ...patch, page: null })}#${anchor}`; const inProgress = counts.assets > 0 && counts.priced < counts.assets; const summary = [ `${fmtNum(counts.assets)} assets`, `${fmtNum(counts.priced)} with valuation`, counts.withSales ? `${fmtNum(counts.withSales)} with sales` : null, counts.withObservations ? `${fmtNum(counts.withObservations)} with guide prices` : null, counts.listings ? `${fmtNum(counts.listings)} live listings` : null, ].filter(Boolean).join(' · '); const columns = sort === 'change30d' ? (['riv', 'change7d', 'change30d', 'latestSale', 'sales'] as const) : (['riv', 'latestSale', 'change30d', 'sales', 'listings'] as const); return ( {summary} {inProgress ? · valuations in progress : null} } action={
{Object.entries(params).map(([k, v]) => (v && k !== 'bq' ? : null))} } />
{subcategories.length ? (
Subcategory All {subcategories.map((c) => ( {c.name} ))}
) : null}
Show {HAS.map((h) => ( {h.label} ))}
Sort {SORTS.map((s) => ( {SORT_LABEL[s]} ))} Table Cards
{q ? (

Filtering by “{q}” ·{' '} clear

) : null}
{view === 'grid' ? (
) : ( )}
); } function Chip({ href, active, children }: { href: string; active: boolean; children: React.ReactNode }) { return ( {children} ); }