From d87047bfe34916bb84e0ed31f76d4745f46c6993 Mon Sep 17 00:00:00 2001 From: seb Date: Wed, 5 Aug 2026 07:15:18 +0200 Subject: [PATCH] u --- src/api/index.js | 31 ++++++-- src/api/storageManager.jsx | 148 +++++++++++++++++++++++++++---------- 2 files changed, 133 insertions(+), 46 deletions(-) diff --git a/src/api/index.js b/src/api/index.js index b3cd403..6d12d20 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -183,6 +183,16 @@ async function httpGetBinary(path) { return blob; } +/** + * Company-scoped article search index (selectedCompanyId from session). + * GET /api/articleIndex → { articles: [...] } + */ +export const articleIndex = { + list() { + return http('/api/articleIndex'); + }, +}; + export const article = { search({ target, filter, offset, maxCount } = {}) { const params = new URLSearchParams({ target }); @@ -512,12 +522,9 @@ export const brand = { }; export const company = { - async list() { - try { - return await http('/api/company'); - } catch { - return { companies: [] }; - } + /** GET /api/company → { companies: [...] } */ + list() { + return http('/api/company'); }, /** Uses GET /api/company until a dedicated endpoint exists. */ @@ -682,4 +689,14 @@ export const auth = { }, }; -export default { auth, article, brand, category, company, posterminal, thumb, unit }; +export default { + auth, + article, + articleIndex, + brand, + category, + company, + posterminal, + thumb, + unit, +}; diff --git a/src/api/storageManager.jsx b/src/api/storageManager.jsx index 9a2b00a..b4a2559 100644 --- a/src/api/storageManager.jsx +++ b/src/api/storageManager.jsx @@ -1,53 +1,96 @@ import { createContext, useContext, useRef } from 'react'; +import { articleIndex, brand, category, company, posterminal, unit } from './index'; +import { requireArray, requireDefined, requireObject } from '../utils/apiShape'; export const StorageManagerContext = createContext(null); -function loadArticleIndex() { - /** Fetch /articleIndex and convert to the row shape the search page renders. */ - return fetch('/api/articleIndex').then(async (res) => { - const data = await res.json(); - const articles = Array.isArray(data) ? data : (data?.articles ?? []); - console.log('[storageMgr] GET /articleIndex', { - status: res.status, - count: articles.length, - sampleKeys: - articles.slice(0, 3).map((a) => (a && typeof a === 'object' ? Object.keys(a) : null)), - }); - // Map backend article rows to the recordset shape used by the result list. - // `categories` is a comma-joined string (formatCategoryList splits on ','). - return articles.map((a) => ({ - id: a.id, - sku: a.sku, - name: a.name, - eans: a.eans ?? [], - categories: Array.isArray(a.categoryIds) ? a.categoryIds.join(',') : null, - })); - }); -} - -function createStorageManager() { - let articleIndexPromise = loadArticleIndex().catch((err) => { - console.error('[storageMgr] GET /articleIndex failed', err); - return []; +/** + * Stale-while-revalidate cache: readers keep the current promise; update() + * swaps only after a successful reload. Failed updates leave the cache intact. + */ +function createCache(label, load) { + let promise = load().catch((err) => { + console.error(`[storageMgr] ${label} failed`, err); + throw err; }); - /** - * Re-fetch /articleIndex. Searches keep using the old cache until the new - * load resolves; only then is the cache replaced. On failure the old cache stays. - */ async function update() { try { - const articles = await loadArticleIndex(); - articleIndexPromise = Promise.resolve(articles); - return articles; + const data = await load(); + promise = Promise.resolve(data); + return data; } catch (err) { - console.error('[storageMgr] update /articleIndex failed', err); + console.error(`[storageMgr] update ${label} failed`, err); throw err; } } + return { + update, + get promise() { + return promise; + }, + }; +} + +async function loadArticleIndex() { + const data = await articleIndex.list(); + requireObject(data, 'GET /api/articleIndex'); + const articles = requireArray(data.articles, 'articleIndex.articles'); + // Map backend rows to the recordset shape used by the result list. + // `categories` is a comma-joined string (formatCategoryList splits on ','). + return articles.map((a) => ({ + id: a.id, + sku: a.sku, + name: a.name, + eans: a.eans ?? [], + categories: Array.isArray(a.categoryIds) ? a.categoryIds.join(',') : null, + })); +} + +async function loadBrands() { + const data = await brand.list(); + requireObject(data, 'GET /api/brand'); + return requireArray(data.brands, 'brands'); +} + +async function loadUnits() { + const data = await unit.list(); + requireObject(data, 'GET /api/unit'); + return requireArray(data.units, 'units'); +} + +async function loadCompanies() { + const data = await company.list(); + requireObject(data, 'GET /api/company'); + return requireArray(data.companies, 'companies'); +} + +async function loadCategoryTree() { + const data = await category.getTree(); + requireObject(data, 'GET /api/category/tree'); + requireArray(data.tree, 'category/tree.tree'); + requireArray(data.articles, 'category/tree.articles'); + requireDefined(data.languageId, 'category/tree.languageId'); + return data; +} + +async function loadPosterminals() { + const data = await posterminal.list(); + requireObject(data, 'GET /api/posterminal'); + return requireArray(data.posterminals, 'posterminals'); +} + +function createStorageManager() { + const articleIndexCache = createCache('GET /articleIndex', loadArticleIndex); + const brandCache = createCache('GET /brand', loadBrands); + const unitCache = createCache('GET /unit', loadUnits); + const companyCache = createCache('GET /company', loadCompanies); + const categoryTreeCache = createCache('GET /category/tree', loadCategoryTree); + const posterminalCache = createCache('GET /posterminal', loadPosterminals); + async function doSearch({ target, filter, offset, maxCount, onUpdate }) { - const articles = await articleIndexPromise; + const articles = await articleIndexCache.promise; const query = target.trim().toLowerCase(); const matches = articles.filter( (p) => @@ -65,11 +108,38 @@ function createStorageManager() { return { doSearch, - update, - // Always the current cache promise (including after update()). + get articleIndexPromise() { - return articleIndexPromise; + return articleIndexCache.promise; }, + updateArticleIndex: articleIndexCache.update, + /** @deprecated use updateArticleIndex */ + update: articleIndexCache.update, + + get brandPromise() { + return brandCache.promise; + }, + updateBrand: brandCache.update, + + get unitPromise() { + return unitCache.promise; + }, + updateUnit: unitCache.update, + + get companyPromise() { + return companyCache.promise; + }, + updateCompany: companyCache.update, + + get categoryTreePromise() { + return categoryTreeCache.promise; + }, + updateCategoryTree: categoryTreeCache.update, + + get posterminalPromise() { + return posterminalCache.promise; + }, + updatePosterminal: posterminalCache.update, }; }