u
This commit is contained in:
@@ -257,8 +257,8 @@ _none_
|
||||
- Params: `sku: str, body: { eans: str[] }`
|
||||
- Returns: `204 void`
|
||||
#### Get article history by SKU — `GET /article/{sku}/history`
|
||||
- Params: `sku: str, query: limit?: int`
|
||||
- Returns: `200 { history: { id: int, date: str, text: str }[], truncated: bool }`
|
||||
- Params: `sku: str, query: offset?: int, limit?: int, search?: str, seekMonths?: int`
|
||||
- Returns: `200 { history: { id: int, date: str, text: str }[], total: int, unfilteredTotal: int, offset: int, limit: int }`
|
||||
#### Add article image — `POST /article/{sku}/image`
|
||||
- Params: `sku: str, image/avif`
|
||||
- Returns: `201 { image: { picId: int, sort: int, full: { w: int, h: int, size: int }, thumb: { w: int, h: int }, hash: str, thumbHash: str } }`
|
||||
|
||||
570
package-lock.json
generated
570
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -33,8 +33,6 @@
|
||||
"@mui/material": "^9.5.0",
|
||||
"@shopify/draggable": "^1.2.1",
|
||||
"@uiw/react-codemirror": "^4.25.12",
|
||||
"ag-grid-community": "^36.2.0",
|
||||
"ag-grid-react": "^36.2.0",
|
||||
"dashgui": "file:../dashgui",
|
||||
"flexsearch": "^0.8.212",
|
||||
"jsbarcode": "^3.12.3",
|
||||
@@ -45,9 +43,8 @@
|
||||
"react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor",
|
||||
"react-dom": "^19.3.1",
|
||||
"react-router-dom": "^7.18.4",
|
||||
"react-virtual-aggridautomat": "file:../react-virtual-aggridautomat",
|
||||
"react-window": "^2.3.4",
|
||||
"skelehash-wasm": "^0.1.0",
|
||||
"skelehash-wasm": "file:../skelehash-wasm",
|
||||
"sortablejs": "^1.15.7",
|
||||
"thumbhash": "^0.1.1"
|
||||
},
|
||||
|
||||
@@ -50,11 +50,19 @@ export const article = {
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* GET /api/article/:sku/history
|
||||
* GET /api/article/:sku/history?offset=0&limit=50&search=...&seekMonths=-1
|
||||
* @param {string} sku
|
||||
* @param {{ offset?: number, limit?: number, search?: string, seekMonths?: number }} [params]
|
||||
* → { history: ArticleHistoryEntry[], total: number, unfilteredTotal?: number, offset: number, limit: number }
|
||||
*/
|
||||
getBySkuHistory(sku, { limit } = {}) {
|
||||
getBySkuHistory(sku, { offset = 0, limit = 50, search, seekMonths } = {}) {
|
||||
const params = new URLSearchParams();
|
||||
if (limit != null) params.set('limit', String(limit));
|
||||
if (offset != null) params.set('offset', String(Math.max(0, Math.floor(Number(offset) || 0))));
|
||||
if (limit != null) params.set('limit', String(Math.max(1, Math.floor(Number(limit) || 50))));
|
||||
if (search && String(search).trim()) params.set('search', String(search).trim());
|
||||
if (seekMonths != null && Number(seekMonths) < 0) {
|
||||
params.set('seekMonths', String(Math.floor(Number(seekMonths))));
|
||||
}
|
||||
const qs = params.toString();
|
||||
return http(`/api/article/${encodeURIComponent(sku)}/history${qs ? `?${qs}` : ''}`);
|
||||
},
|
||||
|
||||
@@ -34,26 +34,72 @@ import { requireArray, requireObject } from '../utils/apiShape';
|
||||
* @property {number|null} taxRate Tax rate in percent.
|
||||
*/
|
||||
|
||||
import { createChunkListAutomat } from './createChunkListAutomat';
|
||||
import authAutomat from './authAutomat';
|
||||
|
||||
/**
|
||||
* Per-SKU article change history (`GET /api/article/{sku}/history`).
|
||||
*
|
||||
* The backend returns the full list; pagination happens client-side in
|
||||
* `ArticleHistoryTab`. Keyed by SKU so several tabs can watch the same article.
|
||||
*
|
||||
* @type {KeyedAutomat<Array<ArticleHistoryEntry>>}
|
||||
* Registry of SKU-specific chunk-list automats for article history.
|
||||
* @type {Map<string, import('react-automat').Automat>}
|
||||
*/
|
||||
export const articleHistoryAutomat = new KeyedAutomat(
|
||||
null,
|
||||
{},
|
||||
{
|
||||
name: 'articleHistory',
|
||||
loader: async (sku) => {
|
||||
const res = await articleApi.getBySkuHistory(String(sku));
|
||||
requireObject(res, 'GET /api/article/:sku/history');
|
||||
return requireArray(res.history, 'history');
|
||||
},
|
||||
const skuHistoryAutomats = new Map();
|
||||
|
||||
/**
|
||||
* Gets or creates the chunk-list automat for a specific article's history.
|
||||
*
|
||||
* @param {string|number} sku Article SKU.
|
||||
* @returns {import('react-automat').Automat} Configured chunk-list automat for the SKU.
|
||||
*/
|
||||
export function getArticleHistoryAutomat(sku) {
|
||||
const key = String(sku);
|
||||
let inst = skuHistoryAutomats.get(key);
|
||||
if (!inst) {
|
||||
inst = createChunkListAutomat({
|
||||
name: `articleHistory:${key}`,
|
||||
chunkSize: 50,
|
||||
maxLruChunks: 50,
|
||||
fetchPage: (params) => articleApi.getBySkuHistory(key, params),
|
||||
});
|
||||
skuHistoryAutomats.set(key, inst);
|
||||
}
|
||||
return inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-SKU article change history manager (`GET /api/article/{sku}/history`).
|
||||
*
|
||||
* Backed by chunk-list automats (`createChunkListAutomat`) keyed by SKU with
|
||||
* multi-filter memory caching, LRU eviction, and infinite-scrolling / FillSpace support.
|
||||
*/
|
||||
export const articleHistoryAutomat = {
|
||||
get: getArticleHistoryAutomat,
|
||||
key: getArticleHistoryAutomat,
|
||||
has(sku) {
|
||||
return skuHistoryAutomats.has(String(sku));
|
||||
},
|
||||
);
|
||||
async load(sku) {
|
||||
const inst = getArticleHistoryAutomat(sku);
|
||||
return inst.getItems();
|
||||
},
|
||||
async reload(sku) {
|
||||
const inst = skuHistoryAutomats.get(String(sku));
|
||||
if (inst) {
|
||||
await inst.actions.reload();
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
for (const inst of skuHistoryAutomats.values()) {
|
||||
inst.actions.reset();
|
||||
}
|
||||
skuHistoryAutomats.clear();
|
||||
},
|
||||
};
|
||||
|
||||
// Reset on logout or company change
|
||||
authAutomat.subscribe((auth) => {
|
||||
if (auth.status === 'anon') {
|
||||
articleHistoryAutomat.reset();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Suppliers assigned to an article (`GET /api/article/{sku}/supplier`).
|
||||
|
||||
@@ -117,7 +117,7 @@ export { createListAutomat, selectListItems, selectListStatus } from './createLi
|
||||
|
||||
|
||||
/** Per-SKU article detail views keyed by SKU. */
|
||||
export { articleHistoryAutomat, articleSupplierAutomat } from './articleDetailAutomat';
|
||||
export { articleHistoryAutomat, getArticleHistoryAutomat, articleSupplierAutomat } from './articleDetailAutomat';
|
||||
|
||||
/** Single shared GET /api/events connection. */
|
||||
export { default as sseAutomat, invalidateArticle } from './sseAutomat';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,3 @@
|
||||
@import 'ag-grid-community/styles/ag-grid.css';
|
||||
@import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||
|
||||
/* App background. Provided as a CSS custom property by src/colors.js
|
||||
(installCssVars), with a hard-coded fallback for the very first paint. */
|
||||
html,
|
||||
|
||||
Reference in New Issue
Block a user