From 0a073f7cf42a2fe94d39bd8a8176354c73b7f904 Mon Sep 17 00:00:00 2001 From: seb Date: Mon, 12 Oct 2026 01:31:41 +0200 Subject: [PATCH] u --- src/automats/attributeAutomat.js | 22 ++++ src/automats/brandAutomat.js | 8 ++ src/automats/propertyAutomat.js | 11 ++ src/routes/artikel/marken.jsx | 43 ++++++- src/routes/artikel/merkmale.jsx | 186 +++++++++++++++++++++++++------ 5 files changed, 232 insertions(+), 38 deletions(-) diff --git a/src/automats/attributeAutomat.js b/src/automats/attributeAutomat.js index 60c0b3c..994dbc1 100644 --- a/src/automats/attributeAutomat.js +++ b/src/automats/attributeAutomat.js @@ -117,6 +117,17 @@ const actions = { await actions.load(); }, + /** + * Delete multiple attribute names. + * @param {number[]} ids + */ + async deleteAttributes(ids) { + for (const id of ids) { + await attributenameApi.delete(id); + } + await actions.load(); + }, + /** * Reorder attribute names. * @param {number[]} attributenameIds @@ -157,6 +168,17 @@ const actions = { await actions.load(); }, + /** + * Delete multiple attribute values. + * @param {number[]} ids + */ + async deleteValues(ids) { + for (const id of ids) { + await attributevalueApi.delete(id); + } + await actions.load(); + }, + /** * Reorder values of an attribute. * @param {number} attributeId diff --git a/src/automats/brandAutomat.js b/src/automats/brandAutomat.js index 846bb0b..eca68c4 100644 --- a/src/automats/brandAutomat.js +++ b/src/automats/brandAutomat.js @@ -113,6 +113,14 @@ const actions = { await brandApi.delete(id); updateStateWithBrands(brandAutomat.state.brands.filter((b) => b.id !== id)); }, + async removeBrands(ids) { + const idSet = new Set(ids); + for (const id of ids) { + await brandApi.delete(id); + } + updateStateWithBrands(brandAutomat.state.brands.filter((b) => !idSet.has(b.id))); + await actions.load(true); + }, // #region addPic async addImage(id, file) { const res = await brandApi.addImage(id, file); diff --git a/src/automats/propertyAutomat.js b/src/automats/propertyAutomat.js index 88de378..570a488 100644 --- a/src/automats/propertyAutomat.js +++ b/src/automats/propertyAutomat.js @@ -97,6 +97,17 @@ const actions = { await actions.load(); }, + /** + * Delete multiple property names. + * @param {number[]} ids + */ + async deleteProperties(ids) { + for (const id of ids) { + await propertynameApi.delete(id); + } + await actions.load(); + }, + /** * Reorder property names. * @param {number[]} propertynameIds diff --git a/src/routes/artikel/marken.jsx b/src/routes/artikel/marken.jsx index d28ebea..397c6a2 100644 --- a/src/routes/artikel/marken.jsx +++ b/src/routes/artikel/marken.jsx @@ -22,16 +22,21 @@ import { class ArtikelMarkenSearchField extends PureComponent { constructor(props) { super(props); + const brands = brandAutomat.state.brands || []; this.state = { input: brandAutomat.state.filter ?? '', submitting: false, + deletingUnused: false, + unusedCount: brands.filter((b) => (b.articleCount ?? 0) === 0).length, }; this.handleSubmit = this.handleSubmit.bind(this); + this.handleDeleteUnused = this.handleDeleteUnused.bind(this); } componentDidMount() { this.unsub = brandAutomat.subscribe(this, (state) => ({ input: state.filter, + unusedCount: (state.brands || []).filter((b) => (b.articleCount ?? 0) === 0).length, })); } @@ -60,9 +65,30 @@ class ArtikelMarkenSearchField extends PureComponent { } } + async handleDeleteUnused() { + if (this.state.deletingUnused) return; + const allBrands = brandAutomat.state.brands || []; + const unused = allBrands.filter((b) => (b.articleCount ?? 0) === 0); + if (!unused.length) return; + + if (!window.confirm(`Alle ${unused.length} ungenutzten Marken wirklich löschen?`)) { + return; + } + + this.setState({ deletingUnused: true }); + try { + await brandAutomat.actions.removeBrands(unused.map((b) => b.id)); + enqueueSnackbar(`${unused.length} ungenutzte Marken gelöscht.`, { variant: 'success' }); + } catch (err) { + notifyError(err); + } finally { + this.setState({ deletingUnused: false }); + } + } + render() { const { inputRef } = this.props; - const { input, submitting } = this.state; + const { input, submitting, deletingUnused, unusedCount } = this.state; const canSubmit = Boolean(input.trim()) && !submitting; return ( @@ -89,6 +115,12 @@ class ArtikelMarkenSearchField extends PureComponent { value={input} disabled={submitting} onChange={(next) => this.onInputChange(next)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + event.preventDefault(); + void this.handleSubmit(); + } + }} /> + ); diff --git a/src/routes/artikel/merkmale.jsx b/src/routes/artikel/merkmale.jsx index 8428726..e6b9df2 100644 --- a/src/routes/artikel/merkmale.jsx +++ b/src/routes/artikel/merkmale.jsx @@ -46,16 +46,19 @@ class ArtikelMerkmalePage extends PureComponent { attributes: initialAttributes, attrFilter: '', attrSubmitting: false, + attrDeletingUnused: false, attrPage: 1, selectedAttributeId: initialSelectedAttrId, valFilter: '', valSubmitting: false, + valDeletingUnused: false, valPage: 1, properties: initialProperties, propFilter: '', propSubmitting: false, + propDeletingUnused: false, propPage: 1, }; @@ -73,6 +76,9 @@ class ArtikelMerkmalePage extends PureComponent { this.handleCreateAttribute = this.handleCreateAttribute.bind(this); this.handleCreateValue = this.handleCreateValue.bind(this); this.handleCreateProperty = this.handleCreateProperty.bind(this); + this.handleDeleteUnusedAttributes = this.handleDeleteUnusedAttributes.bind(this); + this.handleDeleteUnusedValues = this.handleDeleteUnusedValues.bind(this); + this.handleDeleteUnusedProperties = this.handleDeleteUnusedProperties.bind(this); } componentDidMount() { @@ -416,6 +422,77 @@ class ArtikelMerkmalePage extends PureComponent { } } + async handleDeleteUnusedAttributes() { + const { attributes, attrDeletingUnused } = this.state; + if (attrDeletingUnused) return; + const unused = attributes.filter((a) => (a.articleCount ?? 0) === 0); + if (!unused.length) return; + + if (!window.confirm(`Alle ${unused.length} ungenutzten Auswahl-Merkmale wirklich löschen?`)) { + return; + } + + this.setState({ attrDeletingUnused: true }); + try { + await attributeAutomat.actions.deleteAttributes(unused.map((a) => a.id)); + enqueueSnackbar(`${unused.length} ungenutzte Auswahl-Merkmale gelöscht.`, { variant: 'success' }); + } catch (err) { + notifyError(err); + } finally { + if (!this._cancelled) { + this.setState({ attrDeletingUnused: false }); + } + } + } + + async handleDeleteUnusedValues() { + const { attributes, selectedAttributeId, valDeletingUnused } = this.state; + if (valDeletingUnused) return; + const selectedAttr = attributes.find((a) => a.id === selectedAttributeId) ?? attributes[0] ?? null; + if (!selectedAttr) return; + const unused = (selectedAttr.values || []).filter((v) => (v.articleCount ?? 0) === 0); + if (!unused.length) return; + + if (!window.confirm(`Alle ${unused.length} ungenutzten Werte wirklich löschen?`)) { + return; + } + + this.setState({ valDeletingUnused: true }); + try { + await attributeAutomat.actions.deleteValues(unused.map((v) => v.id)); + enqueueSnackbar(`${unused.length} ungenutzte Werte gelöscht.`, { variant: 'success' }); + } catch (err) { + notifyError(err); + } finally { + if (!this._cancelled) { + this.setState({ valDeletingUnused: false }); + } + } + } + + async handleDeleteUnusedProperties() { + const { properties, propDeletingUnused } = this.state; + if (propDeletingUnused) return; + const unused = properties.filter((p) => (p.articleCount ?? 0) === 0); + if (!unused.length) return; + + if (!window.confirm(`Alle ${unused.length} ungenutzten Text-Merkmale wirklich löschen?`)) { + return; + } + + this.setState({ propDeletingUnused: true }); + try { + await propertyAutomat.actions.deleteProperties(unused.map((p) => p.id)); + enqueueSnackbar(`${unused.length} ungenutzte Text-Merkmale gelöscht.`, { variant: 'success' }); + } catch (err) { + notifyError(err); + } finally { + if (!this._cancelled) { + this.setState({ propDeletingUnused: false }); + } + } + } + render() { const { attrStatus, @@ -426,10 +503,13 @@ class ArtikelMerkmalePage extends PureComponent { selectedAttributeId, attrFilter, attrSubmitting, + attrDeletingUnused, valFilter, valSubmitting, + valDeletingUnused, propFilter, propSubmitting, + propDeletingUnused, } = this.state; if (attrStatus === 'loading' && propStatus === 'loading' && !properties.length && !attributes.length) { @@ -462,6 +542,10 @@ class ArtikelMerkmalePage extends PureComponent { ? properties.filter((p) => p.name.toLowerCase().includes(propFilterTrimmed.toLowerCase())) : properties; + const unusedAttrCount = attributes.filter((a) => (a.articleCount ?? 0) === 0).length; + const unusedValCount = (selectedValues || []).filter((v) => (v.articleCount ?? 0) === 0).length; + const unusedPropCount = properties.filter((p) => (p.articleCount ?? 0) === 0).length; + return ( {/* Tab 1: Auswahl-Merkmale & Werte */} @@ -469,11 +553,23 @@ class ArtikelMerkmalePage extends PureComponent { {/* Tile 1: Auswahl-Merkmale */} - - - - Auswahl-Merkmale - + + + + + Auswahl-Merkmale + + + @@ -532,11 +628,23 @@ class ArtikelMerkmalePage extends PureComponent { {/* Tile 2: Werte für das gewählte Auswahl-Merkmal */} - - - - {selectedAttr ? `Werte für Auswahl-Merkmal „${selectedAttr.name}“` : 'Werte'} - + + + + + {selectedAttr ? `Werte für Auswahl-Merkmal „${selectedAttr.name}“` : 'Werte'} + + + {!selectedAttr ? ( @@ -604,11 +712,23 @@ class ArtikelMerkmalePage extends PureComponent { {/* Tile 3: Text-Merkmale */} - - - - Text-Merkmale - + + + + + Text-Merkmale + + + @@ -637,28 +757,20 @@ class ArtikelMerkmalePage extends PureComponent { - {filteredProperties.length === 0 ? ( - - {propFilterTrimmed - ? `Keine Treffer für „${propFilterTrimmed}“.` - : 'Noch keine Text-Merkmale angelegt.'} - - ) : ( - this.setState({ propPage: page })} - renderItem={(p) => ( - - )} - /> - )} + this.setState({ propPage: page })} + renderItem={(p) => ( + + )} + />