u
This commit is contained in:
@@ -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,
|
||||||
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user