/** * Pure builders for the Publish preview confirmation screen. * Resolves taxonomy ids → display names; never invents fields beyond the slab. */ import { decodeName } from '../decodeName'; import type { PhotoRow, Slab, Taxonomy, TaxAttribute, TaxCategory, TaxTag } from '../../api/slabs'; export interface PreviewPhoto { id: string; url: string; role: 'calibration' | 'overlay' | 'batch'; /** Leftmost (seq order) = Woo cover. */ primary: boolean; } export interface PreviewFact { label: string; value: string; } export interface PublishPreviewModel { photos: PreviewPhoto[]; sku: string; title: string; price: string; facts: PreviewFact[]; shortDescription: string; description: string; } const fmt1 = (v: number) => { const s = v.toFixed(1); return s.endsWith('.0') ? s.slice(0, -2) : s; }; const fmt2 = (v: number) => v.toFixed(2); function catName(cats: TaxCategory[], id: number | null | undefined): string { if (id == null) return '—'; const c = cats.find((x) => x.id === id); return c ? decodeName(c.name) : `#${id}`; } function termName(attrs: TaxAttribute[], id: number | null | undefined): string { if (id == null) return '—'; for (const a of attrs) { const t = a.terms.find((x) => x.id === id); if (t) return decodeName(t.name); } return `#${id}`; } function attrTermsByName(attrs: TaxAttribute[], name: string): { id: number; name: string }[] { const attr = attrs.find((a) => a.name.toLowerCase() === name.toLowerCase()); return (attr?.terms ?? []).map((t) => ({ id: t.id, name: decodeName(t.name) })); } function namesForIds( ids: number[], lookup: (id: number) => string | null, ): string { if (!ids.length) return '—'; return ids.map((id) => lookup(id) ?? `#${id}`).join(', '); } function tagName(tags: TaxTag[], id: number): string | null { const t = tags.find((x) => x.id === id); return t ? decodeName(t.name) : null; } /** Widths as "min–max (avg)" inches, matching Review dims language. */ export function formatWidths(slab: Pick): string { return `${fmt1(slab.width_min_in)}–${fmt1(slab.width_max_in)}″ (avg ${fmt1(slab.width_avg_in)}″)`; } export function buildPublishPreviewModel( slab: Slab, tax: Taxonomy | null, photos: PhotoRow[], urlFor: (photoId: string, kind: 'original' | 'processed') => string, ): PublishPreviewModel { const cats = tax?.categories ?? []; const attrs = tax?.attributes ?? []; const tags = tax?.tags ?? []; const figureTerms = attrTermsByName(attrs, 'figure'); const figureLookup = (id: number) => { const t = figureTerms.find((x) => x.id === id); return t ? t.name : null; }; const ordered = [...photos].sort((a, b) => a.seq - b.seq); const previewPhotos: PreviewPhoto[] = ordered.map((p, i) => ({ id: p.id, url: urlFor(p.id, p.has_processed ? 'processed' : 'original'), role: p.role ?? 'batch', primary: i === 0, })); const woodNames = namesForIds(slab.wood_category_ids ?? [], (id) => { const c = cats.find((x) => x.id === id); return c ? decodeName(c.name) : null; }); const facts: PreviewFact[] = [ { label: 'Species', value: catName(cats, slab.species_id) }, { label: 'Wood categories', value: woodNames }, { label: 'Edge', value: termName(attrs, slab.edge_type_term_id) }, { label: 'Figure', value: namesForIds(slab.figure_term_ids ?? [], figureLookup), }, { label: 'Grade', value: termName(attrs, slab.grade_term_id) }, { label: 'Moisture', value: termName(attrs, slab.moisture_term_id) }, { label: 'Features', value: namesForIds(slab.feat_tag_ids ?? [], (id) => tagName(tags, id)), }, { label: 'Length', value: `${fmt1(slab.length_in)}″` }, { label: 'Widths', value: formatWidths(slab) }, { label: 'Thickness', value: `${fmt1(slab.thickness_in)}″` }, { label: 'Bdft', value: fmt2(slab.bdft) }, ]; return { photos: previewPhotos, sku: slab.sku || '—', title: slab.title || '—', price: slab.price != null ? `$${slab.price.toFixed(2)}` : '—', facts, shortDescription: (slab.short_description ?? '').trim() || '—', description: (slab.description ?? '').trim() || '—', }; }