Files
justvitamin/src-editorial/composables/useEditorialApi.js
T
Omair Saleh 056c47581f feat: editorial review dashboard + elite-grade pilot batch (5 SKUs)
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>
2026-06-02 18:50:09 +08:00

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 }
}
}