73e28180c8
After Review → Continue to Publish, show a glanceable confirmation: ordered photo thumbs, SKU/title/price, taxonomy + dims, short and long descriptions (plain text). Back to Review edits; Publish posts the draft. Reuses slab/taxonomy/photos only — no invented fields.
134 lines
4.2 KiB
TypeScript
134 lines
4.2 KiB
TypeScript
/**
|
||
* 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<Slab, 'width_min_in' | 'width_max_in' | 'width_avg_in'>): 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() || '—',
|
||
};
|
||
}
|