This commit is contained in:
seb
2026-08-05 07:15:18 +02:00
parent b720c6ee25
commit d87047bfe3
2 changed files with 133 additions and 46 deletions

View File

@@ -183,6 +183,16 @@ async function httpGetBinary(path) {
return blob; 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 = { export const article = {
search({ target, filter, offset, maxCount } = {}) { search({ target, filter, offset, maxCount } = {}) {
const params = new URLSearchParams({ target }); const params = new URLSearchParams({ target });
@@ -512,12 +522,9 @@ export const brand = {
}; };
export const company = { export const company = {
async list() { /** GET /api/company → { companies: [...] } */
try { list() {
return await http('/api/company'); return http('/api/company');
} catch {
return { companies: [] };
}
}, },
/** Uses GET /api/company until a dedicated endpoint exists. */ /** 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,
};

View File

@@ -1,53 +1,96 @@
import { createContext, useContext, useRef } from 'react'; 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); export const StorageManagerContext = createContext(null);
function loadArticleIndex() { /**
/** Fetch /articleIndex and convert to the row shape the search page renders. */ * Stale-while-revalidate cache: readers keep the current promise; update()
return fetch('/api/articleIndex').then(async (res) => { * swaps only after a successful reload. Failed updates leave the cache intact.
const data = await res.json(); */
const articles = Array.isArray(data) ? data : (data?.articles ?? []); function createCache(label, load) {
console.log('[storageMgr] GET /articleIndex', { let promise = load().catch((err) => {
status: res.status, console.error(`[storageMgr] ${label} failed`, err);
count: articles.length, throw err;
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 [];
}); });
/**
* 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() { async function update() {
try { try {
const articles = await loadArticleIndex(); const data = await load();
articleIndexPromise = Promise.resolve(articles); promise = Promise.resolve(data);
return articles; return data;
} catch (err) { } catch (err) {
console.error('[storageMgr] update /articleIndex failed', err); console.error(`[storageMgr] update ${label} failed`, err);
throw 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 }) { async function doSearch({ target, filter, offset, maxCount, onUpdate }) {
const articles = await articleIndexPromise; const articles = await articleIndexCache.promise;
const query = target.trim().toLowerCase(); const query = target.trim().toLowerCase();
const matches = articles.filter( const matches = articles.filter(
(p) => (p) =>
@@ -65,11 +108,38 @@ function createStorageManager() {
return { return {
doSearch, doSearch,
update,
// Always the current cache promise (including after update()).
get articleIndexPromise() { 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,
}; };
} }