056c47581f
Ships the second dashboard surface — a Pattern Library + Preview Theatre — that presents the 4-section PDP pilot batch back to Umar, compliance, and the board in an editorial format. Adds the full data layer that drives it: 5 source-backed per-SKU drafts at QA 100/100, 15 competitor PDP semantic extracts, PubMed evidence packs, EFSA claims library extension, JV brand voice guide, hand-curated product FAQs, and the Matrixify-ready CSV exports for Lewis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
86 lines
2.5 KiB
JavaScript
86 lines
2.5 KiB
JavaScript
/* Tiny composable that pre-loads the editorial data layer:
|
|
- pilot summary (5 SKUs at-a-glance)
|
|
- per-SKU drafts (full content + QA)
|
|
- competitor index (15 brands)
|
|
- per-brand extracts
|
|
- product truth (used for the Preview Theatre price/format)
|
|
*/
|
|
import { ref, shallowRef } from 'vue'
|
|
|
|
const BASE = '/api/editorial'
|
|
|
|
const _summary = shallowRef(null)
|
|
const _skuMap = shallowRef({})
|
|
const _competitorIndex = shallowRef(null)
|
|
const _competitorMap = shallowRef({})
|
|
const _truthMap = shallowRef({})
|
|
|
|
const loading = ref(false)
|
|
const error = ref(null)
|
|
|
|
async function fetchJson(path) {
|
|
const res = await fetch(path)
|
|
if (!res.ok) throw new Error(`${path} → ${res.status}`)
|
|
return res.json()
|
|
}
|
|
|
|
async function loadSummary() {
|
|
if (_summary.value) return _summary.value
|
|
_summary.value = await fetchJson(`${BASE}/pilot`)
|
|
return _summary.value
|
|
}
|
|
|
|
async function loadSku(sku) {
|
|
if (_skuMap.value[sku]) return _skuMap.value[sku]
|
|
const doc = await fetchJson(`${BASE}/pilot/${encodeURIComponent(sku)}`)
|
|
_skuMap.value = { ..._skuMap.value, [sku]: doc }
|
|
return doc
|
|
}
|
|
|
|
async function loadAllPilots() {
|
|
const s = await loadSummary()
|
|
await Promise.all(s.perSku.map(p => loadSku(p.sku)))
|
|
return s.perSku.map(p => _skuMap.value[p.sku]).filter(Boolean)
|
|
}
|
|
|
|
async function loadCompetitors() {
|
|
if (_competitorIndex.value) return _competitorIndex.value
|
|
_competitorIndex.value = await fetchJson(`${BASE}/competitors`)
|
|
return _competitorIndex.value
|
|
}
|
|
|
|
async function loadCompetitor(handle) {
|
|
if (_competitorMap.value[handle]) return _competitorMap.value[handle]
|
|
const doc = await fetchJson(`${BASE}/competitor/${encodeURIComponent(handle)}`)
|
|
_competitorMap.value = { ..._competitorMap.value, [handle]: doc }
|
|
return doc
|
|
}
|
|
|
|
async function loadAllCompetitors() {
|
|
const idx = await loadCompetitors()
|
|
const docs = await Promise.all(idx.extracts.map(c => loadCompetitor(c.handle).catch(() => null)))
|
|
return docs.filter(Boolean)
|
|
}
|
|
|
|
async function loadTruth(sku) {
|
|
if (_truthMap.value[sku]) return _truthMap.value[sku]
|
|
const doc = await fetchJson(`${BASE}/product-truth/${encodeURIComponent(sku)}`)
|
|
_truthMap.value = { ..._truthMap.value, [sku]: doc }
|
|
return doc
|
|
}
|
|
|
|
export function useEditorialApi() {
|
|
return {
|
|
loading,
|
|
error,
|
|
loadSummary,
|
|
loadSku,
|
|
loadAllPilots,
|
|
loadCompetitors,
|
|
loadCompetitor,
|
|
loadAllCompetitors,
|
|
loadTruth,
|
|
cache: { summary: _summary, skuMap: _skuMap, competitorIndex: _competitorIndex, competitorMap: _competitorMap, truthMap: _truthMap }
|
|
}
|
|
}
|