u
This commit is contained in:
53
package-lock.json
generated
53
package-lock.json
generated
@@ -18,6 +18,7 @@
|
|||||||
"ag-grid-community": "^36.2.0",
|
"ag-grid-community": "^36.2.0",
|
||||||
"ag-grid-react": "^36.2.0",
|
"ag-grid-react": "^36.2.0",
|
||||||
"dashgui": "file:../dashgui",
|
"dashgui": "file:../dashgui",
|
||||||
|
"flexsearch": "^0.8.212",
|
||||||
"jsbarcode": "^3.12.3",
|
"jsbarcode": "^3.12.3",
|
||||||
"notistack": "^3.0.2",
|
"notistack": "^3.0.2",
|
||||||
"pdfconvert-wasm": "^0.3.0",
|
"pdfconvert-wasm": "^0.3.0",
|
||||||
@@ -92,23 +93,23 @@
|
|||||||
"ag-grid-react": "^36.2.0",
|
"ag-grid-react": "^36.2.0",
|
||||||
"marked": "^18.0.14",
|
"marked": "^18.0.14",
|
||||||
"prismjs": "^1.30.0",
|
"prismjs": "^1.30.0",
|
||||||
"react": "^19.2.8",
|
"react": "^19.3.0",
|
||||||
"react-automat": "file:../automat",
|
"react-automat": "file:../automat",
|
||||||
"react-dom": "^19.2.8"
|
"react-dom": "^19.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@types/node": "^24.13.3",
|
"@types/node": "^26.6.4",
|
||||||
"@types/react": "^19.2.18",
|
"@types/react": "^19.3.0",
|
||||||
"@types/react-dom": "^19.2.7",
|
"@types/react-dom": "^19.3.0",
|
||||||
"@vitejs/plugin-react": "^6.1.1",
|
"@vitejs/plugin-react": "^6.1.2",
|
||||||
"eslint": "^10.10.0",
|
"eslint": "^10.12.0",
|
||||||
"eslint-plugin-react-hooks": "^7.1.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.6",
|
"eslint-plugin-react-refresh": "^0.5.7",
|
||||||
"globals": "^17.12.0",
|
"globals": "^17.13.0",
|
||||||
"typescript": "~6.0.2",
|
"typescript": "~7.0.2",
|
||||||
"typescript-eslint": "^8.69.0",
|
"typescript-eslint": "^8.71.0",
|
||||||
"vite": "^8.3.0"
|
"vite": "^8.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@@ -5277,6 +5278,34 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/flexsearch": {
|
||||||
|
"version": "0.8.212",
|
||||||
|
"resolved": "https://registry.npmjs.org/flexsearch/-/flexsearch-0.8.212.tgz",
|
||||||
|
"integrity": "sha512-wSyJr1GUWoOOIISRu+X2IXiOcVfg9qqBRyCPRUdLMIGJqPzMo+jMRlvE83t14v1j0dRMEaBbER/adQjp6Du2pw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/ts-thomas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paypal",
|
||||||
|
"url": "https://www.paypal.com/donate/?hosted_button_id=GEVR88FC9BWRW"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/flexsearch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://patreon.com/user?u=96245532"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "liberapay",
|
||||||
|
"url": "https://liberapay.com/ts-thomas"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/for-each": {
|
"node_modules/for-each": {
|
||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"ag-grid-community": "^36.2.0",
|
"ag-grid-community": "^36.2.0",
|
||||||
"ag-grid-react": "^36.2.0",
|
"ag-grid-react": "^36.2.0",
|
||||||
"dashgui": "file:../dashgui",
|
"dashgui": "file:../dashgui",
|
||||||
|
"flexsearch": "^0.8.212",
|
||||||
"jsbarcode": "^3.12.3",
|
"jsbarcode": "^3.12.3",
|
||||||
"notistack": "^3.0.2",
|
"notistack": "^3.0.2",
|
||||||
"pdfconvert-wasm": "^0.3.0",
|
"pdfconvert-wasm": "^0.3.0",
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
"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",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Automat } from 'react-automat';
|
import { Automat } from 'react-automat';
|
||||||
|
import FlexSearch from 'flexsearch';
|
||||||
import articleIndexAutomat from './articleIndexAutomat';
|
import articleIndexAutomat from './articleIndexAutomat';
|
||||||
import categoryAutomat from './categoryAutomat';
|
import categoryAutomat from './categoryAutomat';
|
||||||
import { parseFilter } from '../components/article/searchFormat';
|
import { parseFilter } from '../components/article/searchFormat';
|
||||||
@@ -61,28 +62,101 @@ const initialState = {
|
|||||||
|
|
||||||
let articleSearchAutomat;
|
let articleSearchAutomat;
|
||||||
|
|
||||||
|
const { Document, Charset } = FlexSearch;
|
||||||
|
|
||||||
|
let flexDocument = null;
|
||||||
|
let lastIndexedArticles = null;
|
||||||
|
let articleByInternalId = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches one index record against a search query (checks tokens in name, sku, brand, eans).
|
* Lazily constructs and maintains the FlexSearch Document index for the given articles array.
|
||||||
*
|
*
|
||||||
* @param {object} row Article index record.
|
* @param {Array<object>} articles Current article index items.
|
||||||
* @param {string} query Lowercased search term.
|
* @returns {{ doc: any, byId: Map<number, object> }}
|
||||||
* @returns {boolean} Whether the record matches.
|
|
||||||
*/
|
*/
|
||||||
function matches(row, query) {
|
function getOrCreateFlexIndex(articles) {
|
||||||
if (!query) return true;
|
if (flexDocument && lastIndexedArticles === articles) {
|
||||||
const tokens = query.split(/\s+/).filter(Boolean);
|
return { doc: flexDocument, byId: articleByInternalId };
|
||||||
if (tokens.length === 0) return true;
|
}
|
||||||
const name = String(row.name ?? '').toLowerCase();
|
|
||||||
const sku = String(row.sku ?? '').toLowerCase();
|
const doc = new Document({
|
||||||
const brand = String(row.brand?.name || row.brandName || '').toLowerCase();
|
document: {
|
||||||
const eans = (row.eans ?? []).map((e) => String(e).toLowerCase());
|
id: 'id',
|
||||||
return tokens.every(
|
index: [
|
||||||
(token) =>
|
{ field: 'all', tokenize: 'forward', resolution: 9 },
|
||||||
name.includes(token) ||
|
{ field: 'sku', tokenize: 'full', resolution: 9 },
|
||||||
sku.includes(token) ||
|
{ field: 'name', tokenize: 'forward', resolution: 9 },
|
||||||
brand.includes(token) ||
|
{ field: 'brand', tokenize: 'forward', resolution: 5 },
|
||||||
eans.some((ean) => ean.includes(token)),
|
{ field: 'eans', tokenize: 'forward', resolution: 9 },
|
||||||
);
|
],
|
||||||
|
},
|
||||||
|
tokenize: 'forward',
|
||||||
|
charset: Charset.LatinAdvanced,
|
||||||
|
});
|
||||||
|
|
||||||
|
const byId = new Map();
|
||||||
|
for (let i = 0; i < articles.length; i++) {
|
||||||
|
const article = articles[i];
|
||||||
|
const docId = i;
|
||||||
|
byId.set(docId, article);
|
||||||
|
const sku = String(article.sku ?? '');
|
||||||
|
const name = String(article.name ?? '');
|
||||||
|
const brand = String(article.brand?.name || article.brandName || '');
|
||||||
|
const eans = (article.eans ?? []).join(' ');
|
||||||
|
const all = `${sku} ${name} ${brand} ${eans}`.trim();
|
||||||
|
|
||||||
|
doc.add({
|
||||||
|
id: docId,
|
||||||
|
sku,
|
||||||
|
name,
|
||||||
|
brand,
|
||||||
|
eans,
|
||||||
|
all,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
flexDocument = doc;
|
||||||
|
lastIndexedArticles = articles;
|
||||||
|
articleByInternalId = byId;
|
||||||
|
return { doc, byId };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the FlexSearch index with the query string.
|
||||||
|
*
|
||||||
|
* @param {string} query Search query.
|
||||||
|
* @param {Array<object>} articles Full articles collection.
|
||||||
|
* @returns {Array<object>} Articles matching the search query.
|
||||||
|
*/
|
||||||
|
function queryFlexSearch(query, articles) {
|
||||||
|
const q = String(query ?? '').trim();
|
||||||
|
if (!q) return articles;
|
||||||
|
|
||||||
|
const { doc, byId } = getOrCreateFlexIndex(articles);
|
||||||
|
let rawResults;
|
||||||
|
try {
|
||||||
|
rawResults = doc.search(q);
|
||||||
|
} catch {
|
||||||
|
rawResults = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchedIds = new Set();
|
||||||
|
for (const fieldResult of rawResults) {
|
||||||
|
if (Array.isArray(fieldResult.result)) {
|
||||||
|
for (const id of fieldResult.result) {
|
||||||
|
matchedIds.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchesList = [];
|
||||||
|
for (const id of matchedIds) {
|
||||||
|
const art = byId.get(id);
|
||||||
|
if (art) {
|
||||||
|
matchesList.push(art);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchesList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,11 +254,12 @@ export function searchArticlesIndex(query, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const articles = articleIndexAutomat.state.items ?? [];
|
const articles = articleIndexAutomat.state.items ?? [];
|
||||||
const q = String(query ?? '').trim().toLowerCase();
|
const q = String(query ?? '').trim();
|
||||||
|
|
||||||
|
const rawMatches = q ? queryFlexSearch(q, articles) : articles;
|
||||||
const excludedSet = new Set((excludeSkus ?? []).map((s) => String(s).toLowerCase()));
|
const excludedSet = new Set((excludeSkus ?? []).map((s) => String(s).toLowerCase()));
|
||||||
|
|
||||||
let pool = articles;
|
let pool = rawMatches;
|
||||||
if (!allowParent) {
|
if (!allowParent) {
|
||||||
pool = pool.filter((a) => !a.isParent && !a.is_parent);
|
pool = pool.filter((a) => !a.isParent && !a.is_parent);
|
||||||
}
|
}
|
||||||
@@ -192,7 +267,7 @@ export function searchArticlesIndex(query, options = {}) {
|
|||||||
pool = pool.filter((a) => a.sku && !excludedSet.has(String(a.sku).toLowerCase()));
|
pool = pool.filter((a) => a.sku && !excludedSet.has(String(a.sku).toLowerCase()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let matchesList = q ? pool.filter((row) => matches(row, q)) : pool;
|
let matchesList = pool;
|
||||||
|
|
||||||
if (categories && categories.length > 0) {
|
if (categories && categories.length > 0) {
|
||||||
const categoriesMap = categoryAutomat.state?.categories ?? {};
|
const categoriesMap = categoryAutomat.state?.categories ?? {};
|
||||||
@@ -390,11 +465,11 @@ const actions = {
|
|||||||
const offset = params.offset !== undefined ? Math.max(0, Number(params.offset) || 0) : currentState.offset;
|
const offset = params.offset !== undefined ? Math.max(0, Number(params.offset) || 0) : currentState.offset;
|
||||||
|
|
||||||
const articles = articleIndexAutomat.state.items ?? [];
|
const articles = articleIndexAutomat.state.items ?? [];
|
||||||
const query = target.trim().toLowerCase();
|
const query = target.trim();
|
||||||
|
|
||||||
// Filter by text query across all tokens
|
// Query candidates using FlexSearch
|
||||||
const textMatches = query
|
const textMatches = query
|
||||||
? articles.filter((row) => matches(row, query))
|
? queryFlexSearch(query, articles)
|
||||||
: articles;
|
: articles;
|
||||||
|
|
||||||
// Build facets for all text matches (before category filtering)
|
// Build facets for all text matches (before category filtering)
|
||||||
@@ -506,8 +581,11 @@ const actions = {
|
|||||||
return actions.runSearch({ offset });
|
return actions.runSearch({ offset });
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Reset the search state. */
|
/** Reset the search state and clear FlexSearch index. */
|
||||||
reset() {
|
reset() {
|
||||||
|
flexDocument = null;
|
||||||
|
lastIndexedArticles = null;
|
||||||
|
articleByInternalId = new Map();
|
||||||
articleSearchAutomat.setState(initialState);
|
articleSearchAutomat.setState(initialState);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ export default class ArticleSearchDialog extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSelect = async (article) => {
|
handleSelect = async (article) => {
|
||||||
const { onAssign, line } = this.props;
|
const { onAssign, line, kind = 'delivery' } = this.props;
|
||||||
|
const isDelivery = kind === 'delivery';
|
||||||
if (!onAssign || !article?.sku) return;
|
if (!onAssign || !article?.sku) return;
|
||||||
|
|
||||||
if (article.isParent || article.is_parent) {
|
if (article.isParent || article.is_parent) {
|
||||||
@@ -41,14 +42,19 @@ export default class ArticleSearchDialog extends PureComponent {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({
|
this.setState({
|
||||||
assigningSku: null,
|
assigningSku: null,
|
||||||
error: err?.message || 'Fehler beim Verknüpfen des Lieferanten-Artikels.',
|
error:
|
||||||
|
err?.message ||
|
||||||
|
(isDelivery
|
||||||
|
? 'Fehler beim Verknüpfen des Lieferanten-Artikels.'
|
||||||
|
: 'Fehler beim Zuordnen des Artikels.'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { open, onClose, line } = this.props;
|
const { open, onClose, line, kind = 'delivery' } = this.props;
|
||||||
const { assigningSku, error } = this.state;
|
const { assigningSku, error } = this.state;
|
||||||
|
const isDelivery = kind === 'delivery';
|
||||||
const rawSku = line?.sku != null ? String(line.sku).trim() : '';
|
const rawSku = line?.sku != null ? String(line.sku).trim() : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -93,10 +99,10 @@ export default class ArticleSearchDialog extends PureComponent {
|
|||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h6" component="div" fontWeight={600}>
|
<Typography variant="h6" component="div" fontWeight={600}>
|
||||||
Existierenden Artikel zuordnen
|
{line ? 'Existierenden Artikel zuordnen' : 'Artikel auswählen'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
Lieferanten-SKU: <strong>{rawSku || '–'}</strong>
|
{isDelivery ? 'Lieferanten-SKU' : 'SKU'}: <strong>{rawSku || '–'}</strong>
|
||||||
{line?.name ? ` · ${line.name}` : ''}
|
{line?.name ? ` · ${line.name}` : ''}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import ManifestHeader from './ManifestHeader';
|
|||||||
import ManifestLines from './ManifestLines';
|
import ManifestLines from './ManifestLines';
|
||||||
import ManifestPdfDialog from './ManifestPdfDialog';
|
import ManifestPdfDialog from './ManifestPdfDialog';
|
||||||
import ArticleSearchDialog from './ArticleSearchDialog';
|
import ArticleSearchDialog from './ArticleSearchDialog';
|
||||||
|
import artikelAutomat, { getArticle } from '../../automats/artikelAutomat';
|
||||||
import {
|
import {
|
||||||
buildManifestBody,
|
buildManifestBody,
|
||||||
restoreAttachmentsFromApi,
|
restoreAttachmentsFromApi,
|
||||||
@@ -55,6 +56,7 @@ class ManifestForm extends PureComponent {
|
|||||||
this.handleExternalNumberChange =
|
this.handleExternalNumberChange =
|
||||||
this.handleExternalNumberChange.bind(this);
|
this.handleExternalNumberChange.bind(this);
|
||||||
this.handleLineChange = this.handleLineChange.bind(this);
|
this.handleLineChange = this.handleLineChange.bind(this);
|
||||||
|
this.handleSkuBlur = this.handleSkuBlur.bind(this);
|
||||||
this.handleAddLine = this.handleAddLine.bind(this);
|
this.handleAddLine = this.handleAddLine.bind(this);
|
||||||
this.handleAddArticleLine = this.handleAddArticleLine.bind(this);
|
this.handleAddArticleLine = this.handleAddArticleLine.bind(this);
|
||||||
this.handleOpenArticleSearchForLine = this.handleOpenArticleSearchForLine.bind(this);
|
this.handleOpenArticleSearchForLine = this.handleOpenArticleSearchForLine.bind(this);
|
||||||
@@ -208,6 +210,47 @@ class ManifestForm extends PureComponent {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleSkuBlur(key, sku) {
|
||||||
|
const trimmed = String(sku ?? '').trim();
|
||||||
|
if (!trimmed) return;
|
||||||
|
try {
|
||||||
|
const art = getArticle(trimmed) || (await artikelAutomat.actions.loadArticle(trimmed));
|
||||||
|
if (art && !this._cancelled) {
|
||||||
|
this.setState((prev) => ({
|
||||||
|
lines: prev.lines.map((l) => {
|
||||||
|
if (l.key !== key) return l;
|
||||||
|
const netVal =
|
||||||
|
art.price != null
|
||||||
|
? String(art.price)
|
||||||
|
: art.vknetto != null
|
||||||
|
? String(art.vknetto)
|
||||||
|
: art.net != null
|
||||||
|
? String(art.net)
|
||||||
|
: '';
|
||||||
|
const taxVal =
|
||||||
|
art.taxrate != null
|
||||||
|
? String(art.taxrate)
|
||||||
|
: art.tax != null
|
||||||
|
? String(art.tax)
|
||||||
|
: '';
|
||||||
|
return {
|
||||||
|
...l,
|
||||||
|
sku: art.sku || l.sku,
|
||||||
|
name: art.name || l.name || '',
|
||||||
|
tax: l.tax || taxVal,
|
||||||
|
taxrate: l.taxrate || taxVal,
|
||||||
|
net: l.net || netVal,
|
||||||
|
netprice: l.netprice || netVal,
|
||||||
|
unit: art.unit || l.unit || 'Stück',
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// not in catalog or load error; preserve user entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleAddLine() {
|
handleAddLine() {
|
||||||
this.setState((prev) => ({ lines: [...prev.lines, emptyLine()] }));
|
this.setState((prev) => ({ lines: [...prev.lines, emptyLine()] }));
|
||||||
}
|
}
|
||||||
@@ -266,7 +309,15 @@ class ManifestForm extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleAssignArticleForLine(article, targetLine) {
|
handleAssignArticleForLine(article, targetLine) {
|
||||||
if (!article || !targetLine) return;
|
if (!article) return;
|
||||||
|
if (!targetLine) {
|
||||||
|
this.handleAddArticleLine(article);
|
||||||
|
this.setState({
|
||||||
|
articleSearchDialogOpen: false,
|
||||||
|
articleSearchLine: null,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const netVal =
|
const netVal =
|
||||||
article.price != null
|
article.price != null
|
||||||
? String(article.price)
|
? String(article.price)
|
||||||
@@ -513,12 +564,14 @@ class ManifestForm extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ManifestLines
|
<ManifestLines
|
||||||
|
kind={cfg.kind}
|
||||||
columns={cfg.columns}
|
columns={cfg.columns}
|
||||||
lines={lines}
|
lines={lines}
|
||||||
saving={saving}
|
saving={saving}
|
||||||
wide={4}
|
wide={4}
|
||||||
linesColor={cfg.linesColor}
|
linesColor={cfg.linesColor}
|
||||||
onLineChange={this.handleLineChange}
|
onLineChange={this.handleLineChange}
|
||||||
|
onSkuBlur={this.handleSkuBlur}
|
||||||
onAddLine={this.handleAddLine}
|
onAddLine={this.handleAddLine}
|
||||||
onAddArticleLine={this.handleAddArticleLine}
|
onAddArticleLine={this.handleAddArticleLine}
|
||||||
onSearchArticle={this.handleOpenArticleSearchForLine}
|
onSearchArticle={this.handleOpenArticleSearchForLine}
|
||||||
@@ -597,6 +650,7 @@ class ManifestForm extends PureComponent {
|
|||||||
<ArticleSearchDialog
|
<ArticleSearchDialog
|
||||||
open={this.state.articleSearchDialogOpen}
|
open={this.state.articleSearchDialogOpen}
|
||||||
line={this.state.articleSearchLine}
|
line={this.state.articleSearchLine}
|
||||||
|
kind={kind}
|
||||||
onClose={this.handleCloseArticleSearch}
|
onClose={this.handleCloseArticleSearch}
|
||||||
onAssign={this.handleAssignArticleForLine}
|
onAssign={this.handleAssignArticleForLine}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -31,9 +31,10 @@ import {
|
|||||||
class ManifestFormLine extends PureComponent {
|
class ManifestFormLine extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const sku = props.supplierArticle?.article?.sku;
|
const sku = this.getArticleSku(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
cachedArticle: sku ? getArticle(sku) : null,
|
cachedArticle: sku ? getArticle(sku) : null,
|
||||||
|
loadingArticle: false,
|
||||||
};
|
};
|
||||||
this.handleRemove = this.handleRemove.bind(this);
|
this.handleRemove = this.handleRemove.bind(this);
|
||||||
this.handleBlur = this.handleBlur.bind(this);
|
this.handleBlur = this.handleBlur.bind(this);
|
||||||
@@ -42,13 +43,20 @@ class ManifestFormLine extends PureComponent {
|
|||||||
this.handleRemoveSupplierArticle = this.handleRemoveSupplierArticle.bind(this);
|
this.handleRemoveSupplierArticle = this.handleRemoveSupplierArticle.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getArticleSku(props = this.props) {
|
||||||
|
if (props.kind === 'delivery') {
|
||||||
|
return props.supplierArticle?.article?.sku || null;
|
||||||
|
}
|
||||||
|
return props.line?.sku ? String(props.line.sku).trim() : null;
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.subscribeArticle(this.props.supplierArticle?.article?.sku);
|
this.subscribeArticle(this.getArticleSku());
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const prevSku = prevProps.supplierArticle?.article?.sku;
|
const prevSku = this.getArticleSku(prevProps);
|
||||||
const nextSku = this.props.supplierArticle?.article?.sku;
|
const nextSku = this.getArticleSku(this.props);
|
||||||
if (prevSku !== nextSku) {
|
if (prevSku !== nextSku) {
|
||||||
this.subscribeArticle(nextSku);
|
this.subscribeArticle(nextSku);
|
||||||
}
|
}
|
||||||
@@ -62,19 +70,35 @@ class ManifestFormLine extends PureComponent {
|
|||||||
this.unsubArticle?.();
|
this.unsubArticle?.();
|
||||||
this.unsubArticle = null;
|
this.unsubArticle = null;
|
||||||
if (!sku) {
|
if (!sku) {
|
||||||
if (this.state.cachedArticle) {
|
if (this.state.cachedArticle || this.state.loadingArticle) {
|
||||||
this.setState({ cachedArticle: null });
|
this.setState({ cachedArticle: null, loadingArticle: false });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const current = getArticle(sku);
|
const current = getArticle(sku);
|
||||||
this.setState({ cachedArticle: current });
|
if (current) {
|
||||||
if (!current) {
|
this.setState({ cachedArticle: current, loadingArticle: false });
|
||||||
artikelAutomat.actions.loadArticle(sku).catch(() => { });
|
} else {
|
||||||
|
this.setState({ cachedArticle: null, loadingArticle: true });
|
||||||
|
artikelAutomat.actions.loadArticle(sku)
|
||||||
|
.then((art) => {
|
||||||
|
if (this.getArticleSku() === sku) {
|
||||||
|
this.setState({ cachedArticle: art, loadingArticle: false });
|
||||||
}
|
}
|
||||||
this.unsubArticle = artikelAutomat.subscribe(this, (state) => ({
|
})
|
||||||
cachedArticle: createArticleSelector(sku)(state),
|
.catch(() => {
|
||||||
}));
|
if (this.getArticleSku() === sku) {
|
||||||
|
this.setState({ cachedArticle: null, loadingArticle: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.unsubArticle = artikelAutomat.subscribe(this, (state) => {
|
||||||
|
const art = createArticleSelector(sku)(state);
|
||||||
|
return {
|
||||||
|
cachedArticle: art ?? null,
|
||||||
|
...(art ? { loadingArticle: false } : {}),
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRemove() {
|
handleRemove() {
|
||||||
@@ -251,8 +275,255 @@ class ManifestFormLine extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderSecondLine() {
|
renderSecondLine() {
|
||||||
|
const { kind = 'delivery' } = this.props;
|
||||||
|
const rawSku = this.props.line?.sku != null ? String(this.props.line.sku).trim() : '';
|
||||||
|
|
||||||
|
if (kind === 'delivery') {
|
||||||
|
return this.renderDeliverySecondLine(rawSku);
|
||||||
|
}
|
||||||
|
return this.renderArticleSecondLine(rawSku);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderArticleSecondLine(rawSku) {
|
||||||
|
const { loading } = this.props;
|
||||||
|
const { cachedArticle, loadingArticle } = this.state;
|
||||||
|
|
||||||
|
if (loading || loadingArticle) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, py: 0.25 }}>
|
||||||
|
<CircularProgress size={14} thickness={5} sx={{ color: 'primary.main' }} />
|
||||||
|
<Typography variant="caption" color="text.secondary" sx={{ fontStyle: 'italic', fontSize: '0.72rem' }}>
|
||||||
|
Artikeldaten werden geladen…
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedArticle) {
|
||||||
|
const photo = cachedArticle.photos?.[0] || cachedArticle.articlepic?.[0];
|
||||||
|
const fullHash = photo?.hash || photo?.fullHash;
|
||||||
|
const thumbHash = photo?.thumbhash || photo?.thumbHash;
|
||||||
|
const thumbW = photo?.thumbw || photo?.thumbW;
|
||||||
|
const thumbH = photo?.thumbh || photo?.thumbH;
|
||||||
|
const hasPhoto = fullHash != null || thumbHash != null;
|
||||||
|
|
||||||
|
const eans = cachedArticle.eans ?? cachedArticle.articleean ?? [];
|
||||||
|
const primaryEan = Array.isArray(eans) ? eans[0] : eans;
|
||||||
|
|
||||||
|
const vkPrice = primaryPrice(cachedArticle);
|
||||||
|
const vkNetto = vkPrice?.net;
|
||||||
|
const vkBrutto = vkPrice
|
||||||
|
? priceBrutto(vkPrice) ??
|
||||||
|
(vkPrice.net != null && vkPrice.taxrate != null
|
||||||
|
? vkPrice.net * (1 + vkPrice.taxrate / 100)
|
||||||
|
: null)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1.25,
|
||||||
|
py: 0.25,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Article Photo Thumbnail */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
minWidth: 28,
|
||||||
|
borderRadius: 0.75,
|
||||||
|
overflow: 'hidden',
|
||||||
|
bgcolor: BORDER,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{hasPhoto ? (
|
||||||
|
<ArticlePhoto
|
||||||
|
photo={{ fullHash, thumbHash, thumbW, thumbH }}
|
||||||
|
size={28}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Inventory2OutlinedIcon sx={{ fontSize: 16, color: 'text.secondary' }} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Internal Article SKU */}
|
||||||
|
{cachedArticle.sku && (
|
||||||
|
<Chip
|
||||||
|
component={RouterLink}
|
||||||
|
to={artikelDetailPath(cachedArticle)}
|
||||||
|
clickable
|
||||||
|
size="small"
|
||||||
|
label={`Art.-Nr.: ${cachedArticle.sku}`}
|
||||||
|
sx={{
|
||||||
|
height: 20,
|
||||||
|
fontSize: '0.7rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
bgcolor: 'primary.50',
|
||||||
|
color: 'primary.dark',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'primary.200',
|
||||||
|
cursor: 'pointer',
|
||||||
|
textDecoration: 'none',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'primary.100',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Brand */}
|
||||||
|
{cachedArticle.brand?.name && (
|
||||||
|
<BrandLabel
|
||||||
|
brand={cachedArticle.brand}
|
||||||
|
size={16}
|
||||||
|
typographyProps={{
|
||||||
|
variant: 'caption',
|
||||||
|
color: 'text.secondary',
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Article Name */}
|
||||||
|
{cachedArticle.name && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
fontWeight={600}
|
||||||
|
noWrap
|
||||||
|
sx={{ color: 'text.primary', maxWidth: { xs: 200, sm: 300, md: 400 } }}
|
||||||
|
title={cachedArticle.name}
|
||||||
|
>
|
||||||
|
{cachedArticle.name}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Primary EAN */}
|
||||||
|
{primaryEan && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: '0.68rem',
|
||||||
|
bgcolor: 'action.hover',
|
||||||
|
px: 0.5,
|
||||||
|
py: 0.1,
|
||||||
|
borderRadius: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
EAN: {primaryEan}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* VK Netto & VK Brutto */}
|
||||||
|
{(vkNetto != null || vkBrutto != null) && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1.5,
|
||||||
|
ml: 'auto',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{vkNetto != null && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
fontWeight={600}
|
||||||
|
sx={{ color: 'text.secondary', fontSize: '0.72rem' }}
|
||||||
|
>
|
||||||
|
VK Netto: {formatPrice(vkNetto)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{vkBrutto != null && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
fontWeight={600}
|
||||||
|
sx={{ color: 'primary.dark', fontSize: '0.72rem' }}
|
||||||
|
>
|
||||||
|
VK Brutto: {formatPrice(vkBrutto)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawSku) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, py: 0.25 }}>
|
||||||
|
{this.props.onSearchArticle && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="text"
|
||||||
|
startIcon={<SearchIcon sx={{ fontSize: 14 }} />}
|
||||||
|
onClick={this.handleSearchArticle}
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.7rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
textTransform: 'none',
|
||||||
|
minWidth: 'auto',
|
||||||
|
py: 0.25,
|
||||||
|
px: 1,
|
||||||
|
color: 'primary.main',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'primary.50',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Artikel suchen
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<HelpOutlineIcon sx={{ fontSize: 15, color: 'warning.main' }} />
|
||||||
|
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.72rem' }}>
|
||||||
|
Kein Artikel für SKU „{rawSku}“ gefunden.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.onSearchArticle) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, py: 0.25 }}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="text"
|
||||||
|
startIcon={<SearchIcon sx={{ fontSize: 14 }} />}
|
||||||
|
onClick={this.handleSearchArticle}
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.7rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
textTransform: 'none',
|
||||||
|
minWidth: 'auto',
|
||||||
|
py: 0.25,
|
||||||
|
px: 1,
|
||||||
|
color: 'primary.main',
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: 'primary.50',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Artikel suchen
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDeliverySecondLine(rawSku) {
|
||||||
const { line, supplierArticle, loading } = this.props;
|
const { line, supplierArticle, loading } = this.props;
|
||||||
const rawSku = line?.sku != null ? String(line.sku).trim() : '';
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import TableContainer from '@mui/material/TableContainer';
|
|||||||
import TableHead from '@mui/material/TableHead';
|
import TableHead from '@mui/material/TableHead';
|
||||||
import TableRow from '@mui/material/TableRow';
|
import TableRow from '@mui/material/TableRow';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
import DeleteSweepOutlinedIcon from '@mui/icons-material/DeleteSweepOutlined';
|
import DeleteSweepOutlinedIcon from '@mui/icons-material/DeleteSweepOutlined';
|
||||||
import { PanelTile } from 'dashgui/panels';
|
import { PanelTile } from 'dashgui/panels';
|
||||||
import { LINE_COLUMNS, hasArticleLines } from './manifestConfig';
|
import { LINE_COLUMNS, hasArticleLines } from './manifestConfig';
|
||||||
@@ -17,6 +18,7 @@ import ManifestFormLine from './ManifestFormLine';
|
|||||||
class ManifestLines extends PureComponent {
|
class ManifestLines extends PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
kind = 'delivery',
|
||||||
lines,
|
lines,
|
||||||
saving,
|
saving,
|
||||||
linesColor,
|
linesColor,
|
||||||
@@ -86,17 +88,21 @@ class ManifestLines extends PureComponent {
|
|||||||
lines.map((line, index) => {
|
lines.map((line, index) => {
|
||||||
const lineKey = line.key ?? index;
|
const lineKey = line.key ?? index;
|
||||||
const rawSku = line.sku != null ? String(line.sku).trim() : '';
|
const rawSku = line.sku != null ? String(line.sku).trim() : '';
|
||||||
const matched =
|
const isDelivery = kind === 'delivery';
|
||||||
supplierArticlesByLineKey[lineKey] ||
|
const matched = isDelivery
|
||||||
(rawSku ? supplierArticlesMap[rawSku.toLowerCase()] || supplierArticlesMap[rawSku] : null);
|
? supplierArticlesByLineKey[lineKey] ||
|
||||||
const isLineLoading =
|
(rawSku ? supplierArticlesMap[rawSku.toLowerCase()] || supplierArticlesMap[rawSku] : null)
|
||||||
loadingLineKeys[lineKey] ||
|
: null;
|
||||||
(supplierArticlesLoading && !matched);
|
const isLineLoading = isDelivery
|
||||||
|
? loadingLineKeys[lineKey] ||
|
||||||
|
(supplierArticlesLoading && !matched)
|
||||||
|
: false;
|
||||||
const ledgerLine = line.id != null ? ledgerLinesById[line.id] : null;
|
const ledgerLine = line.id != null ? ledgerLinesById[line.id] : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManifestFormLine
|
<ManifestFormLine
|
||||||
key={lineKey}
|
key={lineKey}
|
||||||
|
kind={kind}
|
||||||
line={line.key != null ? line : { ...line, key: index }}
|
line={line.key != null ? line : { ...line, key: index }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
saving={saving}
|
saving={saving}
|
||||||
@@ -126,6 +132,16 @@ class ManifestLines extends PureComponent {
|
|||||||
>
|
>
|
||||||
Zeile
|
Zeile
|
||||||
</Button>
|
</Button>
|
||||||
|
{onSearchArticle && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
startIcon={<SearchIcon />}
|
||||||
|
disabled={saving}
|
||||||
|
onClick={() => onSearchArticle(null)}
|
||||||
|
>
|
||||||
|
Artikel suchen
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
color="error"
|
color="error"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// #region Imports
|
||||||
import { Component, createRef } from 'react';
|
import { Component, createRef } from 'react';
|
||||||
import FillSpace from 'dashgui/FillSpace';
|
import FillSpace from 'dashgui/FillSpace';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
@@ -47,7 +48,9 @@ import {
|
|||||||
categoryAutomat,
|
categoryAutomat,
|
||||||
} from '../automats';
|
} from '../automats';
|
||||||
import { BrandLabel } from '../components/brand';
|
import { BrandLabel } from '../components/brand';
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Styles & Measure Sample
|
||||||
const resultRowSx = {
|
const resultRowSx = {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minHeight: 64,
|
minHeight: 64,
|
||||||
@@ -106,7 +109,9 @@ function ResultRowMeasureSample() {
|
|||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Helpers
|
||||||
function initialSearchState(searchParams) {
|
function initialSearchState(searchParams) {
|
||||||
const query = queryFromParams(searchParams);
|
const query = queryFromParams(searchParams);
|
||||||
const stored = readArtikelQuery();
|
const stored = readArtikelQuery();
|
||||||
@@ -155,14 +160,22 @@ function getCategoryTreeOrder(categoriesMap, rootCategoryId) {
|
|||||||
|
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region ArtikelSearchPage
|
||||||
class ArtikelSearchPage extends Component {
|
class ArtikelSearchPage extends Component {
|
||||||
state = initialSearchState(this.props.searchParams);
|
// #region Constructor & State
|
||||||
paginationRef = createRef();
|
constructor(props) {
|
||||||
filterHeaderRef = createRef();
|
super(props);
|
||||||
fillSpaceRef = createRef();
|
this.state = initialSearchState(props.searchParams);
|
||||||
categoryFillSpaceRef = createRef();
|
this.paginationRef = createRef();
|
||||||
|
this.filterHeaderRef = createRef();
|
||||||
|
this.fillSpaceRef = createRef();
|
||||||
|
this.categoryFillSpaceRef = createRef();
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Measurement & Query Persistence
|
||||||
toggleCategoryFilter = () => {
|
toggleCategoryFilter = () => {
|
||||||
this.setState((prev) => ({ categoryFilterOpen: !prev.categoryFilterOpen }));
|
this.setState((prev) => ({ categoryFilterOpen: !prev.categoryFilterOpen }));
|
||||||
};
|
};
|
||||||
@@ -190,7 +203,9 @@ class ArtikelSearchPage extends Component {
|
|||||||
offset: query.offset,
|
offset: query.offset,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Lifecycle
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('mouseup', preserveSearchFocus, true);
|
document.addEventListener('mouseup', preserveSearchFocus, true);
|
||||||
setLastArticleSku(null);
|
setLastArticleSku(null);
|
||||||
@@ -263,7 +278,9 @@ class ArtikelSearchPage extends Component {
|
|||||||
this.fetchResults();
|
this.fetchResults();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Search & Navigation Handlers
|
||||||
updateParams = (nextTarget, nextFilter, nextOffset = 0) => {
|
updateParams = (nextTarget, nextFilter, nextOffset = 0) => {
|
||||||
writeArtikelQuery({
|
writeArtikelQuery({
|
||||||
target: nextTarget,
|
target: nextTarget,
|
||||||
@@ -329,7 +346,9 @@ class ArtikelSearchPage extends Component {
|
|||||||
const filterParam = this.props.searchParams.get('filter') ?? '';
|
const filterParam = this.props.searchParams.get('filter') ?? '';
|
||||||
this.updateParams(this.state.input, filterParam, nextOffset);
|
this.updateParams(this.state.input, filterParam, nextOffset);
|
||||||
};
|
};
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Render
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
input,
|
input,
|
||||||
@@ -635,6 +654,9 @@ class ArtikelSearchPage extends Component {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// #endregion
|
||||||
}
|
}
|
||||||
|
// #endregion
|
||||||
|
|
||||||
export default withSearchParams(ArtikelSearchPage);
|
export default withSearchParams(ArtikelSearchPage);
|
||||||
|
|
||||||
|
|||||||
@@ -1321,6 +1321,7 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
</PanelTile>
|
</PanelTile>
|
||||||
)}
|
)}
|
||||||
<ManifestLines
|
<ManifestLines
|
||||||
|
kind="delivery"
|
||||||
columns={DELIVERY_LINE_COLUMNS}
|
columns={DELIVERY_LINE_COLUMNS}
|
||||||
lines={lines}
|
lines={lines}
|
||||||
saving={saving}
|
saving={saving}
|
||||||
@@ -1379,6 +1380,7 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
<ArticleSearchDialog
|
<ArticleSearchDialog
|
||||||
open={this.state.articleSearchDialogOpen}
|
open={this.state.articleSearchDialogOpen}
|
||||||
line={this.state.articleSearchLine}
|
line={this.state.articleSearchLine}
|
||||||
|
kind="delivery"
|
||||||
onClose={this.handleCloseArticleSearch}
|
onClose={this.handleCloseArticleSearch}
|
||||||
onAssign={this.handleAssignArticleForLine}
|
onAssign={this.handleAssignArticleForLine}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user