import { PureComponent } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import Autocomplete from '@mui/material/Autocomplete'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; import Chip from '@mui/material/Chip'; import CircularProgress from '@mui/material/CircularProgress'; import AppDialog from '../../AppDialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; import FormControlLabel from '@mui/material/FormControlLabel'; import IconButton from '@mui/material/IconButton'; import Link from '@mui/material/Link'; import MenuItem from '@mui/material/MenuItem'; import Tab from '@mui/material/Tab'; import Tabs from '@mui/material/Tabs'; import TextInput from '../../TextInput'; import ArticleSearch from '../ArticleSearch'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import AddIcon from '@mui/icons-material/Add'; import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; import AccountTreeIcon from '@mui/icons-material/AccountTree'; import LayersIcon from '@mui/icons-material/Layers'; import PlaylistAddCheckIcon from '@mui/icons-material/PlaylistAddCheck'; import PowerSettingsNewIcon from '@mui/icons-material/PowerSettingsNew'; import LinkOffIcon from '@mui/icons-material/LinkOff'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import { PanelTile } from 'dashgui/panels'; import { enqueueSnackbar } from 'notistack'; import { artikelDetailPath } from '../articleRoute'; import { article as articleApi } from '../../../api'; import attributeAutomat, { selectAttributes } from '../../../automats/attributeAutomat'; import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat'; import { notifyError } from '../../../utils/notify'; import articleIndexAutomat from '../../../automats/articleIndexAutomat'; import { MAGENTA_PANEL, SHADOW_CARD, } from '../../../colors'; export default class ArticleViewLists extends PureComponent { constructor(props) { super(props); const initialAttributes = selectAttributes() || []; const hasStueckliste = Boolean( props.article?.articlelistId != null || props.article?.articlelist?.length > 0 ); const hasOptions = Boolean( props.article?.optionslistId != null || props.article?.options?.length > 0 ); let activeListTab = 'stueckliste'; if (!hasStueckliste && hasOptions) { activeListTab = 'options'; } this.state = { attributes: initialAttributes, assignedAttributes: [], allArticles: [], busy: false, // Active sub-tab for compound article: 'stueckliste' | 'options' activeListTab, // Creator mode for standard article: null | 'parent' | 'stueckliste' | 'options' activeCreatorMode: null, // Parent mode state selectedAttributeId: '', selectedChildValueId: '', newChildText: '', isCustomChildValue: false, // Stückliste item add state stuecklisteTargetArticle: null, stuecklisteCount: 1, // Options item add state optionTargetArticle: null, optionCount: 1, optionMandated: false, // Confirmation dialogs removeTargetChild: null, // { sku, attributeValue } confirmClearType: null, // 'stueckliste' | 'options' | null }; this._cancelled = false; } componentDidMount() { this._cancelled = false; this.unsubAttr = attributeAutomat.subscribe((state) => { if (this._cancelled) return; this.setState((prev) => { const assignedIds = new Set(prev.assignedAttributes.map((a) => a.attributeId)); const selectable = state.attributes.filter((a) => !assignedIds.has(a.id)); let nextSelectedId = prev.selectedAttributeId; if ((!nextSelectedId || assignedIds.has(Number(nextSelectedId))) && selectable[0]) { nextSelectedId = selectable[0].id; } return { attributes: state.attributes, selectedAttributeId: nextSelectedId, }; }); }); this.unsubArtikel = artikelAutomat.subscribe(() => { if (this._cancelled) return; this.forceUpdate(); }); if (attributeAutomat.state.status === 'idle') { void attributeAutomat.actions.load(); } if (this.props.article?.sku) { void this.loadAssignedAttributes(this.props.article.sku); } if (this.props.article?.parentSku) { void artikelAutomat.actions.loadArticle(this.props.article.parentSku); } void this.loadAllArticles(); } componentDidUpdate(prevProps) { if (prevProps.article?.sku !== this.props.article?.sku) { if (this.props.article?.sku) { void this.loadAssignedAttributes(this.props.article.sku); } if (this.props.article?.parentSku) { void artikelAutomat.actions.loadArticle(this.props.article.parentSku); } const nextArticle = this.props.article; const hasStueckliste = Boolean( nextArticle?.articlelistId != null || nextArticle?.articlelist?.length > 0 ); const hasOptions = Boolean( nextArticle?.optionslistId != null || nextArticle?.options?.length > 0 ); let activeListTab = 'stueckliste'; if (!hasStueckliste && hasOptions) { activeListTab = 'options'; } if (this.state.allArticles.length === 0) { void this.loadAllArticles(); } this.setState({ activeListTab, activeCreatorMode: null, stuecklisteTargetArticle: null, stuecklisteCount: 1, optionTargetArticle: null, optionCount: 1, optionMandated: false, }); } } componentWillUnmount() { this._cancelled = true; this.unsubAttr?.(); this.unsubArtikel?.(); } getApi() { return articleApi; } async loadAllArticles() { try { const articles = await articleIndexAutomat.actions.ensureLoaded(); if (!this._cancelled) { this.setState({ allArticles: articles }); } } catch (err) { console.error('Failed to load article index:', err); } } async loadAssignedAttributes(sku) { if (!sku) return; try { const api = this.getApi(); const res = await api.getAttributeValues(sku); if (this._cancelled) return; const assigned = Array.isArray(res?.attributevalues) ? res.attributevalues : []; this.setState((prev) => { const assignedIds = new Set(assigned.map((a) => a.attributeId)); const selectable = prev.attributes.filter((a) => !assignedIds.has(a.id)); let nextSelectedId = prev.selectedAttributeId; if ((!nextSelectedId || assignedIds.has(Number(nextSelectedId))) && selectable[0]) { nextSelectedId = selectable[0].id; } else if (selectable.length === 0) { nextSelectedId = ''; } return { assignedAttributes: assigned, selectedAttributeId: nextSelectedId, }; }); } catch { // Ignore } } // ── Parent Actions ────────────────────────────────────────────────────────── handleEnableParent = async () => { const { article } = this.props; const { selectedAttributeId, assignedAttributes, busy } = this.state; if (busy || !article?.sku || !selectedAttributeId) return; const isStueckliste = article.articlelistId != null || (article.articleListCount ?? 0) > 0; const hasOptions = article.optionslistId != null || (article.optionsCount ?? 0) > 0; if (isStueckliste) { enqueueSnackbar('Ein Stücklisten-Artikel kann kein Vaterartikel sein.', { variant: 'error' }); return; } if (hasOptions) { enqueueSnackbar('Ein Artikel mit Optionen kann kein Vaterartikel sein.', { variant: 'error' }); return; } const assignedIds = new Set(assignedAttributes.map((a) => a.attributeId)); if (assignedIds.has(Number(selectedAttributeId))) { enqueueSnackbar('Dieses Merkmal ist dem Artikel bereits zugewiesen.', { variant: 'warning' }); return; } this.setState({ busy: true }); try { const api = this.getApi(); const res = await api.toggleParent(article.sku, { isParent: true, attributeNameId: Number(selectedAttributeId), }); if (this._cancelled) return; if (res?.article) { artikelAutomat.actions.setArticle(res.article); } else { await artikelAutomat.actions.reloadArticle(article.sku); } enqueueSnackbar('Vaterartikel-Modus aktiviert.', { variant: 'success' }); this.setState({ busy: false, activeCreatorMode: null }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busy: false }); } }; handleDisableParent = async () => { const { article } = this.props; const { busy } = this.state; if (busy || !article?.sku) return; this.setState({ busy: true }); try { const api = this.getApi(); const res = await api.toggleParent(article.sku, { isParent: false }); if (this._cancelled) return; if (res?.article) { artikelAutomat.actions.setArticle(res.article); } else { await artikelAutomat.actions.reloadArticle(article.sku); } enqueueSnackbar('Vaterartikel-Modus deaktiviert.', { variant: 'success' }); this.setState({ busy: false }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busy: false }); } }; handleAddChild = async () => { const { article } = this.props; const { selectedChildValueId, newChildText, isCustomChildValue, busy } = this.state; if (busy || !article?.sku) return; const payload = {}; if (isCustomChildValue) { const val = newChildText.trim(); if (!val) return; payload.value = val; } else { if (!selectedChildValueId) return; payload.attributeValueId = Number(selectedChildValueId); } this.setState({ busy: true }); try { const api = this.getApi(); await api.addChild(article.sku, payload); if (this._cancelled) return; await artikelAutomat.actions.reloadArticle(article.sku); if (this._cancelled) return; enqueueSnackbar('Variante erfolgreich angelegt.', { variant: 'success' }); this.setState({ busy: false, selectedChildValueId: '', newChildText: '', }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busy: false }); } }; handleOpenRemoveDialog = (child) => { this.setState({ removeTargetChild: child }); }; handleCloseRemoveDialog = () => { this.setState({ removeTargetChild: null }); }; handleConfirmRemoveChild = async (deletePermanently) => { const { article } = this.props; const { removeTargetChild, busy } = this.state; if (busy || !article?.sku || !removeTargetChild?.sku) return; const childSku = removeTargetChild.sku; this.setState({ busy: true, removeTargetChild: null }); try { const api = this.getApi(); await api.removeChild(article.sku, childSku, { deleteArticle: deletePermanently, }); if (this._cancelled) return; await artikelAutomat.actions.reloadArticle(article.sku); if (this._cancelled) return; enqueueSnackbar( deletePermanently ? 'Variante vollständig gelöscht.' : 'Variante vom Vaterartikel getrennt.', { variant: 'success' } ); this.setState({ busy: false }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busy: false }); } }; // ── Stückliste Actions ────────────────────────────────────────────────────── handleAddItemToStueckliste = async () => { const { article } = this.props; const { stuecklisteTargetArticle, stuecklisteCount, busy } = this.state; if (busy || !article?.sku || !stuecklisteTargetArticle?.sku) return; if (stuecklisteTargetArticle.sku === article.sku) { enqueueSnackbar('Ein Artikel kann sich nicht selbst als Komponente enthalten.', { variant: 'error' }); return; } const isTargetParent = Boolean( stuecklisteTargetArticle.isParent || stuecklisteTargetArticle.is_parent || getArticle(stuecklisteTargetArticle.sku)?.isParent ); if (isTargetParent) { enqueueSnackbar('Ein Vaterartikel kann nicht als Komponente einer Stückliste hinzugefügt werden.', { variant: 'error' }); return; } const currentItems = Array.isArray(article.articlelist) ? article.articlelist : []; const count = Math.max(1, Number(stuecklisteCount) || 1); const existingIndex = currentItems.findIndex((i) => i.sku === stuecklisteTargetArticle.sku); const nextItems = existingIndex >= 0 ? currentItems.map((item, idx) => idx === existingIndex ? { ...item, count: item.count + count } : item ) : [ ...currentItems, { articleId: stuecklisteTargetArticle.id, sku: stuecklisteTargetArticle.sku, count, sort: currentItems.length, }, ]; this.setState({ busy: true }); try { const api = this.getApi(); await api.setArticlelist(article.sku, nextItems); if (this._cancelled) return; await artikelAutomat.actions.reloadArticle(article.sku); if (this._cancelled) return; enqueueSnackbar('Komponente hinzugefügt.', { variant: 'success' }); this.setState({ busy: false, stuecklisteTargetArticle: null, stuecklisteCount: 1, activeCreatorMode: null, activeListTab: 'stueckliste', }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busy: false }); } }; handleRemoveStuecklisteItem = async (itemSku) => { const { article } = this.props; const { busy } = this.state; if (busy || !article?.sku) return; const currentItems = Array.isArray(article.articlelist) ? article.articlelist : []; const remainingItems = currentItems .filter((i) => i.sku !== itemSku) .map((item, idx) => ({ articleId: item.articleId, sku: item.sku, count: item.count, sort: idx, })); this.setState({ busy: true }); try { const api = this.getApi(); await api.setArticlelist(article.sku, remainingItems); if (this._cancelled) return; await artikelAutomat.actions.reloadArticle(article.sku); if (this._cancelled) return; enqueueSnackbar('Komponente entfernt.', { variant: 'success' }); this.setState({ busy: false }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busy: false }); } }; handleClearStueckliste = async () => { const { article } = this.props; const { busy } = this.state; if (busy || !article?.sku) return; this.setState({ busy: true, confirmClearType: null }); try { const api = this.getApi(); await api.setArticlelist(article.sku, []); if (this._cancelled) return; await artikelAutomat.actions.reloadArticle(article.sku); if (this._cancelled) return; enqueueSnackbar('Stückliste aufgelöst.', { variant: 'success' }); this.setState({ busy: false, activeListTab: 'options', }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busy: false }); } }; // ── Optionen Actions ──────────────────────────────────────────────────────── handleAddOption = async () => { const { article } = this.props; const { optionTargetArticle, optionCount, optionMandated, busy } = this.state; if (busy || !article?.sku || !optionTargetArticle?.sku) return; if (optionTargetArticle.sku === article.sku) { enqueueSnackbar('Ein Artikel kann sich nicht selbst als Option enthalten.', { variant: 'error' }); return; } const isTargetParent = Boolean( optionTargetArticle.isParent || optionTargetArticle.is_parent || getArticle(optionTargetArticle.sku)?.isParent ); if (isTargetParent) { enqueueSnackbar('Ein Vaterartikel kann nicht als Option hinzugefügt werden.', { variant: 'error' }); return; } const currentOptions = Array.isArray(article.options) ? article.options : []; const count = Math.max(1, Number(optionCount) || 1); const existingIndex = currentOptions.findIndex((o) => o.sku === optionTargetArticle.sku); const nextOptions = existingIndex >= 0 ? currentOptions.map((opt, idx) => idx === existingIndex ? { ...opt, count: opt.count + count, mandated: Boolean(optionMandated) } : opt ) : [ ...currentOptions, { articleId: optionTargetArticle.id, sku: optionTargetArticle.sku, count, sort: currentOptions.length, mandated: Boolean(optionMandated), }, ]; this.setState({ busy: true }); try { const api = this.getApi(); await api.setOptionslist(article.sku, nextOptions); if (this._cancelled) return; await artikelAutomat.actions.reloadArticle(article.sku); if (this._cancelled) return; enqueueSnackbar('Option hinzugefügt.', { variant: 'success' }); this.setState({ busy: false, optionTargetArticle: null, optionCount: 1, optionMandated: false, activeCreatorMode: null, activeListTab: 'options', }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busy: false }); } }; handleRemoveOption = async (optionSku) => { const { article } = this.props; const { busy } = this.state; if (busy || !article?.sku) return; const currentOptions = Array.isArray(article.options) ? article.options : []; const remainingOptions = currentOptions .filter((o) => o.sku !== optionSku) .map((opt, idx) => ({ articleId: opt.articleId, sku: opt.sku, count: opt.count, sort: idx, mandated: Boolean(opt.mandated), })); this.setState({ busy: true }); try { const api = this.getApi(); await api.setOptionslist(article.sku, remainingOptions); if (this._cancelled) return; await artikelAutomat.actions.reloadArticle(article.sku); if (this._cancelled) return; enqueueSnackbar('Option entfernt.', { variant: 'success' }); this.setState({ busy: false }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busy: false }); } }; handleClearOptions = async () => { const { article } = this.props; const { busy } = this.state; if (busy || !article?.sku) return; this.setState({ busy: true, confirmClearType: null }); try { const api = this.getApi(); await api.setOptionslist(article.sku, []); if (this._cancelled) return; await artikelAutomat.actions.reloadArticle(article.sku); if (this._cancelled) return; enqueueSnackbar('Optionen entfernt.', { variant: 'success' }); this.setState({ busy: false, activeListTab: 'stueckliste', }); } catch (err) { if (this._cancelled) return; notifyError(err); this.setState({ busy: false }); } }; renderStuecklisteContent(stuecklisteItems, selectableOtherArticles, isStueckliste = true) { const { article } = this.props; const { busy, stuecklisteTargetArticle, stuecklisteCount } = this.state; return ( Stückliste {isStueckliste && ( )} {isStueckliste ? ( this.setState({ confirmClearType: 'stueckliste' })} disabled={busy} sx={{ p: 0.4, color: 'text.secondary', '&:hover': { color: 'error.main' } }} > ) : ( )} {/* Add Component Row */} item.component_sku || item.sku)]} onSelect={(val) => this.setState({ stuecklisteTargetArticle: val })} sx={{ flex: 1, minWidth: 0 }} /> this.setState({ stuecklisteCount: Math.max(1, parseInt(e.target.value, 10) || 1), }) } slotProps={{ htmlInput: { min: 1, style: { textAlign: 'center' }, }, }} sx={{ width: 72, flexShrink: 0 }} /> {busy ? : } {/* Items List */} {stuecklisteItems.map((item) => ( {item.name || item.sku} {item.sku} this.handleRemoveStuecklisteItem(item.sku)} sx={{ p: 0.25, color: 'text.secondary', '&:hover': { color: 'error.main' }, }} > )) } ); } renderOptionsContent(optionsItems, selectableOtherArticles, hasOptions = true) { const { article } = this.props; const { busy, optionTargetArticle, optionCount, optionMandated } = this.state; return ( Optionen {hasOptions && ( )} {hasOptions ? ( this.setState({ confirmClearType: 'options' })} disabled={busy} sx={{ p: 0.4, color: 'text.secondary', '&:hover': { color: 'error.main' } }} > ) : ( )} {/* Add Option Row */} item.component_sku || item.sku)]} onSelect={(val) => this.setState({ optionTargetArticle: val })} sx={{ flex: 1, minWidth: 0 }} /> this.setState({ optionCount: Math.max(1, parseInt(e.target.value, 10) || 1), }) } slotProps={{ htmlInput: { min: 1, style: { textAlign: 'center' }, }, }} sx={{ width: 72, flexShrink: 0 }} /> {busy ? : } {/* Mandated checkbox toggle */} this.setState({ optionMandated: e.target.checked })} sx={{ p: 0.5 }} /> } label={ Fest } sx={{ ml: 0.5 }} /> {/* Options List */} {optionsItems.map((opt) => ( {opt.name || opt.sku} {opt.sku} {opt.mandated ? ( ) : null} this.handleRemoveOption(opt.sku)} sx={{ p: 0.25, color: 'text.secondary', '&:hover': { color: 'error.main' }, }} > )) } ); } renderCompoundManager( isStueckliste, hasOptions, stuecklisteItems, optionsItems, selectableOtherArticles ) { const { activeListTab } = this.state; return ( this.setState({ activeListTab: val })} sx={{ minHeight: 32, mb: 0.5, borderBottom: '1px solid', borderColor: 'divider', '& .MuiTab-root': { minHeight: 32, py: 0.25, px: 1.5, textTransform: 'none', fontSize: '0.8rem', fontWeight: 600, }, }} > } iconPosition="start" /> } iconPosition="start" /> {activeListTab === 'stueckliste' ? this.renderStuecklisteContent(stuecklisteItems, selectableOtherArticles, isStueckliste) : this.renderOptionsContent(optionsItems, selectableOtherArticles, hasOptions)} ); } renderChildView(parentSku, parentArticle, attributes, assignedAttributes) { const { article: articleData } = this.props; const parentAttrName = parentArticle?.parentAttributeName || (parentArticle?.parentAttributeNameId ? attributes.find((a) => a.id === parentArticle.parentAttributeNameId)?.name : null) || 'Varianten'; const selfInParent = (parentArticle?.children || []).find( (c) => c.sku === articleData.sku || c.id === articleData.id ); const assignedMatch = (assignedAttributes || []).find( (a) => (parentArticle?.parentAttributeNameId != null && Number(a.attributeId) === Number(parentArticle.parentAttributeNameId)) || (a.attributeName && parentAttrName && String(a.attributeName).toLowerCase() === String(parentAttrName).toLowerCase()) ); const variationValue = selfInParent?.attributeValue || assignedMatch?.attributeValue || null; const siblings = (parentArticle?.children || []).filter( (c) => c.sku !== articleData.sku && c.id !== articleData.id ); return ( {/* Header: Variation [Grün] für [Farbe] */} Variation {variationValue ? ( ) : null} für {/* Link Vater */} Vaterartikel: {parentArticle?.name || parentSku} {parentSku} {/* Link(s) Geschwister */} Geschwister: {siblings.length === 0 ? ( Keine weiteren Varianten vorhanden. ) : ( {siblings.map((sibling) => ( {sibling.attributeValue || sibling.name || sibling.sku} {sibling.sku} ))} )} ); } renderStandardView(selectableAttributes, assignedAttributeIds) { const { busy, activeCreatorMode, selectedAttributeId } = this.state; return ( {activeCreatorMode === 'parent' ? ( Vaterartikel aktivieren {selectableAttributes.length === 0 ? ( Alle vorhandenen Merkmale sind diesem Artikel bereits zugewiesen. ) : ( this.setState({ selectedAttributeId: e.target.value })} sx={{ flex: 1, minWidth: 0 }} > Merkmal wählen... {selectableAttributes.map((a) => ( {a.name} ))} )} ) : ( )} ); } render() { const { article: articleData } = this.props; const { attributes, assignedAttributes, allArticles, busy, activeCreatorMode, selectedChildValueId, newChildText, isCustomChildValue, removeTargetChild, confirmClearType, } = this.state; if (!articleData) return null; const isChild = articleData.parentArticleId != null || Boolean(articleData.parentSku); const isParent = Boolean(articleData.isParent); const children = Array.isArray(articleData.children) ? articleData.children : []; const stuecklisteItems = Array.isArray(articleData.articlelist) ? articleData.articlelist : []; const isStueckliste = articleData.articlelistId != null || stuecklisteItems.length > 0; const optionsItems = Array.isArray(articleData.options) ? articleData.options : []; const hasOptions = articleData.optionslistId != null || optionsItems.length > 0; // Attributes assigned to this article that cannot be selected for parent mode const assignedAttributeIds = new Set( (assignedAttributes || []).map((a) => a.attributeId) ); const selectableAttributes = attributes.filter( (a) => !assignedAttributeIds.has(a.id) ); // Filter available articles for Autocomplete (cannot select self, and cannot select parent articles) const selectableOtherArticles = allArticles.filter( (a) => a.sku !== articleData.sku && !a.isParent && !a.is_parent ); // Find parent attribute definition & values const parentAttrDef = isParent ? attributes.find((a) => a.id === articleData.parentAttributeNameId) : null; const allParentValues = parentAttrDef?.values || []; const assignedValueIds = new Set( children.map((c) => c.attributeValueId).filter((id) => id != null) ); const availableParentValues = allParentValues.filter((v) => !assignedValueIds.has(v.id)); const parentSku = articleData.parentSku; const parentArticle = parentSku ? getArticle(parentSku) : null; return ( {/* ── Mode 1: Parent Article ──────────────────────────────────────── */} {isParent ? ( {isChild && parentSku ? ( Übergeordnete Variante von: {parentSku} ) : null} Vater für Auswahl-Merkmal {children.length === 0 ? ( ) : null} {/* Add Child Variant Row */} {isCustomChildValue || availableParentValues.length === 0 ? ( this.setState({ newChildText: e.target.value })} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); void this.handleAddChild(); } }} sx={{ flex: 1, minWidth: 0 }} /> ) : ( this.setState({ selectedChildValueId: e.target.value })} sx={{ flex: 1, minWidth: 0 }} > Variante wählen... {availableParentValues.map((v) => ( {v.value} ))} )} {busy ? : } {/* Toggle custom value / predefined value if values exist */} {allParentValues.length > 0 && availableParentValues.length > 0 ? ( this.setState((prev) => ({ isCustomChildValue: !prev.isCustomChildValue, selectedChildValueId: '', newChildText: '', })) } sx={{ fontSize: '0.72rem', color: 'text.secondary', textDecoration: 'none' }} > {isCustomChildValue ? 'Aus Liste wählen' : 'Neuen Wert tippen'} ) : null} {/* Children List */} {children.length === 0 ? ( Noch keine Varianten angelegt. ) : ( children.map((child) => ( {child.attributeValue || child.name || child.sku} {child.sku} this.handleOpenRemoveDialog(child)} title="Variante entfernen" sx={{ p: 0.35, color: 'text.secondary', '&:hover': { color: 'error.main' }, }} > )) )} ) : isChild && parentSku ? ( /* ── Mode 2: Child Variant Article ───────────────────────────────── */ {this.renderChildView( parentSku, parentArticle, attributes, assignedAttributes )} {isStueckliste || hasOptions || activeCreatorMode === 'stueckliste' || activeCreatorMode === 'options' ? this.renderCompoundManager( isStueckliste, hasOptions, stuecklisteItems, optionsItems, selectableOtherArticles ) : this.renderStandardView(selectableAttributes, assignedAttributeIds)} ) : isStueckliste || hasOptions || activeCreatorMode === 'stueckliste' || activeCreatorMode === 'options' ? ( /* ── Mode 3: Combined Compound Manager (Stückliste & Optionen) ───── */ this.renderCompoundManager( isStueckliste, hasOptions, stuecklisteItems, optionsItems, selectableOtherArticles ) ) : ( /* ── Mode 4: Standard Article ────────────────────────────────────── */ this.renderStandardView(selectableAttributes, assignedAttributeIds) )} {/* Remove Child Confirmation Dialog */} Variante entfernen Möchten Sie die Variante{' '} „{removeTargetChild?.attributeValue || removeTargetChild?.name || removeTargetChild?.sku}“ {' '} ({removeTargetChild?.sku}) vom Vaterartikel trennen oder den Artikel vollständig löschen? {/* Clear Stückliste / Optionen Confirmation Dialog */} this.setState({ confirmClearType: null })} maxWidth="xs" fullWidth > {confirmClearType === 'stueckliste' ? 'Stückliste auflösen?' : 'Optionen entfernen?'} {confirmClearType === 'stueckliste' ? (hasOptions ? 'Möchten Sie alle Komponenten aus dieser Stückliste entfernen? Die Optionen bleiben dabei erhalten.' : 'Möchten Sie alle Komponenten aus dieser Stückliste entfernen und den Artikel wieder als Standard-Artikel führen?') : (isStueckliste ? 'Möchten Sie alle Optionen von diesem Artikel entfernen? Die Stückliste bleibt dabei erhalten.' : 'Möchten Sie alle Optionen von diesem Artikel entfernen und ihn wieder als Standard-Artikel führen?')} ); } }