doxa-maps/library/composables/useDashboardData.js
Doxa Maps 68c7a6d4b6 Kit 4d-2: useDashboardData moves to the library
82 lines, one importer (useDashboardShell), and every import already @map/ — so it moves as-is with
no adapter and no shim. Its inject('profileConfig') stays inside its try/catch: it can be called from
outside setup, and that is the property the move had to preserve.

Source only. research-dashboard and dashboard-plus-grid need a rebuild.
2026-09-27 06:04:09 +00:00

86 lines
4.6 KiB
JavaScript

/**
* useDashboardData.js — RDASH phase 2: the single in-memory row store (kills the hardcode).
*
* Loads ALL people groups once + /statistics into a slug-keyed store. Uses the SHARED data layer
* — sources.json (active source = pray-tools, its endpoints + defaultFields + lang) and getApiBaseUrl
* (window.MAP_APP_API_URL / VITE_API_BASE_URL / pray.doxa.life) — but keeps the RAW API record per
* row rather than DataSourceManager's normalized projection: the dashboard table/charts/CSV reference
* raw fields ({value,label} objects like country_code, wagf_region, religion) and the CSV export must
* serialize the FULL API record. DataSourceManager.normalizeData renames fields to system names and
* drops no-coordinate rows (geo set for the map layer, phase 7) — wrong shape for this store.
*
* Shadow-DOM safe: pure data, no DOM/style. Reuses read-only @map config without editing it.
*/
// KIT LANDING 4d-2: moved from plugin-designs/research-dashboard/src/composables/. It needed no
// adapter — every import was already @map/ — and it injects profileConfig in a try/catch because it
// can be called outside setup, which is the one property the move had to preserve.
import { ref, shallowRef, inject } from 'vue'
import { getApiBaseUrl } from '@map/api/apiBaseUrl.js'
import sourcesConfig from '@map/api/sources.json'
import { applyPeopleGroupList, resolvePeopleGroupList, LIST_DEFAULT } from '@map/lists/peopleGroupList.js'
export function useDashboardData(dataOpts = {}) {
const loading = ref(false)
const error = ref(null)
const rowStore = shallowRef(new Map()) // slug (PK) -> full raw API record
const rows = shallowRef([]) // array view of the same records
const stats = ref(null) // /statistics global aggregates (KPI baselines)
const total = ref(0) // rowStore size (live-data marker; mock had 3)
// Which sources.json source the rows come from. Honors the profile's declared
// config.data.source (parameterizable) and falls back to the shared activeSource,
// preserving current pray-tools behavior. Exposed so the shell can normalize the
// SAME source's fieldMappings for the geocoder dataStore (rows + normalization stay
// in lockstep — no desync if a dashboard ever points at a non-active source).
const sourceId = dataOpts.source || sourcesConfig.activeSource
// The map's people-group LIST (library/lists/): read here, in setup, where inject works.
let profileConfig = null
try { profileConfig = inject('profileConfig', null) } catch (_) { /* called outside setup */ }
async function load() {
loading.value = true
error.value = null
try {
const src = sourcesConfig?.sources?.[sourceId] || {}
const base = getApiBaseUrl()
const bulk = src.endpoints?.bulk || '/api/people-groups/list'
const fields = (src.defaultFields || []).join(',')
const lang = src.queryParams?.lang || 'en'
// ── 1) bulk list (shared config: endpoint + defaultFields + lang + cache-bust) ──
const listUrl = `${base}${bulk}?${fields ? `fields=${fields}&` : ''}lang=${lang}&_=${Date.now()}`
const res = await fetch(listUrl, { cache: 'no-store' })
if (!res.ok) throw new Error(`list HTTP ${res.status}`)
const json = await res.json()
const raw = await applyPeopleGroupList(Array.isArray(json) ? json : (json.posts || json.data || json.results || []), profileConfig)
const map = new Map()
for (const r of raw) {
const slug = r && (r.slug || r.id)
if (slug) map.set(slug, r)
}
rowStore.value = map
rows.value = raw
total.value = map.size
// ── 2) /statistics seeds KPI baselines ──
const statEndpoint = src.endpoints?.statistics || '/api/people-groups/statistics'
// /statistics describes DOXA's whole list, so under any other list its totals would be about
// the wrong set of groups. They are optional baselines; leave them out there.
if (resolvePeopleGroupList(profileConfig) === LIST_DEFAULT) try {
const sres = await fetch(`${base}${statEndpoint}?_=${Date.now()}`, { cache: 'no-store' })
if (sres.ok && (sres.headers.get('content-type') || '').includes('json')) {
stats.value = await sres.json()
}
} catch (_) { /* KPI baselines optional; the list is the source of truth */ }
if (total.value === 0) error.value = 'No records loaded from the live API.'
} catch (e) {
error.value = (e && e.message) ? e.message : String(e)
} finally {
loading.value = false
}
}
return { loading, error, rowStore, rows, stats, total, load, sourceId }
}