u
This commit is contained in:
27
package-lock.json
generated
27
package-lock.json
generated
@@ -29,12 +29,13 @@
|
|||||||
"react-router-dom": "^7.18.4",
|
"react-router-dom": "^7.18.4",
|
||||||
"react-virtual-aggridautomat": "file:../react-virtual-aggridautomat",
|
"react-virtual-aggridautomat": "file:../react-virtual-aggridautomat",
|
||||||
"react-window": "^2.3.3",
|
"react-window": "^2.3.3",
|
||||||
|
"skelehash-wasm": "^0.1.0",
|
||||||
"sortablejs": "^1.15.7",
|
"sortablejs": "^1.15.7",
|
||||||
"thumbhash": "^0.1.1"
|
"thumbhash": "^0.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@vitejs/plugin-react": "6.1.1",
|
"@vitejs/plugin-react": "6.1.2",
|
||||||
"eslint": "^10.12.0",
|
"eslint": "^10.12.0",
|
||||||
"eslint-plugin-react-hooks": "^7.1.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
"globals": "^17.13.0",
|
"globals": "^17.13.0",
|
||||||
@@ -3795,16 +3796,6 @@
|
|||||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
|
||||||
"version": "19.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
|
|
||||||
"integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"csstype": "^3.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/react-transition-group": {
|
"node_modules/@types/react-transition-group": {
|
||||||
"version": "4.4.12",
|
"version": "4.4.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||||
@@ -3829,9 +3820,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "6.1.1",
|
"version": "6.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.2.tgz",
|
||||||
"integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==",
|
"integrity": "sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3843,7 +3834,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
|
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
|
||||||
"babel-plugin-react-compiler": "^1.0.0",
|
"babel-plugin-react-compiler": "^1.0.0",
|
||||||
"oxc-transform-react": "^0.145.0",
|
"oxc-transform-react": "^0.152.0",
|
||||||
"vite": "^8.0.0"
|
"vite": "^8.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
@@ -8154,6 +8145,12 @@
|
|||||||
"simple-concat": "^1.0.0"
|
"simple-concat": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/skelehash-wasm": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/skelehash-wasm/-/skelehash-wasm-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-QdxF9YsJvKlT93O70dXFaWnsKVX7KDD0RgYf9cwgOT6kGnSMOdsO0rsdge+5Tg+vv6BjivbJ/2o5P1KcQsavEw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/smob": {
|
"node_modules/smob": {
|
||||||
"version": "1.6.2",
|
"version": "1.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/smob/-/smob-1.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/smob/-/smob-1.6.2.tgz",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"react-router-dom": "^7.18.4",
|
"react-router-dom": "^7.18.4",
|
||||||
"react-virtual-aggridautomat": "file:../react-virtual-aggridautomat",
|
"react-virtual-aggridautomat": "file:../react-virtual-aggridautomat",
|
||||||
"react-window": "^2.3.3",
|
"react-window": "^2.3.3",
|
||||||
|
"skelehash-wasm": "^0.1.0",
|
||||||
"sortablejs": "^1.15.7",
|
"sortablejs": "^1.15.7",
|
||||||
"thumbhash": "^0.1.1"
|
"thumbhash": "^0.1.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,27 +2,37 @@ import { http } from './http.js';
|
|||||||
|
|
||||||
export const attributename = {
|
export const attributename = {
|
||||||
/** GET /api/attributename → { attributenames: Attributename[] } */
|
/** GET /api/attributename → { attributenames: Attributename[] } */
|
||||||
|
// #region GET /api/attributename — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/attributename');
|
return http('/api/attributename');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** POST /api/attributename → 201 { attributename } */
|
/** POST /api/attributename → 201 { attributename } */
|
||||||
|
// #region POST /api/attributename — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/attributename', { method: 'POST', body });
|
return http('/api/attributename', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** PATCH /api/attributename/:id → 200 { attributename } */
|
/** PATCH /api/attributename/:id → 200 { attributename } */
|
||||||
|
// #region PATCH /api/attributename/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/attributename/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
return http(`/api/attributename/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/attributename/:id → 200 */
|
/** DELETE /api/attributename/:id → 200 */
|
||||||
|
// #region DELETE /api/attributename/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/attributename/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
return http(`/api/attributename/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** PUT /api/attributename/order → 200 */
|
/** PUT /api/attributename/order → 200 */
|
||||||
|
// #region PUT /api/attributename/order — order
|
||||||
order(attributenameIds) {
|
order(attributenameIds) {
|
||||||
return http('/api/attributename/order', { method: 'PUT', body: { attributenameIds } });
|
return http('/api/attributename/order', { method: 'PUT', body: { attributenameIds } });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,33 +2,43 @@ import { http } from './http.js';
|
|||||||
|
|
||||||
export const attributevalue = {
|
export const attributevalue = {
|
||||||
/** GET /api/attributename/:attributeId/value → { values: AttributeValue[] } */
|
/** GET /api/attributename/:attributeId/value → { values: AttributeValue[] } */
|
||||||
|
// #region GET /api/attributename/:attributeId/value — listByAttributeId
|
||||||
listByAttributeId(attributeId) {
|
listByAttributeId(attributeId) {
|
||||||
return http(`/api/attributename/${encodeURIComponent(attributeId)}/value`);
|
return http(`/api/attributename/${encodeURIComponent(attributeId)}/value`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** POST /api/attributename/:attributeId/value → 201 { attributevalue } */
|
/** POST /api/attributename/:attributeId/value → 201 { attributevalue } */
|
||||||
|
// #region POST /api/attributename/:attributeId/value — create
|
||||||
create(attributeId, body) {
|
create(attributeId, body) {
|
||||||
return http(`/api/attributename/${encodeURIComponent(attributeId)}/value`, {
|
return http(`/api/attributename/${encodeURIComponent(attributeId)}/value`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** PATCH /api/attributevalue/:id → 200 { attributevalue } */
|
/** PATCH /api/attributevalue/:id → 200 { attributevalue } */
|
||||||
|
// #region PATCH /api/attributevalue/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/attributevalue/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
return http(`/api/attributevalue/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/attributevalue/:id → 200 */
|
/** DELETE /api/attributevalue/:id → 200 */
|
||||||
|
// #region DELETE /api/attributevalue/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/attributevalue/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
return http(`/api/attributevalue/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** PUT /api/attributename/:attributeId/value/order → 200 */
|
/** PUT /api/attributename/:attributeId/value/order → 200 */
|
||||||
|
// #region PUT /api/attributename/:attributeId/value/order — order
|
||||||
order(attributeId, attributevalueIds) {
|
order(attributeId, attributevalueIds) {
|
||||||
return http(`/api/attributename/${encodeURIComponent(attributeId)}/value/order`, {
|
return http(`/api/attributename/${encodeURIComponent(attributeId)}/value/order`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: { attributevalueIds },
|
body: { attributevalueIds },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { http, logApi } from './http.js';
|
import { http, logApi } from './http.js';
|
||||||
|
|
||||||
export const auth = {
|
export const auth = {
|
||||||
|
// #region GET /api/auth/me — me
|
||||||
async me() {
|
async me() {
|
||||||
try {
|
try {
|
||||||
const data = await http('/api/auth/me');
|
const data = await http('/api/auth/me');
|
||||||
@@ -20,7 +21,9 @@ export const auth = {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region POST /api/auth/login — loginWithGoogle
|
||||||
async loginWithGoogle(idToken) {
|
async loginWithGoogle(idToken) {
|
||||||
logApi('loginWithGoogle', {
|
logApi('loginWithGoogle', {
|
||||||
idTokenLength: idToken?.length ?? 0,
|
idTokenLength: idToken?.length ?? 0,
|
||||||
@@ -41,7 +44,9 @@ export const auth = {
|
|||||||
});
|
});
|
||||||
return { user: data.user, session };
|
return { user: data.user, session };
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region POST /api/auth/logout — logout
|
||||||
async logout() {
|
async logout() {
|
||||||
try {
|
try {
|
||||||
await http('/api/auth/logout', { method: 'POST' });
|
await http('/api/auth/logout', { method: 'POST' });
|
||||||
@@ -49,4 +54,5 @@ export const auth = {
|
|||||||
if (err.status !== 401) throw err;
|
if (err.status !== 401) throw err;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const brand = {
|
|||||||
* GET /api/brand → { brands: Brand[], total, offset, limit }
|
* GET /api/brand → { brands: Brand[], total, offset, limit }
|
||||||
* @param {{ offset?: number, limit?: number, includeSmallThumbs?: boolean }} [params]
|
* @param {{ offset?: number, limit?: number, includeSmallThumbs?: boolean }} [params]
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/brand — list
|
||||||
list(params) {
|
list(params) {
|
||||||
const offset = Number(params?.offset ?? 0);
|
const offset = Number(params?.offset ?? 0);
|
||||||
const limit = Number(params?.limit ?? 0);
|
const limit = Number(params?.limit ?? 0);
|
||||||
@@ -20,6 +21,7 @@ export const brand = {
|
|||||||
const qs = query.toString();
|
const qs = query.toString();
|
||||||
return http(qs ? `/api/brand?${qs}` : '/api/brand');
|
return http(qs ? `/api/brand?${qs}` : '/api/brand');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/brand
|
* POST /api/brand
|
||||||
@@ -27,32 +29,39 @@ export const brand = {
|
|||||||
* Empty name ⇒ 400 Invalid brand.
|
* Empty name ⇒ 400 Invalid brand.
|
||||||
* → 201 { brand: Brand } · 409 Brand already exists
|
* → 201 { brand: Brand } · 409 Brand already exists
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/brand — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/brand', { method: 'POST', body });
|
return http('/api/brand', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/brand/:id
|
* PATCH /api/brand/:id
|
||||||
* Body: { name, gsprpinfo? } — rename / update info.
|
* Body: { name, gsprpinfo? } — rename / update info.
|
||||||
* → 200 { id, name, gsprpinfo, touchedArticleCount } · 404 Brand not found
|
* → 200 { id, name, gsprpinfo, touchedArticleCount } · 404 Brand not found
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/brand/:id — rename
|
||||||
rename(id, body) {
|
rename(id, body) {
|
||||||
return http(`/api/brand/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
return http(`/api/brand/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/brand/:id
|
* DELETE /api/brand/:id
|
||||||
* → 204 No Content · 404 Brand not found
|
* → 204 No Content · 404 Brand not found
|
||||||
*/
|
*/
|
||||||
|
// #region DELETE /api/brand/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/brand/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
return http(`/api/brand/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/brand/:id/image
|
* POST /api/brand/:id/image
|
||||||
* Accepts full-size image body (Content-Type: image/webp).
|
* Accepts full-size image body (Content-Type: image/webp).
|
||||||
* → 201 { brand: { id, fullHash, thumbW, thumbH, thumbHash, pics } }
|
* → 201 { brand: { id, fullHash, thumbW, thumbH, thumbHash, pics } }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/brand/:id/image — addImage
|
||||||
addImage(brandId, file) {
|
addImage(brandId, file) {
|
||||||
return httpBinary(`/api/brand/${brandId}/image`, {
|
return httpBinary(`/api/brand/${brandId}/image`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -60,12 +69,15 @@ export const brand = {
|
|||||||
body: file,
|
body: file,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/brand/:id/image
|
* DELETE /api/brand/:id/image
|
||||||
* → 200 { brand: { id, thumbW: null, thumbH: null, thumbHash: null, pics: [] } }
|
* → 200 { brand: { id, thumbW: null, thumbH: null, thumbHash: null, pics: [] } }
|
||||||
*/
|
*/
|
||||||
|
// #region DELETE /api/brand/:id/image — deleteImage
|
||||||
deleteImage(brandId) {
|
deleteImage(brandId) {
|
||||||
return http(`/api/brand/${brandId}/image`, { method: 'DELETE' });
|
return http(`/api/brand/${brandId}/image`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,34 +2,42 @@ import { http, httpBinary, httpGetBinary } from './http.js';
|
|||||||
|
|
||||||
export const category = {
|
export const category = {
|
||||||
/** GET /api/category/tree → { languageId, tree, articles } (company from session) */
|
/** GET /api/category/tree → { languageId, tree, articles } (company from session) */
|
||||||
|
// #region GET /api/category/tree — getTree
|
||||||
getTree() {
|
getTree() {
|
||||||
return http('/api/category/tree');
|
return http('/api/category/tree');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/category/:id/articles → { articles: [...] }
|
* GET /api/category/:id/articles → { articles: [...] }
|
||||||
* Article rows: sku, name, net, taxRate, fullHash, thumbW, thumbH, thumbHash, childCount, …
|
* Article rows: sku, name, net, taxRate, fullHash, thumbW, thumbH, thumbHash, childCount, …
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/category/:id/articles — getArticles
|
||||||
getArticles(categoryId) {
|
getArticles(categoryId) {
|
||||||
return http(`/api/category/${categoryId}/articles`);
|
return http(`/api/category/${categoryId}/articles`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** POST /api/category → 201 { category } */
|
/** POST /api/category → 201 { category } */
|
||||||
|
// #region POST /api/category — create
|
||||||
create({ parentId, name }) {
|
create({ parentId, name }) {
|
||||||
return http('/api/category', {
|
return http('/api/category', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { parentId: Number(parentId), name },
|
body: { parentId: Number(parentId), name },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/category/:id → 204
|
* DELETE /api/category/:id → 204
|
||||||
* Query: ?recursive=true (cascades deletion of subcategories and articles)
|
* Query: ?recursive=true (cascades deletion of subcategories and articles)
|
||||||
*/
|
*/
|
||||||
|
// #region DELETE /api/category/:id — delete
|
||||||
delete(categoryId, { recursive = false } = {}) {
|
delete(categoryId, { recursive = false } = {}) {
|
||||||
const qs = recursive ? '?recursive=true' : '';
|
const qs = recursive ? '?recursive=true' : '';
|
||||||
return http(`/api/category/${categoryId}${qs}`, { method: 'DELETE' });
|
return http(`/api/category/${categoryId}${qs}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/category/:id
|
* PATCH /api/category/:id
|
||||||
@@ -37,12 +45,14 @@ export const category = {
|
|||||||
* Empty name → 400 name is required.
|
* Empty name → 400 name is required.
|
||||||
* → 200 { category: { id, name, slug } }
|
* → 200 { category: { id, name, slug } }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/category/:id — updateName
|
||||||
updateName(categoryId, name) {
|
updateName(categoryId, name) {
|
||||||
return http(`/api/category/${categoryId}`, {
|
return http(`/api/category/${categoryId}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: { name },
|
body: { name },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/category/order
|
* PUT /api/category/order
|
||||||
@@ -51,24 +61,28 @@ export const category = {
|
|||||||
* set of parentId (same count, no foreign/duplicate ids) → 400 Invalid category order.
|
* set of parentId (same count, no foreign/duplicate ids) → 400 Invalid category order.
|
||||||
* → 200 { categoryIds: number[] }
|
* → 200 { categoryIds: number[] }
|
||||||
*/
|
*/
|
||||||
|
// #region PUT /api/category/order — reorder
|
||||||
reorder(parentId, categoryIds) {
|
reorder(parentId, categoryIds) {
|
||||||
return http('/api/category/order', {
|
return http('/api/category/order', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: { parentId, categoryIds },
|
body: { parentId, categoryIds },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/category/:id
|
* PATCH /api/category/:id
|
||||||
* Body: { parentId } — moves the category under a new parent.
|
* Body: { parentId } — moves the category under a new parent.
|
||||||
* → 200 { category: { id, parentId, sort } } (kein name/slug im Response)
|
* → 200 { category: { id, parentId, sort } } (kein name/slug im Response)
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/category/:id — move
|
||||||
move(categoryId, parentId) {
|
move(categoryId, parentId) {
|
||||||
return http(`/api/category/${categoryId}`, {
|
return http(`/api/category/${categoryId}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: { parentId },
|
body: { parentId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/category/:id
|
* PATCH /api/category/:id
|
||||||
@@ -77,18 +91,21 @@ export const category = {
|
|||||||
*
|
*
|
||||||
* PROPOSED — backend needs to implement this field.
|
* PROPOSED — backend needs to implement this field.
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/category/:id — updatePos
|
||||||
updatePos(categoryId, pos) {
|
updatePos(categoryId, pos) {
|
||||||
return http(`/api/category/${categoryId}`, {
|
return http(`/api/category/${categoryId}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: { pos },
|
body: { pos },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/category/:id/image
|
* POST /api/category/:id/image
|
||||||
* Body: raw image bytes (Content-Type: image/webp)
|
* Body: raw image bytes (Content-Type: image/webp)
|
||||||
* → 201 { category: { id, fullHash, thumbW, thumbH, thumbHash } }
|
* → 201 { category: { id, fullHash, thumbW, thumbH, thumbHash } }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/category/:id/image — uploadImage
|
||||||
uploadImage(categoryId, file) {
|
uploadImage(categoryId, file) {
|
||||||
return httpBinary(`/api/category/${categoryId}/image`, {
|
return httpBinary(`/api/category/${categoryId}/image`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -96,14 +113,17 @@ export const category = {
|
|||||||
body: file,
|
body: file,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/category/:id/image
|
* DELETE /api/category/:id/image
|
||||||
* → 200 { category: { id, thumbW: null, thumbH: null, thumbHash: null } }
|
* → 200 { category: { id, thumbW: null, thumbH: null, thumbHash: null } }
|
||||||
*/
|
*/
|
||||||
|
// #region DELETE /api/category/:id/image — deleteImage
|
||||||
deleteImage(categoryId) {
|
deleteImage(categoryId) {
|
||||||
return http(`/api/category/${categoryId}/image`, { method: 'DELETE' });
|
return http(`/api/category/${categoryId}/image`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/category/export.zip → application/zip (binary download)
|
* GET /api/category/export.zip → application/zip (binary download)
|
||||||
@@ -112,7 +132,9 @@ export const category = {
|
|||||||
* entry per category image (content-addressed, deduplicated).
|
* entry per category image (content-addressed, deduplicated).
|
||||||
* Errors: 400 No company selected, 401, 403, 404 (JSON { error }).
|
* Errors: 400 No company selected, 401, 403, 404 (JSON { error }).
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/category/export.zip — exportZip
|
||||||
exportZip() {
|
exportZip() {
|
||||||
return httpGetBinary('/api/category/export.zip');
|
return httpGetBinary('/api/category/export.zip');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,42 +2,52 @@ import { http } from './http.js';
|
|||||||
|
|
||||||
export const company = {
|
export const company = {
|
||||||
/** GET /api/company → { companies: [...] } */
|
/** GET /api/company → { companies: [...] } */
|
||||||
|
// #region GET /api/company — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/company');
|
return http('/api/company');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** Uses GET /api/company until a dedicated endpoint exists. */
|
/** Uses GET /api/company until a dedicated endpoint exists. */
|
||||||
|
// #region GET /api/company — getById
|
||||||
async getById(companyId) {
|
async getById(companyId) {
|
||||||
const data = await http('/api/company');
|
const data = await http('/api/company');
|
||||||
const found = data.companies?.find((item) => item.id === companyId);
|
const found = data.companies?.find((item) => item.id === companyId);
|
||||||
if (!found) throw new Error('Unternehmen nicht gefunden.');
|
if (!found) throw new Error('Unternehmen nicht gefunden.');
|
||||||
return { company: found };
|
return { company: found };
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region POST /api/company — create
|
||||||
create({ name, iso639, taxrate, reducedtaxrate }) {
|
create({ name, iso639, taxrate, reducedtaxrate }) {
|
||||||
return http('/api/company', {
|
return http('/api/company', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { name, iso639, taxrate, reducedtaxrate },
|
body: { name, iso639, taxrate, reducedtaxrate },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/company/:companyId
|
* PUT /api/company/:companyId
|
||||||
* Body: { name, iso639, taxrate, reducedtaxrate }
|
* Body: { name, iso639, taxrate, reducedtaxrate }
|
||||||
*/
|
*/
|
||||||
|
// #region PUT /api/company/:companyId — update
|
||||||
update(companyId, { name, iso639, taxrate, reducedtaxrate }) {
|
update(companyId, { name, iso639, taxrate, reducedtaxrate }) {
|
||||||
return http(`/api/company/${companyId}`, {
|
return http(`/api/company/${companyId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: { name, iso639, taxrate, reducedtaxrate },
|
body: { name, iso639, taxrate, reducedtaxrate },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region PUT /api/auth/selected-company — select
|
||||||
select(companyId) {
|
select(companyId) {
|
||||||
return http('/api/auth/selected-company', {
|
return http('/api/auth/selected-company', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: { companyId },
|
body: { companyId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/company/import
|
* POST /api/company/import
|
||||||
@@ -48,12 +58,14 @@ export const company = {
|
|||||||
* @param {object} [callbacks]
|
* @param {object} [callbacks]
|
||||||
* @param {(progress: { loaded: number, total: number, percent: number }) => void} [callbacks.onUploadProgress]
|
* @param {(progress: { loaded: number, total: number, percent: number }) => void} [callbacks.onUploadProgress]
|
||||||
* @param {(message: string) => void} [callbacks.onStatus]
|
* @param {(message: string) => void} [callbacks.onStatus]
|
||||||
* @param {(progress: { phase: string, current: number, total: number, item?: string }) => void} [callbacks.onProgress]
|
* @param {(progress: { categoriestotal: number, categoriesdone: number, articlestotal: number, articlesdone: number, imagestotal: number, imagesdone: number, receiptstotal: number, receiptsdone: number }) => void} [callbacks.onProgress] Counter-based progress snapshot across all phases.
|
||||||
|
* @param {(images: string[]) => void} [callbacks.onImages] Batched `images` events (~every 100ms) carrying Base62 SkeleHash strings.
|
||||||
* @param {(stats: object) => void} [callbacks.onDone]
|
* @param {(stats: object) => void} [callbacks.onDone]
|
||||||
* @param {(error: string) => void} [callbacks.onError]
|
* @param {(error: string) => void} [callbacks.onError]
|
||||||
* @param {object} [options]
|
* @param {object} [options]
|
||||||
* @param {AbortSignal} [options.signal]
|
* @param {AbortSignal} [options.signal]
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/company/import — importCatalog
|
||||||
importCatalog(file, callbacks = {}, { signal } = {}) {
|
importCatalog(file, callbacks = {}, { signal } = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
@@ -122,6 +134,21 @@ export const company = {
|
|||||||
case 'progress':
|
case 'progress':
|
||||||
callbacks.onProgress?.(eventData);
|
callbacks.onProgress?.(eventData);
|
||||||
break;
|
break;
|
||||||
|
case 'images': {
|
||||||
|
// Payload is a plain array of Base62 SkeleHash strings;
|
||||||
|
// also tolerate the { images: [...] } wrapper shape.
|
||||||
|
const list = Array.isArray(eventData)
|
||||||
|
? eventData
|
||||||
|
: Array.isArray(eventData?.images)
|
||||||
|
? eventData.images
|
||||||
|
: [];
|
||||||
|
if (list.length) callbacks.onImages?.(list);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'image':
|
||||||
|
// Legacy: single image event (pre-batching).
|
||||||
|
if (eventData) callbacks.onImages?.([eventData]);
|
||||||
|
break;
|
||||||
case 'done':
|
case 'done':
|
||||||
callbacks.onDone?.(eventData?.stats ?? eventData);
|
callbacks.onDone?.(eventData?.stats ?? eventData);
|
||||||
break;
|
break;
|
||||||
@@ -165,4 +192,5 @@ export const company = {
|
|||||||
xhr.send(formData);
|
xhr.send(formData);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ import { http } from './http.js';
|
|||||||
*/
|
*/
|
||||||
export const customergroup = {
|
export const customergroup = {
|
||||||
/** GET /api/customergroup → { customergroups: (CustomerGroup & { editable })[] } ordered by sort */
|
/** GET /api/customergroup → { customergroups: (CustomerGroup & { editable })[] } ordered by sort */
|
||||||
|
// #region GET /api/customergroup — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/customergroup');
|
return http('/api/customergroup');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/customergroup
|
* POST /api/customergroup
|
||||||
@@ -18,18 +20,22 @@ export const customergroup = {
|
|||||||
* reserved POS / Lieferanten ⇒ 400 Name not allowed; sort appends next index.
|
* reserved POS / Lieferanten ⇒ 400 Name not allowed; sort appends next index.
|
||||||
* → 201 { customergroup: CustomerGroup }
|
* → 201 { customergroup: CustomerGroup }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/customergroup — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/customergroup', { method: 'POST', body });
|
return http('/api/customergroup', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/customergroup/:id
|
* PATCH /api/customergroup/:id
|
||||||
* Body: { name } — locked groups cannot be renamed (Name cannot be changed).
|
* Body: { name } — locked groups cannot be renamed (Name cannot be changed).
|
||||||
* → 200 { customergroup: CustomerGroup }
|
* → 200 { customergroup: CustomerGroup }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/customergroup/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/customergroup/${id}`, { method: 'PATCH', body });
|
return http(`/api/customergroup/${id}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/customergroup/order
|
* PUT /api/customergroup/order
|
||||||
@@ -37,12 +43,16 @@ export const customergroup = {
|
|||||||
* else 400 Invalid customer group order. Sets sort to each id’s index 0…n-1.
|
* else 400 Invalid customer group order. Sets sort to each id’s index 0…n-1.
|
||||||
* → 200 { customergroupIds: number[] }
|
* → 200 { customergroupIds: number[] }
|
||||||
*/
|
*/
|
||||||
|
// #region PUT /api/customergroup/order — order
|
||||||
order(body) {
|
order(body) {
|
||||||
return http('/api/customergroup/order', { method: 'PUT', body });
|
return http('/api/customergroup/order', { method: 'PUT', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/customergroup/:id → 204 · 409 Customer group in use */
|
/** DELETE /api/customergroup/:id → 204 · 409 Customer group in use */
|
||||||
|
// #region DELETE /api/customergroup/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/customergroup/${id}`, { method: 'DELETE' });
|
return http(`/api/customergroup/${id}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,14 +11,18 @@ import { http, clientIdHeaders } from './http.js';
|
|||||||
*/
|
*/
|
||||||
export const delivery = {
|
export const delivery = {
|
||||||
/** GET /api/delivery → { deliveries: DeliverySummary[] } */
|
/** GET /api/delivery → { deliveries: DeliverySummary[] } */
|
||||||
|
// #region GET /api/delivery — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/delivery');
|
return http('/api/delivery');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** GET /api/delivery/:id → { delivery: Delivery } */
|
/** GET /api/delivery/:id → { delivery: Delivery } */
|
||||||
|
// #region GET /api/delivery/:id — get
|
||||||
get(id) {
|
get(id) {
|
||||||
return http(`/api/delivery/${id}`);
|
return http(`/api/delivery/${id}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/delivery
|
* POST /api/delivery
|
||||||
@@ -28,9 +32,11 @@ export const delivery = {
|
|||||||
* Line requires sku, amount, net, tax. rebate→0, transactedAmount→amount.
|
* Line requires sku, amount, net, tax. rebate→0, transactedAmount→amount.
|
||||||
* → 201 { delivery: Delivery }
|
* → 201 { delivery: Delivery }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/delivery — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/delivery', { method: 'POST', body });
|
return http('/api/delivery', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/delivery/:id
|
* PATCH /api/delivery/:id
|
||||||
@@ -40,19 +46,24 @@ export const delivery = {
|
|||||||
* If lines present, replaces all lines; if omitted, lines unchanged.
|
* If lines present, replaces all lines; if omitted, lines unchanged.
|
||||||
* → 200 { delivery: Delivery }
|
* → 200 { delivery: Delivery }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/delivery/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/delivery/${id}`, { method: 'PATCH', body });
|
return http(`/api/delivery/${id}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/delivery/:id → 204 */
|
/** DELETE /api/delivery/:id → 204 */
|
||||||
|
// #region DELETE /api/delivery/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/delivery/${id}`, { method: 'DELETE' });
|
return http(`/api/delivery/${id}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/delivery/:id/file/:fileId → Blob
|
* GET /api/delivery/:id/file/:fileId → Blob
|
||||||
* Full binary content of attached file (PDF, text, etc.)
|
* Full binary content of attached file (PDF, text, etc.)
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/delivery/:id/file/:fileId — getFile
|
||||||
async getFile(id, fileId, { download = false } = {}) {
|
async getFile(id, fileId, { download = false } = {}) {
|
||||||
const query = download ? '?download=true' : '';
|
const query = download ? '?download=true' : '';
|
||||||
const res = await fetch(`/api/delivery/${encodeURIComponent(id)}/file/${encodeURIComponent(fileId)}${query}`, {
|
const res = await fetch(`/api/delivery/${encodeURIComponent(id)}/file/${encodeURIComponent(fileId)}${query}`, {
|
||||||
@@ -71,12 +82,14 @@ export const delivery = {
|
|||||||
}
|
}
|
||||||
return res.blob();
|
return res.blob();
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/delivery/parse — multipart/form-data, field "file"
|
* POST /api/delivery/parse — multipart/form-data, field "file"
|
||||||
* Accepts PDF, TXT or CSV document (max 50 MB)
|
* Accepts PDF, TXT or CSV document (max 50 MB)
|
||||||
* Streams SSE events: status, llm_call, tool_call, usage, progress, result, error
|
* Streams SSE events: status, llm_call, tool_call, usage, progress, result, error
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/delivery/parse — parseSse
|
||||||
async *parseSse(file, { signal } = {}) {
|
async *parseSse(file, { signal } = {}) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file, file.name);
|
formData.append('file', file, file.name);
|
||||||
@@ -148,4 +161,5 @@ export const delivery = {
|
|||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const entity = {
|
|||||||
* GET /api/entity → { entities: Entity[] }
|
* GET /api/entity → { entities: Entity[] }
|
||||||
* Optional query: customergroupId
|
* Optional query: customergroupId
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/entity — list
|
||||||
list({ customergroupId } = {}) {
|
list({ customergroupId } = {}) {
|
||||||
const qs =
|
const qs =
|
||||||
customergroupId != null
|
customergroupId != null
|
||||||
@@ -16,83 +17,110 @@ export const entity = {
|
|||||||
: '';
|
: '';
|
||||||
return http(`/api/entity${qs}`);
|
return http(`/api/entity${qs}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** GET /api/entity/:id → { entity: Entity } */
|
/** GET /api/entity/:id → { entity: Entity } */
|
||||||
|
// #region GET /api/entity/:id — get
|
||||||
get(id) {
|
get(id) {
|
||||||
return http(`/api/entity/${id}`);
|
return http(`/api/entity/${id}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/entity
|
* POST /api/entity
|
||||||
* Body: { customergroupId, name, firstName? }
|
* Body: { customergroupId, name, firstName? }
|
||||||
* → 201 { entity: Entity }
|
* → 201 { entity: Entity }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/entity — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/entity', { method: 'POST', body });
|
return http('/api/entity', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/entity/:id
|
* PATCH /api/entity/:id
|
||||||
* Body: { name?, firstName? }
|
* Body: { name?, firstName? }
|
||||||
* → 200 { entity: Entity }
|
* → 200 { entity: Entity }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/entity/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/entity/${id}`, { method: 'PATCH', body });
|
return http(`/api/entity/${id}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/entity/:id → 204 */
|
/** DELETE /api/entity/:id → 204 */
|
||||||
|
// #region DELETE /api/entity/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/entity/${id}`, { method: 'DELETE' });
|
return http(`/api/entity/${id}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** GET /api/entity/next-number → { number: string } */
|
/** GET /api/entity/next-number → { number: string } */
|
||||||
|
// #region GET /api/entity/next-number — nextNumber
|
||||||
nextNumber() {
|
nextNumber() {
|
||||||
return http('/api/entity/next-number');
|
return http('/api/entity/next-number');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** GET /api/entity/:entityId/address → { addresses: EntityAddress[] } */
|
/** GET /api/entity/:entityId/address → { addresses: EntityAddress[] } */
|
||||||
|
// #region GET /api/entity/:entityId/address — listAddresses
|
||||||
listAddresses(entityId) {
|
listAddresses(entityId) {
|
||||||
return http(`/api/entity/${entityId}/address`);
|
return http(`/api/entity/${entityId}/address`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** POST /api/entity/:entityId/address → 201 { address } */
|
/** POST /api/entity/:entityId/address → 201 { address } */
|
||||||
|
// #region POST /api/entity/:entityId/address — createAddress
|
||||||
createAddress(entityId, body) {
|
createAddress(entityId, body) {
|
||||||
return http(`/api/entity/${entityId}/address`, { method: 'POST', body });
|
return http(`/api/entity/${entityId}/address`, { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** PATCH /api/entity/:entityId/address/:id → { address } */
|
/** PATCH /api/entity/:entityId/address/:id → { address } */
|
||||||
|
// #region PATCH /api/entity/:entityId/address/:id — updateAddress
|
||||||
updateAddress(entityId, id, body) {
|
updateAddress(entityId, id, body) {
|
||||||
return http(`/api/entity/${entityId}/address/${id}`, {
|
return http(`/api/entity/${entityId}/address/${id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/entity/:entityId/address/:id → 204 */
|
/** DELETE /api/entity/:entityId/address/:id → 204 */
|
||||||
|
// #region DELETE /api/entity/:entityId/address/:id — deleteAddress
|
||||||
deleteAddress(entityId, id) {
|
deleteAddress(entityId, id) {
|
||||||
return http(`/api/entity/${entityId}/address/${id}`, { method: 'DELETE' });
|
return http(`/api/entity/${entityId}/address/${id}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** GET /api/entity/:entityId/contact → { contacts: EntityContact[] } */
|
/** GET /api/entity/:entityId/contact → { contacts: EntityContact[] } */
|
||||||
|
// #region GET /api/entity/:entityId/contact — listContacts
|
||||||
listContacts(entityId) {
|
listContacts(entityId) {
|
||||||
return http(`/api/entity/${entityId}/contact`);
|
return http(`/api/entity/${entityId}/contact`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** POST /api/entity/:entityId/contact → 201 { contact } */
|
/** POST /api/entity/:entityId/contact → 201 { contact } */
|
||||||
|
// #region POST /api/entity/:entityId/contact — createContact
|
||||||
createContact(entityId, body) {
|
createContact(entityId, body) {
|
||||||
return http(`/api/entity/${entityId}/contact`, { method: 'POST', body });
|
return http(`/api/entity/${entityId}/contact`, { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** PATCH /api/entity/:entityId/contact/:id → { contact } */
|
/** PATCH /api/entity/:entityId/contact/:id → { contact } */
|
||||||
|
// #region PATCH /api/entity/:entityId/contact/:id — updateContact
|
||||||
updateContact(entityId, id, body) {
|
updateContact(entityId, id, body) {
|
||||||
return http(`/api/entity/${entityId}/contact/${id}`, {
|
return http(`/api/entity/${entityId}/contact/${id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/entity/:entityId/contact/:id → 204 */
|
/** DELETE /api/entity/:entityId/contact/:id → 204 */
|
||||||
|
// #region DELETE /api/entity/:entityId/contact/:id — deleteContact
|
||||||
deleteContact(entityId, id) {
|
deleteContact(entityId, id) {
|
||||||
return http(`/api/entity/${entityId}/contact/${id}`, { method: 'DELETE' });
|
return http(`/api/entity/${entityId}/contact/${id}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export const globalHistory = {
|
|||||||
* GET /api/history
|
* GET /api/history
|
||||||
* Global system/entity history with paging/sorting/filtering
|
* Global system/entity history with paging/sorting/filtering
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/history — list
|
||||||
list({ startRow, endRow, offset, limit, sortModel, filterModel } = {}) {
|
list({ startRow, endRow, offset, limit, sortModel, filterModel } = {}) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (startRow != null) params.set('startRow', String(startRow));
|
if (startRow != null) params.set('startRow', String(startRow));
|
||||||
@@ -16,4 +17,5 @@ export const globalHistory = {
|
|||||||
const qs = params.toString();
|
const qs = params.toString();
|
||||||
return http(`/api/history${qs ? `?${qs}` : ''}`);
|
return http(`/api/history${qs ? `?${qs}` : ''}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,37 +6,47 @@ import { http } from './http.js';
|
|||||||
*/
|
*/
|
||||||
export const labeltemplate = {
|
export const labeltemplate = {
|
||||||
/** GET /api/labeltemplate → { labeltemplates: LabelTemplate[] } */
|
/** GET /api/labeltemplate → { labeltemplates: LabelTemplate[] } */
|
||||||
|
// #region GET /api/labeltemplate — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/labeltemplate');
|
return http('/api/labeltemplate');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** GET /api/labeltemplate/:id → { labeltemplate: LabelTemplate } */
|
/** GET /api/labeltemplate/:id → { labeltemplate: LabelTemplate } */
|
||||||
|
// #region GET /api/labeltemplate/:id — get
|
||||||
get(id) {
|
get(id) {
|
||||||
return http(`/api/labeltemplate/${encodeURIComponent(id)}`);
|
return http(`/api/labeltemplate/${encodeURIComponent(id)}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/labeltemplate
|
* POST /api/labeltemplate
|
||||||
* Body: { name, width, height, dpi, type, content }
|
* Body: { name, width, height, dpi, type, content }
|
||||||
* → 201 { labeltemplate: LabelTemplate }
|
* → 201 { labeltemplate: LabelTemplate }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/labeltemplate — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/labeltemplate', { method: 'POST', body });
|
return http('/api/labeltemplate', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/labeltemplate/:id
|
* PATCH /api/labeltemplate/:id
|
||||||
* Body: partial { name?, width?, height?, dpi?, type?, content? }
|
* Body: partial { name?, width?, height?, dpi?, type?, content? }
|
||||||
* → 200 { labeltemplate: LabelTemplate }
|
* → 200 { labeltemplate: LabelTemplate }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/labeltemplate/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/labeltemplate/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
return http(`/api/labeltemplate/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/labeltemplate/:id → 204 */
|
/** DELETE /api/labeltemplate/:id → 204 */
|
||||||
|
// #region DELETE /api/labeltemplate/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/labeltemplate/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
return http(`/api/labeltemplate/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
// thumb is returned inline as a base64 string in list/get responses
|
// thumb is returned inline as a base64 string in list/get responses
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,35 +11,42 @@ export const ledger = {
|
|||||||
* Body: { manifestarticle_id, transaction_type, amount, transaction_text }
|
* Body: { manifestarticle_id, transaction_type, amount, transaction_text }
|
||||||
* → 201 { ledger: LedgerEntry }
|
* → 201 { ledger: LedgerEntry }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/ledger — add
|
||||||
add(body) {
|
add(body) {
|
||||||
return http('/api/ledger', { method: 'POST', body });
|
return http('/api/ledger', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/ledger/manifestarticle/:manifestarticleId
|
* GET /api/ledger/manifestarticle/:manifestarticleId
|
||||||
* Query: ?transaction_type=...
|
* Query: ?transaction_type=...
|
||||||
* → 200 { ledger: LedgerEntry[] }
|
* → 200 { ledger: LedgerEntry[] }
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/ledger/manifestarticle/:manifestarticleId — listByManifestarticleId
|
||||||
listByManifestarticleId(manifestarticleId, transactionType) {
|
listByManifestarticleId(manifestarticleId, transactionType) {
|
||||||
const query = transactionType ? `?transaction_type=${encodeURIComponent(transactionType)}` : '';
|
const query = transactionType ? `?transaction_type=${encodeURIComponent(transactionType)}` : '';
|
||||||
return http(`/api/ledger/manifestarticle/${encodeURIComponent(manifestarticleId)}${query}`);
|
return http(`/api/ledger/manifestarticle/${encodeURIComponent(manifestarticleId)}${query}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/ledger/sku/:sku
|
* GET /api/ledger/sku/:sku
|
||||||
* Query: ?transaction_type=...
|
* Query: ?transaction_type=...
|
||||||
* → 200 { ledger: LedgerEntry[] }
|
* → 200 { ledger: LedgerEntry[] }
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/ledger/sku/:sku — listBySku
|
||||||
listBySku(sku, transactionType) {
|
listBySku(sku, transactionType) {
|
||||||
const query = transactionType ? `?transaction_type=${encodeURIComponent(transactionType)}` : '';
|
const query = transactionType ? `?transaction_type=${encodeURIComponent(transactionType)}` : '';
|
||||||
return http(`/api/ledger/sku/${encodeURIComponent(sku)}${query}`);
|
return http(`/api/ledger/sku/${encodeURIComponent(sku)}${query}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/ledger
|
* GET /api/ledger
|
||||||
* Query: { manifestarticle_id?, sku?, transaction_type? }
|
* Query: { manifestarticle_id?, sku?, transaction_type? }
|
||||||
* → 200 { ledger: LedgerEntry[] }
|
* → 200 { ledger: LedgerEntry[] }
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/ledger — list
|
||||||
list(query) {
|
list(query) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (query?.manifestarticle_id != null) params.set('manifestarticle_id', query.manifestarticle_id);
|
if (query?.manifestarticle_id != null) params.set('manifestarticle_id', query.manifestarticle_id);
|
||||||
@@ -50,4 +57,5 @@ export const ledger = {
|
|||||||
const qs = params.toString();
|
const qs = params.toString();
|
||||||
return http(`/api/ledger${qs ? `?${qs}` : ''}`);
|
return http(`/api/ledger${qs ? `?${qs}` : ''}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,18 +6,22 @@ export const modelconversation = {
|
|||||||
* Lists the user's model conversations in the selected company.
|
* Lists the user's model conversations in the selected company.
|
||||||
* → 200 { conversations: [{ id, companyuser_id, provider, model, created_at, updated_at, state }] }
|
* → 200 { conversations: [{ id, companyuser_id, provider, model, created_at, updated_at, state }] }
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/modelconversation — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/modelconversation');
|
return http('/api/modelconversation');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/modelconversation/:id
|
* GET /api/modelconversation/:id
|
||||||
* Fetch conversation details by ID.
|
* Fetch conversation details by ID.
|
||||||
* @param {number|string} id
|
* @param {number|string} id
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/modelconversation/:id — get
|
||||||
get(id) {
|
get(id) {
|
||||||
return http(`/api/modelconversation/${encodeURIComponent(id)}`);
|
return http(`/api/modelconversation/${encodeURIComponent(id)}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/modelconversation
|
* POST /api/modelconversation
|
||||||
@@ -25,27 +29,35 @@ export const modelconversation = {
|
|||||||
* Body: { provider, model }
|
* Body: { provider, model }
|
||||||
* → 201 { conversation: { id, companyuser_id, provider, model, created_at, updated_at, state } }
|
* → 201 { conversation: { id, companyuser_id, provider, model, created_at, updated_at, state } }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/modelconversation — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/modelconversation', { method: 'POST', body });
|
return http('/api/modelconversation', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/modelconversation/:id/message
|
* GET /api/modelconversation/:id/message
|
||||||
* Lists conversation messages, oldest first.
|
* Lists conversation messages, oldest first.
|
||||||
* → 200 { messages: [{ id, conversation_id, created_at, role, message }] }
|
* → 200 { messages: [{ id, conversation_id, created_at, role, message }] }
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/modelconversation/:id/message — listMessages
|
||||||
listMessages(id) {
|
listMessages(id) {
|
||||||
return http(`/api/modelconversation/${encodeURIComponent(id)}/message`);
|
return http(`/api/modelconversation/${encodeURIComponent(id)}/message`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region POST /api/modelconversation/:id/message — addMessage
|
||||||
addMessage(id, body) {
|
addMessage(id, body) {
|
||||||
return http(`/api/modelconversation/${encodeURIComponent(id)}/message`, { method: 'POST', body });
|
return http(`/api/modelconversation/${encodeURIComponent(id)}/message`, { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/modelconversation/:id
|
* DELETE /api/modelconversation/:id
|
||||||
*/
|
*/
|
||||||
|
// #region DELETE /api/modelconversation/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/modelconversation/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
return http(`/api/modelconversation/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,16 +5,20 @@ export const number = {
|
|||||||
* GET /api/number → { numbers: [{ id, type, prefix, padding, next }] }
|
* GET /api/number → { numbers: [{ id, type, prefix, padding, next }] }
|
||||||
* Lists the number sequences for the selected company.
|
* Lists the number sequences for the selected company.
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/number — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/number');
|
return http('/api/number');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/number/:id
|
* PATCH /api/number/:id
|
||||||
* Body: at least one of { prefix, padding, next } — only present keys are patched.
|
* Body: at least one of { prefix, padding, next } — only present keys are patched.
|
||||||
* → 200 { number: { id, type, prefix, padding, next } }
|
* → 200 { number: { id, type, prefix, padding, next } }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/number/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/number/${id}`, { method: 'PATCH', body });
|
return http(`/api/number/${id}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const order = {
|
|||||||
* @param {{ offset?: number, limit?: number }} [params]
|
* @param {{ offset?: number, limit?: number }} [params]
|
||||||
* → { orders: Order[], total: number, offset: number, limit: number }
|
* → { orders: Order[], total: number, offset: number, limit: number }
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/order — list
|
||||||
list({ offset = 0, limit = 20 } = {}) {
|
list({ offset = 0, limit = 20 } = {}) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (offset != null) params.set('offset', String(offset));
|
if (offset != null) params.set('offset', String(offset));
|
||||||
@@ -18,32 +19,41 @@ export const order = {
|
|||||||
const qs = params.toString();
|
const qs = params.toString();
|
||||||
return http(`/api/order${qs ? `?${qs}` : ''}`);
|
return http(`/api/order${qs ? `?${qs}` : ''}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** GET /api/order/:id → { order: Order } */
|
/** GET /api/order/:id → { order: Order } */
|
||||||
|
// #region GET /api/order/:id — get
|
||||||
get(id) {
|
get(id) {
|
||||||
return http(`/api/order/${id}`);
|
return http(`/api/order/${id}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/order
|
* POST /api/order
|
||||||
* Body: { number, date, entityId?, lines }
|
* Body: { number, date, entityId?, lines }
|
||||||
* → 201 { order: Order }
|
* → 201 { order: Order }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/order — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/order', { method: 'POST', body });
|
return http('/api/order', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/order/:id
|
* PATCH /api/order/:id
|
||||||
* Body: partial { number?, date?, entityId?, lines? }
|
* Body: partial { number?, date?, entityId?, lines? }
|
||||||
* → 200 { order: Order }
|
* → 200 { order: Order }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/order/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/order/${id}`, { method: 'PATCH', body });
|
return http(`/api/order/${id}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/order/:id → 204 */
|
/** DELETE /api/order/:id → 204 */
|
||||||
|
// #region DELETE /api/order/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/order/${id}`, { method: 'DELETE' });
|
return http(`/api/order/${id}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ export const posterminal = {
|
|||||||
* GET /api/posterminal → { posterminals: [{ id, name, token, pairingpin }] }
|
* GET /api/posterminal → { posterminals: [{ id, name, token, pairingpin }] }
|
||||||
* List all terminals for the selected company.
|
* List all terminals for the selected company.
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/posterminal — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/posterminal');
|
return http('/api/posterminal');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/posterminal
|
* PUT /api/posterminal
|
||||||
@@ -15,12 +17,14 @@ export const posterminal = {
|
|||||||
* Creates a new terminal (auto-generates 6-digit pin).
|
* Creates a new terminal (auto-generates 6-digit pin).
|
||||||
* → 200 { posterminal: { id, name, token, pairingpin } }
|
* → 200 { posterminal: { id, name, token, pairingpin } }
|
||||||
*/
|
*/
|
||||||
|
// #region PUT /api/posterminal — create
|
||||||
create({ name }) {
|
create({ name }) {
|
||||||
return http('/api/posterminal', {
|
return http('/api/posterminal', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: { name },
|
body: { name },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/posterminal/:id
|
* PATCH /api/posterminal/:id
|
||||||
@@ -28,64 +32,76 @@ export const posterminal = {
|
|||||||
* Update posterminal configuration.
|
* Update posterminal configuration.
|
||||||
* → 200 { posterminal }
|
* → 200 { posterminal }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/posterminal/:id — update
|
||||||
update(terminalId, patch) {
|
update(terminalId, patch) {
|
||||||
return http(`/api/posterminal/${terminalId}`, {
|
return http(`/api/posterminal/${terminalId}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
body: patch,
|
body: patch,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/posterminal/:id → 204
|
* DELETE /api/posterminal/:id → 204
|
||||||
* Deletes a terminal.
|
* Deletes a terminal.
|
||||||
*/
|
*/
|
||||||
|
// #region DELETE /api/posterminal/:id — delete
|
||||||
delete(terminalId) {
|
delete(terminalId) {
|
||||||
return http(`/api/posterminal/${terminalId}`, {
|
return http(`/api/posterminal/${terminalId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/posterminal/:id/category/:categoryId
|
* PUT /api/posterminal/:id/category/:categoryId
|
||||||
* Enable a category on a terminal.
|
* Enable a category on a terminal.
|
||||||
* → 200 { id: number, categoryIds: number[] }
|
* → 200 { id: number, categoryIds: number[] }
|
||||||
*/
|
*/
|
||||||
|
// #region PUT /api/posterminal/:id/category/:categoryId — enableCategory
|
||||||
enableCategory(terminalId, categoryId) {
|
enableCategory(terminalId, categoryId) {
|
||||||
return http(`/api/posterminal/${terminalId}/category/${categoryId}`, {
|
return http(`/api/posterminal/${terminalId}/category/${categoryId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/posterminal/:id/category/:categoryId
|
* DELETE /api/posterminal/:id/category/:categoryId
|
||||||
* Disable a category on a terminal.
|
* Disable a category on a terminal.
|
||||||
* → 200 { id: number, categoryIds: number[] }
|
* → 200 { id: number, categoryIds: number[] }
|
||||||
*/
|
*/
|
||||||
|
// #region DELETE /api/posterminal/:id/category/:categoryId — disableCategory
|
||||||
disableCategory(terminalId, categoryId) {
|
disableCategory(terminalId, categoryId) {
|
||||||
return http(`/api/posterminal/${terminalId}/category/${categoryId}`, {
|
return http(`/api/posterminal/${terminalId}/category/${categoryId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/posterminal/:id/all
|
* PUT /api/posterminal/:id/all
|
||||||
* Enable all categories on a terminal.
|
* Enable all categories on a terminal.
|
||||||
* → 200 { id: number, categoryIds: number[] }
|
* → 200 { id: number, categoryIds: number[] }
|
||||||
*/
|
*/
|
||||||
|
// #region PUT /api/posterminal/:id/all — enableAllCategories
|
||||||
enableAllCategories(terminalId) {
|
enableAllCategories(terminalId) {
|
||||||
return http(`/api/posterminal/${terminalId}/all`, {
|
return http(`/api/posterminal/${terminalId}/all`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/posterminal/:id/all
|
* DELETE /api/posterminal/:id/all
|
||||||
* Disable all categories on a terminal.
|
* Disable all categories on a terminal.
|
||||||
* → 200 { id: number, categoryIds: number[] }
|
* → 200 { id: number, categoryIds: number[] }
|
||||||
*/
|
*/
|
||||||
|
// #region DELETE /api/posterminal/:id/all — disableAllCategories
|
||||||
disableAllCategories(terminalId) {
|
disableAllCategories(terminalId) {
|
||||||
return http(`/api/posterminal/${terminalId}/all`, {
|
return http(`/api/posterminal/${terminalId}/all`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,44 +6,56 @@ import { http } from './http.js';
|
|||||||
*/
|
*/
|
||||||
export const printer = {
|
export const printer = {
|
||||||
/** GET /api/printer → { printers: Printer[] } */
|
/** GET /api/printer → { printers: Printer[] } */
|
||||||
|
// #region GET /api/printer — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/printer');
|
return http('/api/printer');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** GET /api/printer/:id → { printer: Printer } */
|
/** GET /api/printer/:id → { printer: Printer } */
|
||||||
|
// #region GET /api/printer/:id — getById
|
||||||
getById(id) {
|
getById(id) {
|
||||||
return http(`/api/printer/${id}`);
|
return http(`/api/printer/${id}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/printer
|
* POST /api/printer
|
||||||
* Body: { address, type, model, serial, sort, desc }
|
* Body: { address, type, model, serial, sort, desc }
|
||||||
* → 201 { printer: Printer }
|
* → 201 { printer: Printer }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/printer — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/printer', { method: 'POST', body });
|
return http('/api/printer', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/printer/:id
|
* PATCH /api/printer/:id
|
||||||
* Body: partial printer fields
|
* Body: partial printer fields
|
||||||
* → 200 { printer: Printer }
|
* → 200 { printer: Printer }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/printer/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/printer/${id}`, { method: 'PATCH', body });
|
return http(`/api/printer/${id}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/printer/:id → 204 */
|
/** DELETE /api/printer/:id → 204 */
|
||||||
|
// #region DELETE /api/printer/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/printer/${id}`, { method: 'DELETE' });
|
return http(`/api/printer/${id}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/printer/order
|
* PUT /api/printer/order
|
||||||
* Body: { printerIds: number[] }
|
* Body: { printerIds: number[] }
|
||||||
* → 200 { printerIds: number[] }
|
* → 200 { printerIds: number[] }
|
||||||
*/
|
*/
|
||||||
|
// #region PUT /api/printer/order — order
|
||||||
order({ printerIds }) {
|
order({ printerIds }) {
|
||||||
return http('/api/printer/order', { method: 'PUT', body: { printerIds } });
|
return http('/api/printer/order', { method: 'PUT', body: { printerIds } });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,27 +2,37 @@ import { http } from './http.js';
|
|||||||
|
|
||||||
export const propertyname = {
|
export const propertyname = {
|
||||||
/** GET /api/propertyname → { propertynames: Propertyname[] } */
|
/** GET /api/propertyname → { propertynames: Propertyname[] } */
|
||||||
|
// #region GET /api/propertyname — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/propertyname');
|
return http('/api/propertyname');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** POST /api/propertyname → 201 { propertyname } */
|
/** POST /api/propertyname → 201 { propertyname } */
|
||||||
|
// #region POST /api/propertyname — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/propertyname', { method: 'POST', body });
|
return http('/api/propertyname', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** PATCH /api/propertyname/:id → 200 { propertyname } */
|
/** PATCH /api/propertyname/:id → 200 { propertyname } */
|
||||||
|
// #region PATCH /api/propertyname/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/propertyname/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
return http(`/api/propertyname/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/propertyname/:id → 200 */
|
/** DELETE /api/propertyname/:id → 200 */
|
||||||
|
// #region DELETE /api/propertyname/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/propertyname/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
return http(`/api/propertyname/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** PUT /api/propertyname/order → 200 */
|
/** PUT /api/propertyname/order → 200 */
|
||||||
|
// #region PUT /api/propertyname/order — order
|
||||||
order(propertynameIds) {
|
order(propertynameIds) {
|
||||||
return http('/api/propertyname/order', { method: 'PUT', body: { propertynameIds } });
|
return http('/api/propertyname/order', { method: 'PUT', body: { propertynameIds } });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,35 +8,45 @@ import { http } from './http.js';
|
|||||||
*/
|
*/
|
||||||
export const quote = {
|
export const quote = {
|
||||||
/** GET /api/quote → { quotes: QuoteSummary[] } */
|
/** GET /api/quote → { quotes: QuoteSummary[] } */
|
||||||
|
// #region GET /api/quote — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/quote');
|
return http('/api/quote');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** GET /api/quote/:id → { quote: Quote } */
|
/** GET /api/quote/:id → { quote: Quote } */
|
||||||
|
// #region GET /api/quote/:id — get
|
||||||
get(id) {
|
get(id) {
|
||||||
return http(`/api/quote/${id}`);
|
return http(`/api/quote/${id}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/quote
|
* POST /api/quote
|
||||||
* Body: { number, date, entityId?, lines }
|
* Body: { number, date, entityId?, lines }
|
||||||
* → 201 { quote: Quote }
|
* → 201 { quote: Quote }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/quote — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/quote', { method: 'POST', body });
|
return http('/api/quote', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/quote/:id
|
* PATCH /api/quote/:id
|
||||||
* Body: partial { number?, date?, entityId?, lines? }
|
* Body: partial { number?, date?, entityId?, lines? }
|
||||||
* → 200 { quote: Quote }
|
* → 200 { quote: Quote }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/quote/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/quote/${id}`, { method: 'PATCH', body });
|
return http(`/api/quote/${id}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/quote/:id → 204 */
|
/** DELETE /api/quote/:id → 204 */
|
||||||
|
// #region DELETE /api/quote/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/quote/${id}`, { method: 'DELETE' });
|
return http(`/api/quote/${id}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,24 +9,30 @@ export const setting = {
|
|||||||
* GET /api/company/setting
|
* GET /api/company/setting
|
||||||
* Optional query: { frontendOnly } → 200 { settings: Setting[] }
|
* Optional query: { frontendOnly } → 200 { settings: Setting[] }
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/company/setting — list
|
||||||
list({ frontendOnly } = {}) {
|
list({ frontendOnly } = {}) {
|
||||||
const qs = frontendOnly ? '?frontendOnly=1' : '';
|
const qs = frontendOnly ? '?frontendOnly=1' : '';
|
||||||
return http(`/api/company/setting${qs}`);
|
return http(`/api/company/setting${qs}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** GET /api/company/setting/:key → { setting: Setting } */
|
/** GET /api/company/setting/:key → { setting: Setting } */
|
||||||
|
// #region GET /api/company/setting/:key — get
|
||||||
get(key) {
|
get(key) {
|
||||||
return http(`/api/company/setting/${encodeURIComponent(key)}`);
|
return http(`/api/company/setting/${encodeURIComponent(key)}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/company/setting/:key
|
* PUT /api/company/setting/:key
|
||||||
* Body: { value, frontend } → 200 { setting: Setting }
|
* Body: { value, frontend } → 200 { setting: Setting }
|
||||||
*/
|
*/
|
||||||
|
// #region PUT /api/company/setting/:key — upsert
|
||||||
upsert(key, body) {
|
upsert(key, body) {
|
||||||
return http(`/api/company/setting/${encodeURIComponent(key)}`, {
|
return http(`/api/company/setting/${encodeURIComponent(key)}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ export const stock = {
|
|||||||
* POST /api/stock/adjust
|
* POST /api/stock/adjust
|
||||||
* Body: { sku, warehouseId, amount, text?, date?, externalNumber?, lines? }
|
* Body: { sku, warehouseId, amount, text?, date?, externalNumber?, lines? }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/stock/adjust — adjust
|
||||||
adjust(body) {
|
adjust(body) {
|
||||||
return http('/api/stock/adjust', { method: 'POST', body });
|
return http('/api/stock/adjust', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,37 +6,46 @@ import { http } from './http.js';
|
|||||||
*/
|
*/
|
||||||
export const supplier = {
|
export const supplier = {
|
||||||
/** GET /api/supplier → { suppliers: Supplier[] } */
|
/** GET /api/supplier → { suppliers: Supplier[] } */
|
||||||
|
// #region GET /api/supplier — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/supplier');
|
return http('/api/supplier');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/supplier
|
* POST /api/supplier
|
||||||
* Body: { name } — empty name ⇒ 400 Name required.
|
* Body: { name } — empty name ⇒ 400 Name required.
|
||||||
* → 201 { supplier: Supplier }
|
* → 201 { supplier: Supplier }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/supplier — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/supplier', { method: 'POST', body });
|
return http('/api/supplier', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/supplier/:id
|
* PATCH /api/supplier/:id
|
||||||
* Body: { name } — empty name ⇒ 400 Name required.
|
* Body: { name } — empty name ⇒ 400 Name required.
|
||||||
* → 200 { supplier: Supplier }
|
* → 200 { supplier: Supplier }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/supplier/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/supplier/${id}`, { method: 'PATCH', body });
|
return http(`/api/supplier/${id}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/supplier/:id → 204 */
|
/** DELETE /api/supplier/:id → 204 */
|
||||||
|
// #region DELETE /api/supplier/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/supplier/${id}`, { method: 'DELETE' });
|
return http(`/api/supplier/${id}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/supplier/:id/articles?skus=...
|
* GET /api/supplier/:id/articles?skus=...
|
||||||
* → 200 { articles: SupplierArticle[] }
|
* → 200 { articles: SupplierArticle[] }
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/supplier/:id/articles — getArticles
|
||||||
getArticles(id, skus) {
|
getArticles(id, skus) {
|
||||||
let query = '';
|
let query = '';
|
||||||
if (skus != null) {
|
if (skus != null) {
|
||||||
@@ -48,4 +57,5 @@ export const supplier = {
|
|||||||
}
|
}
|
||||||
return http(`/api/supplier/${encodeURIComponent(id)}/articles${query}`);
|
return http(`/api/supplier/${encodeURIComponent(id)}/articles${query}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ export const thumb = {
|
|||||||
* GET /api/thumb/:fullHash → image/avif (thumb bytes)
|
* GET /api/thumb/:fullHash → image/avif (thumb bytes)
|
||||||
* Use as <img src={thumb.url(fullHash)} /> (same-origin; cookies sent).
|
* Use as <img src={thumb.url(fullHash)} /> (same-origin; cookies sent).
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/thumb/:fullHash — url
|
||||||
url(fullHash) {
|
url(fullHash) {
|
||||||
return `/api/thumb/${encodeURIComponent(fullHash)}`;
|
return `/api/thumb/${encodeURIComponent(fullHash)}`;
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,25 +6,31 @@ import { http } from './http.js';
|
|||||||
*/
|
*/
|
||||||
export const unit = {
|
export const unit = {
|
||||||
/** GET /api/unit → { units: [...] } · 400 No company selected */
|
/** GET /api/unit → { units: [...] } · 400 No company selected */
|
||||||
|
// #region GET /api/unit — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/unit');
|
return http('/api/unit');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/unit
|
* PUT /api/unit
|
||||||
* Body: { si, basesi, basesifactor, symbol, name }
|
* Body: { si, basesi, basesifactor, symbol, name }
|
||||||
* → 200 { unit }
|
* → 200 { unit }
|
||||||
*/
|
*/
|
||||||
|
// #region PUT /api/unit — upsert
|
||||||
upsert(body) {
|
upsert(body) {
|
||||||
return http('/api/unit', { method: 'PUT', body });
|
return http('/api/unit', { method: 'PUT', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/unit?si=… → 204
|
* DELETE /api/unit?si=… → 204
|
||||||
* 409 Unit in use
|
* 409 Unit in use
|
||||||
*/
|
*/
|
||||||
|
// #region DELETE /api/unit?si=… — delete
|
||||||
delete(si) {
|
delete(si) {
|
||||||
const params = new URLSearchParams({ si });
|
const params = new URLSearchParams({ si });
|
||||||
return http(`/api/unit?${params}`, { method: 'DELETE' });
|
return http(`/api/unit?${params}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,15 +6,18 @@ export const vfs = {
|
|||||||
* List files in the virtual filesystem for the selected company.
|
* List files in the virtual filesystem for the selected company.
|
||||||
* → 200 { files: [{ name, size, mime, updated_at, created_at, isText, isImage, isPdf }] }
|
* → 200 { files: [{ name, size, mime, updated_at, created_at, isText, isImage, isPdf }] }
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/vfs — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/vfs');
|
return http('/api/vfs');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/vfs/*path
|
* GET /api/vfs/*path
|
||||||
* Fetch raw content of a file (e.g. text for the editor).
|
* Fetch raw content of a file (e.g. text for the editor).
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
*/
|
*/
|
||||||
|
// #region GET /api/vfs/*path — getText
|
||||||
async getText(path) {
|
async getText(path) {
|
||||||
const clean = String(path || '').replace(/^\/+/, '');
|
const clean = String(path || '').replace(/^\/+/, '');
|
||||||
const res = await fetch(`/api/vfs/${clean}?raw=1`, {
|
const res = await fetch(`/api/vfs/${clean}?raw=1`, {
|
||||||
@@ -25,6 +28,7 @@ export const vfs = {
|
|||||||
}
|
}
|
||||||
return res.text();
|
return res.text();
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/vfs/*path
|
* PUT /api/vfs/*path
|
||||||
@@ -33,6 +37,7 @@ export const vfs = {
|
|||||||
* @param {string} content
|
* @param {string} content
|
||||||
* @param {string} [mime]
|
* @param {string} [mime]
|
||||||
*/
|
*/
|
||||||
|
// #region PUT /api/vfs/*path — save
|
||||||
save(path, content, mime) {
|
save(path, content, mime) {
|
||||||
const clean = String(path || '').replace(/^\/+/, '');
|
const clean = String(path || '').replace(/^\/+/, '');
|
||||||
return http(`/api/vfs/${clean}`, {
|
return http(`/api/vfs/${clean}`, {
|
||||||
@@ -40,18 +45,21 @@ export const vfs = {
|
|||||||
body: { content, mime },
|
body: { content, mime },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/vfs/*path
|
* DELETE /api/vfs/*path
|
||||||
* Delete a file.
|
* Delete a file.
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
*/
|
*/
|
||||||
|
// #region DELETE /api/vfs/*path — delete
|
||||||
delete(path) {
|
delete(path) {
|
||||||
const clean = String(path || '').replace(/^\/+/, '');
|
const clean = String(path || '').replace(/^\/+/, '');
|
||||||
return http(`/api/vfs/${clean}`, {
|
return http(`/api/vfs/${clean}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/vfs/upload
|
* POST /api/vfs/upload
|
||||||
@@ -59,6 +67,7 @@ export const vfs = {
|
|||||||
* @param {File[]|FileList|File} files
|
* @param {File[]|FileList|File} files
|
||||||
* @param {string} [targetDir]
|
* @param {string} [targetDir]
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/vfs/upload — upload
|
||||||
async upload(files, targetDir = '') {
|
async upload(files, targetDir = '') {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
if (targetDir) formData.append('targetDir', targetDir);
|
if (targetDir) formData.append('targetDir', targetDir);
|
||||||
@@ -78,12 +87,14 @@ export const vfs = {
|
|||||||
}
|
}
|
||||||
return res.json();
|
return res.json();
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/vfs/mkdir
|
* POST /api/vfs/mkdir
|
||||||
* Create a directory in VFS.
|
* Create a directory in VFS.
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/vfs/mkdir — mkdir
|
||||||
mkdir(path) {
|
mkdir(path) {
|
||||||
const clean = String(path || '').replace(/^\/+/, '');
|
const clean = String(path || '').replace(/^\/+/, '');
|
||||||
return http('/api/vfs/mkdir', {
|
return http('/api/vfs/mkdir', {
|
||||||
@@ -91,6 +102,7 @@ export const vfs = {
|
|||||||
body: { path: clean },
|
body: { path: clean },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/vfs/rename
|
* POST /api/vfs/rename
|
||||||
@@ -98,6 +110,7 @@ export const vfs = {
|
|||||||
* @param {string} oldPath
|
* @param {string} oldPath
|
||||||
* @param {string} newPath
|
* @param {string} newPath
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/vfs/rename — rename
|
||||||
rename(oldPath, newPath) {
|
rename(oldPath, newPath) {
|
||||||
const cleanOld = String(oldPath || '').replace(/^\/+/, '');
|
const cleanOld = String(oldPath || '').replace(/^\/+/, '');
|
||||||
const cleanNew = String(newPath || '').replace(/^\/+/, '');
|
const cleanNew = String(newPath || '').replace(/^\/+/, '');
|
||||||
@@ -106,6 +119,7 @@ export const vfs = {
|
|||||||
body: { oldPath: cleanOld, newPath: cleanNew },
|
body: { oldPath: cleanOld, newPath: cleanNew },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/vfs/*path — run a `.applet.js` file from VFS.
|
* POST /api/vfs/*path — run a `.applet.js` file from VFS.
|
||||||
@@ -116,6 +130,7 @@ export const vfs = {
|
|||||||
* `result` is a typed CSV string (`;`-separated, header row first), `columns` are the
|
* `result` is a typed CSV string (`;`-separated, header row first), `columns` are the
|
||||||
* declared `{ name, type }` pairs, `rows` is the data-row count.
|
* declared `{ name, type }` pairs, `rows` is the data-row count.
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/vfs/*path — runApplet
|
||||||
runApplet(path, options = {}) {
|
runApplet(path, options = {}) {
|
||||||
const clean = String(path || '').replace(/^\/+/, '');
|
const clean = String(path || '').replace(/^\/+/, '');
|
||||||
const body = {};
|
const body = {};
|
||||||
@@ -127,4 +142,5 @@ export const vfs = {
|
|||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,44 +6,56 @@ import { http } from './http.js';
|
|||||||
*/
|
*/
|
||||||
export const warehouse = {
|
export const warehouse = {
|
||||||
/** GET /api/warehouse → { warehouses: Warehouse[] } */
|
/** GET /api/warehouse → { warehouses: Warehouse[] } */
|
||||||
|
// #region GET /api/warehouse — list
|
||||||
list() {
|
list() {
|
||||||
return http('/api/warehouse');
|
return http('/api/warehouse');
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** GET /api/warehouse/:id → { warehouse: Warehouse } */
|
/** GET /api/warehouse/:id → { warehouse: Warehouse } */
|
||||||
|
// #region GET /api/warehouse/:id — getById
|
||||||
getById(id) {
|
getById(id) {
|
||||||
return http(`/api/warehouse/${id}`);
|
return http(`/api/warehouse/${id}`);
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/warehouse
|
* POST /api/warehouse
|
||||||
* Body: { name, sort? } — empty name ⇒ 400 Name required.
|
* Body: { name, sort? } — empty name ⇒ 400 Name required.
|
||||||
* → 201 { warehouse: Warehouse }
|
* → 201 { warehouse: Warehouse }
|
||||||
*/
|
*/
|
||||||
|
// #region POST /api/warehouse — create
|
||||||
create(body) {
|
create(body) {
|
||||||
return http('/api/warehouse', { method: 'POST', body });
|
return http('/api/warehouse', { method: 'POST', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/warehouse/:id
|
* PATCH /api/warehouse/:id
|
||||||
* Body: { name?, sort? }
|
* Body: { name?, sort? }
|
||||||
* → 200 { warehouse: Warehouse }
|
* → 200 { warehouse: Warehouse }
|
||||||
*/
|
*/
|
||||||
|
// #region PATCH /api/warehouse/:id — update
|
||||||
update(id, body) {
|
update(id, body) {
|
||||||
return http(`/api/warehouse/${id}`, { method: 'PATCH', body });
|
return http(`/api/warehouse/${id}`, { method: 'PATCH', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PUT /api/warehouse/order
|
* PUT /api/warehouse/order
|
||||||
* Body: { warehouseIds: number[] }
|
* Body: { warehouseIds: number[] }
|
||||||
* → 200 { warehouseIds: number[] }
|
* → 200 { warehouseIds: number[] }
|
||||||
*/
|
*/
|
||||||
|
// #region PUT /api/warehouse/order — order
|
||||||
order(body) {
|
order(body) {
|
||||||
return http('/api/warehouse/order', { method: 'PUT', body });
|
return http('/api/warehouse/order', { method: 'PUT', body });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
|
|
||||||
/** DELETE /api/warehouse/:id → 204 */
|
/** DELETE /api/warehouse/:id → 204 */
|
||||||
|
// #region DELETE /api/warehouse/:id — delete
|
||||||
delete(id) {
|
delete(id) {
|
||||||
return http(`/api/warehouse/${id}`, { method: 'DELETE' });
|
return http(`/api/warehouse/${id}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
// #endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|||||||
import { matchPath, useLocation } from 'react-router-dom';
|
import { matchPath, useLocation } from 'react-router-dom';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Tooltip from '@mui/material/Tooltip';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import MapRoundedIcon from '@mui/icons-material/MapRounded';
|
import MapRoundedIcon from '@mui/icons-material/MapRounded';
|
||||||
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
|
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
|
||||||
import { ARTICLE_TAB, readPreferredArticleTab } from './article/articleRoute';
|
import { ARTICLE_TAB, readPreferredArticleTab } from './article/articleRoute';
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ import categoryAutomat from '../../automats/categoryAutomat';
|
|||||||
import articleIndexAutomat from '../../automats/articleIndexAutomat';
|
import articleIndexAutomat from '../../automats/articleIndexAutomat';
|
||||||
import brandAutomat from '../../automats/brandAutomat';
|
import brandAutomat from '../../automats/brandAutomat';
|
||||||
import { orderAutomat } from '../../automats/orderAutomat';
|
import { orderAutomat } from '../../automats/orderAutomat';
|
||||||
|
import SkeleHashImage from './SkeleHashImage';
|
||||||
|
|
||||||
|
// Keep only the most recent N image events in state to avoid unbounded growth
|
||||||
|
// on large imports (thousands of images).
|
||||||
|
const MAX_STREAMED_IMAGES = 60;
|
||||||
|
|
||||||
function formatBytes(bytes) {
|
function formatBytes(bytes) {
|
||||||
if (bytes == null || isNaN(bytes) || bytes === 0) return '0 B';
|
if (bytes == null || isNaN(bytes) || bytes === 0) return '0 B';
|
||||||
@@ -42,14 +47,18 @@ function isZipFile(file) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PHASE_LABELS = {
|
function shortHash(hash) {
|
||||||
brands: 'Marken werden importiert…',
|
if (typeof hash !== 'string') return '';
|
||||||
categories: 'Kategorien werden importiert…',
|
return hash.length > 10 ? `${hash.slice(0, 8)}…` : hash;
|
||||||
articles: 'Artikel werden abgeglichen…',
|
}
|
||||||
variants: 'Varianten werden verknüpft…',
|
|
||||||
images: 'Bilder werden optimiert…',
|
// Counter-based phases emitted by the `progress` event.
|
||||||
orders: 'Aufträge werden importiert…',
|
const PROGRESS_PHASES = [
|
||||||
};
|
{ key: 'categories', label: 'Kategorien', totalKey: 'categoriestotal', doneKey: 'categoriesdone' },
|
||||||
|
{ key: 'articles', label: 'Artikel', totalKey: 'articlestotal', doneKey: 'articlesdone' },
|
||||||
|
{ key: 'images', label: 'Medien', totalKey: 'imagestotal', doneKey: 'imagesdone' },
|
||||||
|
{ key: 'receipts', label: 'Belege', totalKey: 'receiptstotal', doneKey: 'receiptsdone' },
|
||||||
|
];
|
||||||
|
|
||||||
const EXPORT_OPTIONS = [
|
const EXPORT_OPTIONS = [
|
||||||
{
|
{
|
||||||
@@ -94,10 +103,10 @@ export default class ImportExportSettingsPanel extends Component {
|
|||||||
uploadProgress: 0,
|
uploadProgress: 0,
|
||||||
uploadLoaded: 0,
|
uploadLoaded: 0,
|
||||||
uploadTotal: 0,
|
uploadTotal: 0,
|
||||||
importPhase: null, // 'categories' | 'articles' | 'variants' | 'images' | null
|
importProgress: null, // raw counters from the `progress` event
|
||||||
importPhaseCurrent: 0,
|
// Most recent SkeleHashes from the stream ({ id, hash })
|
||||||
importPhaseTotal: 0,
|
importedImages: [],
|
||||||
importItem: null,
|
importedImageCount: 0,
|
||||||
statusMessage: null,
|
statusMessage: null,
|
||||||
importStats: null,
|
importStats: null,
|
||||||
uploadError: null,
|
uploadError: null,
|
||||||
@@ -243,16 +252,16 @@ export default class ImportExportSettingsPanel extends Component {
|
|||||||
|
|
||||||
this.uploadAbortController = new AbortController();
|
this.uploadAbortController = new AbortController();
|
||||||
|
|
||||||
|
this.imageSeq = 0;
|
||||||
this.setState({
|
this.setState({
|
||||||
uploading: true,
|
uploading: true,
|
||||||
uploadStage: 'upload',
|
uploadStage: 'upload',
|
||||||
uploadProgress: 0,
|
uploadProgress: 0,
|
||||||
uploadLoaded: 0,
|
uploadLoaded: 0,
|
||||||
uploadTotal: selectedFile.size,
|
uploadTotal: selectedFile.size,
|
||||||
importPhase: null,
|
importProgress: null,
|
||||||
importPhaseCurrent: 0,
|
importedImages: [],
|
||||||
importPhaseTotal: 0,
|
importedImageCount: 0,
|
||||||
importItem: null,
|
|
||||||
statusMessage: null,
|
statusMessage: null,
|
||||||
importStats: null,
|
importStats: null,
|
||||||
uploadError: null,
|
uploadError: null,
|
||||||
@@ -278,12 +287,31 @@ export default class ImportExportSettingsPanel extends Component {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onProgress: (progress) => {
|
onProgress: (progress) => {
|
||||||
this.setState({
|
if (!progress || typeof progress !== 'object') return;
|
||||||
uploadStage: 'server',
|
// Counter-based protocol: { categoriestotal, categoriesdone, ... }
|
||||||
importPhase: progress?.phase ?? null,
|
this.setState({ uploadStage: 'server', importProgress: progress });
|
||||||
importPhaseCurrent: progress?.current ?? 0,
|
},
|
||||||
importPhaseTotal: progress?.total ?? 0,
|
onImage: (images) => {
|
||||||
importItem: progress?.item ?? null,
|
const list = Array.isArray(images) ? images.filter(Boolean) : [];
|
||||||
|
if (!list.length) return;
|
||||||
|
this.setState((prev) => {
|
||||||
|
let seq = this.imageSeq ?? 0;
|
||||||
|
const batch = list.map((item) => {
|
||||||
|
seq += 1;
|
||||||
|
return {
|
||||||
|
id: seq,
|
||||||
|
hash: typeof item === 'string' ? item : (item?.skelehash ?? null),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.imageSeq = seq;
|
||||||
|
const next = [...batch.reverse(), ...prev.importedImages];
|
||||||
|
if (next.length > MAX_STREAMED_IMAGES) next.length = MAX_STREAMED_IMAGES;
|
||||||
|
return {
|
||||||
|
uploadStage: 'server',
|
||||||
|
importedImages: next,
|
||||||
|
importedImageCount: prev.importedImageCount + list.length,
|
||||||
|
statusMessage: null,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onDone: (stats) => {
|
onDone: (stats) => {
|
||||||
@@ -293,7 +321,7 @@ export default class ImportExportSettingsPanel extends Component {
|
|||||||
importStats: stats,
|
importStats: stats,
|
||||||
uploadSuccess: 'Import erfolgreich abgeschlossen.',
|
uploadSuccess: 'Import erfolgreich abgeschlossen.',
|
||||||
statusMessage: null,
|
statusMessage: null,
|
||||||
importItem: null,
|
importProgress: null,
|
||||||
selectedFile: null,
|
selectedFile: null,
|
||||||
});
|
});
|
||||||
if (this.fileInputRef.current) this.fileInputRef.current.value = '';
|
if (this.fileInputRef.current) this.fileInputRef.current.value = '';
|
||||||
@@ -354,20 +382,30 @@ export default class ImportExportSettingsPanel extends Component {
|
|||||||
uploadProgress,
|
uploadProgress,
|
||||||
uploadLoaded,
|
uploadLoaded,
|
||||||
uploadTotal,
|
uploadTotal,
|
||||||
importPhase,
|
importProgress,
|
||||||
importPhaseCurrent,
|
importedImages,
|
||||||
importPhaseTotal,
|
importedImageCount,
|
||||||
importItem,
|
|
||||||
statusMessage,
|
statusMessage,
|
||||||
importStats,
|
importStats,
|
||||||
uploadError,
|
uploadError,
|
||||||
uploadSuccess,
|
uploadSuccess,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const progressPercent =
|
const phaseRows = PROGRESS_PHASES.map((phase) => {
|
||||||
importPhaseTotal > 0
|
const total = importProgress?.[phase.totalKey] ?? 0;
|
||||||
? Math.min(100, Math.round((importPhaseCurrent / importPhaseTotal) * 100))
|
const done = importProgress?.[phase.doneKey] ?? 0;
|
||||||
: null;
|
return {
|
||||||
|
...phase,
|
||||||
|
total,
|
||||||
|
done,
|
||||||
|
percent: total > 0 ? Math.min(100, Math.round((done / total) * 100)) : 0,
|
||||||
|
};
|
||||||
|
}).filter((row) => row.total > 0);
|
||||||
|
|
||||||
|
const totalUnits = phaseRows.reduce((sum, row) => sum + row.total, 0);
|
||||||
|
const doneUnits = phaseRows.reduce((sum, row) => sum + Math.min(row.done, row.total), 0);
|
||||||
|
const overallPercent =
|
||||||
|
totalUnits > 0 ? Math.min(100, Math.round((doneUnits / totalUnits) * 100)) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ maxWidth: 680, mx: 'auto', py: 2 }}>
|
<Box sx={{ maxWidth: 680, mx: 'auto', py: 2 }}>
|
||||||
@@ -750,7 +788,8 @@ export default class ImportExportSettingsPanel extends Component {
|
|||||||
<>
|
<>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
||||||
<Typography variant="body2" fontWeight={600} color="primary.main">
|
<Typography variant="body2" fontWeight={600} color="primary.main">
|
||||||
{(importPhase && PHASE_LABELS[importPhase]) || statusMessage || 'Verarbeitung läuft…'}
|
{statusMessage || 'Daten werden verarbeitet…'}
|
||||||
|
{overallPercent != null ? ` · ${overallPercent}%` : ''}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -761,32 +800,26 @@ export default class ImportExportSettingsPanel extends Component {
|
|||||||
Abbrechen
|
Abbrechen
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
{importPhaseTotal > 0 ? (
|
{phaseRows.length > 0 ? (
|
||||||
<>
|
<Stack spacing={1}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
|
{phaseRows.map((row) => (
|
||||||
<Typography
|
<Box key={row.key}>
|
||||||
variant="caption"
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.25 }}>
|
||||||
color="text.secondary"
|
<Typography variant="caption" color="text.secondary">
|
||||||
sx={{
|
{row.label}
|
||||||
maxWidth: '75%',
|
</Typography>
|
||||||
overflow: 'hidden',
|
<Typography variant="caption" fontWeight={600} color="text.secondary">
|
||||||
textOverflow: 'ellipsis',
|
{row.done} / {row.total} ({row.percent}%)
|
||||||
whiteSpace: 'nowrap',
|
</Typography>
|
||||||
fontFamily: 'monospace',
|
</Box>
|
||||||
}}
|
<LinearProgress
|
||||||
>
|
variant="determinate"
|
||||||
{importItem || 'Fortschritt'}
|
value={row.percent}
|
||||||
</Typography>
|
sx={{ height: 6, borderRadius: 1 }}
|
||||||
<Typography variant="caption" fontWeight={600} color="text.secondary">
|
/>
|
||||||
{importPhaseCurrent} / {importPhaseTotal} ({progressPercent}%)
|
</Box>
|
||||||
</Typography>
|
))}
|
||||||
</Box>
|
</Stack>
|
||||||
<LinearProgress
|
|
||||||
variant="determinate"
|
|
||||||
value={progressPercent ?? 0}
|
|
||||||
sx={{ height: 8, borderRadius: 1 }}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{statusMessage && (
|
{statusMessage && (
|
||||||
@@ -797,6 +830,91 @@ export default class ImportExportSettingsPanel extends Component {
|
|||||||
<LinearProgress sx={{ height: 8, borderRadius: 1 }} />
|
<LinearProgress sx={{ height: 8, borderRadius: 1 }} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Live stream of decoded SkeleHash placeholders */}
|
||||||
|
{importedImages.length > 0 && (
|
||||||
|
<Box sx={{ mt: 2, pt: 2, borderTop: '1px dashed', borderColor: 'divider' }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
mb: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="caption" fontWeight={600} color="text.secondary">
|
||||||
|
SkeleHash-Platzhalter (Vorschau)
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
{importedImageCount} Bild(er) verarbeitet
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(auto-fill, minmax(72px, 1fr))',
|
||||||
|
gap: 1,
|
||||||
|
maxHeight: 220,
|
||||||
|
overflowY: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{importedImages.map((image) => (
|
||||||
|
<Box
|
||||||
|
key={image.id}
|
||||||
|
title={image.hash || 'kein Hash'}
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
aspectRatio: '1 / 1',
|
||||||
|
borderRadius: 1,
|
||||||
|
overflow: 'hidden',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{image.hash ? (
|
||||||
|
<SkeleHashImage hash={image.hash} />
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'text.disabled',
|
||||||
|
fontSize: '0.6rem',
|
||||||
|
textAlign: 'center',
|
||||||
|
p: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Kein Hash
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 'auto 0 0 0',
|
||||||
|
bgcolor: 'rgba(0,0,0,0.55)',
|
||||||
|
color: '#fff',
|
||||||
|
px: 0.5,
|
||||||
|
py: 0.25,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
noWrap
|
||||||
|
display="block"
|
||||||
|
sx={{ fontSize: '0.6rem', lineHeight: 1.2, fontFamily: 'monospace' }}
|
||||||
|
>
|
||||||
|
{shortHash(image.hash)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
94
src/components/settings/SkeleHashImage.jsx
Normal file
94
src/components/settings/SkeleHashImage.jsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { PureComponent, createRef } from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import { decodeBase62 } from 'skelehash-wasm';
|
||||||
|
|
||||||
|
// Module-level cache so re-renders / re-mounts don't re-run the WASM decoder
|
||||||
|
// for the same SkeleHash. Stores a Promise resolving to the decoded payload.
|
||||||
|
const decodeCache = new Map(); // base62 -> Promise<{ rgba, metadata } | null>
|
||||||
|
|
||||||
|
function decodeSkeleHash(base62) {
|
||||||
|
if (!base62) return Promise.resolve(null);
|
||||||
|
let cached = decodeCache.get(base62);
|
||||||
|
if (!cached) {
|
||||||
|
cached = decodeBase62(base62).catch(() => null);
|
||||||
|
decodeCache.set(base62, cached);
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a SkeleHash (Base62) as an RGBA canvas placeholder.
|
||||||
|
* Mirrors the synchronous-read / fine-grained styling approach of ThumbHashImage.
|
||||||
|
*/
|
||||||
|
export default class SkeleHashImage extends PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { ready: false };
|
||||||
|
this.canvasRef = createRef();
|
||||||
|
this.requestId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (prevProps.hash !== this.props.hash) {
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
// Invalidate any in-flight decode so it doesn't touch the unmounted canvas.
|
||||||
|
this.requestId += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async draw() {
|
||||||
|
const { hash } = this.props;
|
||||||
|
const requestId = ++this.requestId;
|
||||||
|
|
||||||
|
this.setState({ ready: false });
|
||||||
|
const decoded = await decodeSkeleHash(hash);
|
||||||
|
|
||||||
|
// Stale request (hash changed or component unmounted) — bail out.
|
||||||
|
if (requestId !== this.requestId) return;
|
||||||
|
const canvas = this.canvasRef.current;
|
||||||
|
if (!canvas || !decoded?.rgba || !decoded?.metadata) return;
|
||||||
|
|
||||||
|
const { rgba, metadata } = decoded;
|
||||||
|
const width = metadata.outputWidth;
|
||||||
|
const height = metadata.outputHeight;
|
||||||
|
if (!width || !height) return;
|
||||||
|
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const clamped = new Uint8ClampedArray(rgba.buffer, rgba.byteOffset, rgba.byteLength);
|
||||||
|
ctx.putImageData(new ImageData(clamped, width, height), 0, 0);
|
||||||
|
|
||||||
|
this.setState({ ready: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { sx, hash } = this.props;
|
||||||
|
const { ready } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
component="canvas"
|
||||||
|
ref={this.canvasRef}
|
||||||
|
aria-hidden
|
||||||
|
data-skelehash={hash || undefined}
|
||||||
|
sx={{
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: 'cover',
|
||||||
|
opacity: ready ? 1 : 0,
|
||||||
|
transition: 'opacity 200ms ease',
|
||||||
|
...sx,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user