diff --git a/src/components/TextInput.jsx b/src/components/TextInput.jsx
index 6ab9fb7..3dd1152 100644
--- a/src/components/TextInput.jsx
+++ b/src/components/TextInput.jsx
@@ -71,8 +71,9 @@ export default class TextInput extends Component {
} = this.props;
const isSelect = Boolean(rest.select);
- const effectiveReadOnly = isSelect
- ? (propReadOnly ?? slotProps?.htmlInput?.readOnly ?? inputProps?.readOnly)
+ const isAutocomplete = Boolean(inputProps?.['aria-autocomplete'] || rest['aria-autocomplete']);
+ const effectiveReadOnly = (isSelect || isAutocomplete)
+ ? (propReadOnly ?? slotProps?.htmlInput?.readOnly ?? inputProps?.readOnly ?? false)
: (propReadOnly ?? slotProps?.htmlInput?.readOnly ?? inputProps?.readOnly ?? this.state.readOnly);
const htmlInput = isSelect
diff --git a/src/components/article/boxes/ArticleViewLists.jsx b/src/components/article/boxes/ArticleViewLists.jsx
index 0f1cfe3..ef996d3 100644
--- a/src/components/article/boxes/ArticleViewLists.jsx
+++ b/src/components/article/boxes/ArticleViewLists.jsx
@@ -15,6 +15,8 @@ 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 Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
@@ -25,6 +27,7 @@ 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';
@@ -103,6 +106,11 @@ export default class ArticleViewLists extends PureComponent {
});
});
+ this.unsubArtikel = artikelAutomat.subscribe(() => {
+ if (this._cancelled) return;
+ this.forceUpdate();
+ });
+
if (attributeAutomat.state.status === 'idle') {
void attributeAutomat.actions.load();
}
@@ -111,6 +119,10 @@ export default class ArticleViewLists extends PureComponent {
void this.loadAssignedAttributes(this.props.article.sku);
}
+ if (this.props.article?.fatherSku) {
+ void artikelAutomat.actions.loadArticle(this.props.article.fatherSku);
+ }
+
void this.loadAllArticles();
}
@@ -119,6 +131,9 @@ export default class ArticleViewLists extends PureComponent {
if (this.props.article?.sku) {
void this.loadAssignedAttributes(this.props.article.sku);
}
+ if (this.props.article?.fatherSku) {
+ void artikelAutomat.actions.loadArticle(this.props.article.fatherSku);
+ }
const nextArticle = this.props.article;
const hasStueckliste = Boolean(
nextArticle?.articlelistId != null || nextArticle?.articlelist?.length > 0
@@ -130,6 +145,9 @@ export default class ArticleViewLists extends PureComponent {
if (!hasStueckliste && hasOptions) {
activeListTab = 'options';
}
+ if (this.state.allArticles.length === 0) {
+ void this.loadAllArticles();
+ }
this.setState({
activeListTab,
activeCreatorMode: null,
@@ -145,6 +163,7 @@ export default class ArticleViewLists extends PureComponent {
componentWillUnmount() {
this._cancelled = true;
this.unsubAttr?.();
+ this.unsubArtikel?.();
}
getApi() {
@@ -153,13 +172,13 @@ export default class ArticleViewLists extends PureComponent {
async loadAllArticles() {
try {
- const data = await (this.context?.articleIndexPromise || this.getApi().index());
+ const data = await this.context?.articleIndexPromise;
const articles = Array.isArray(data) ? data : (data?.articles || []);
if (!this._cancelled) {
this.setState({ allArticles: articles });
}
- } catch {
- // Ignore
+ } catch (err) {
+ console.error('Failed to load article index:', err);
}
}
@@ -590,7 +609,7 @@ export default class ArticleViewLists extends PureComponent {
}
};
- renderStuecklisteContent(stuecklisteItems, selectableOtherArticles) {
+ renderStuecklisteContent(stuecklisteItems, selectableOtherArticles, isStueckliste = true) {
const { busy, stuecklisteTargetArticle, stuecklisteCount } = this.state;
return (
@@ -600,26 +619,45 @@ export default class ArticleViewLists extends PureComponent {
Stückliste
-
+ {isStueckliste && (
+
+ )}
-
-
- this.setState({ confirmClearType: 'stueckliste' })}
- disabled={busy}
- sx={{ p: 0.4, color: 'text.secondary', '&:hover': { color: 'error.main' } }}
- >
-
-
-
-
+ {isStueckliste ? (
+
+
+ this.setState({ confirmClearType: 'stueckliste' })}
+ disabled={busy}
+ sx={{ p: 0.4, color: 'text.secondary', '&:hover': { color: 'error.main' } }}
+ >
+
+
+
+
+ ) : (
+
+ )}
{/* Add Component Row */}
@@ -631,9 +669,9 @@ export default class ArticleViewLists extends PureComponent {
disabled={busy}
onChange={(_e, val) => this.setState({ stuecklisteTargetArticle: val })}
getOptionLabel={(opt) =>
- typeof opt === 'string' ? opt : `${opt.sku} - ${opt.name || ''}`
+ typeof opt === 'string' ? opt : (opt ? `${opt.sku} - ${opt.name || ''}` : '')
}
- isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
+ isOptionEqualToValue={(opt, val) => opt?.sku === val?.sku}
renderInput={(params) => (
- {stuecklisteItems.length === 0 ? (
-
- Noch keine Komponenten in Stückliste.
-
- ) : (
- stuecklisteItems.map((item) => (
+ {stuecklisteItems.map((item) => (
))
- )}
-
-
- );
- }
-
- renderStuecklisteCreator(selectableOtherArticles, hasOptions) {
- const { busy, stuecklisteTargetArticle, stuecklisteCount } = this.state;
- return (
-
-
- Erste Komponente hinzufügen:
-
-
- this.setState({ stuecklisteTargetArticle: val })}
- getOptionLabel={(opt) =>
- typeof opt === 'string' ? opt : `${opt.sku} - ${opt.name || ''}`
}
- isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
- renderInput={(params) => (
-
- )}
- sx={{ width: '100%' }}
- />
-
-
-
- this.setState({
- stuecklisteCount: Math.max(1, parseInt(e.target.value, 10) || 1),
- })
- }
- slotProps={{
- htmlInput: {
- min: 1,
- style: { textAlign: 'center' },
- },
- }}
- sx={{ width: 72 }}
- />
-
-
-
-
-
);
}
- renderOptionsContent(optionsItems, selectableOtherArticles) {
+ renderOptionsContent(optionsItems, selectableOtherArticles, hasOptions = true) {
const { busy, optionTargetArticle, optionCount, optionMandated } = this.state;
return (
@@ -876,26 +815,46 @@ export default class ArticleViewLists extends PureComponent {
Optionen
-
+ {hasOptions && (
+
+ )}
-
-
- this.setState({ confirmClearType: 'options' })}
- disabled={busy}
- sx={{ p: 0.4, color: 'text.secondary', '&:hover': { color: 'error.main' } }}
- >
-
-
-
-
+ {hasOptions ? (
+
+
+ this.setState({ confirmClearType: 'options' })}
+ disabled={busy}
+ sx={{ p: 0.4, color: 'text.secondary', '&:hover': { color: 'error.main' } }}
+ >
+
+
+
+
+ ) : (
+
+ )}
{/* Add Option Row */}
@@ -907,9 +866,9 @@ export default class ArticleViewLists extends PureComponent {
disabled={busy}
onChange={(_e, val) => this.setState({ optionTargetArticle: val })}
getOptionLabel={(opt) =>
- typeof opt === 'string' ? opt : `${opt.sku} - ${opt.name || ''}`
+ typeof opt === 'string' ? opt : (opt ? `${opt.sku} - ${opt.name || ''}` : '')
}
- isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
+ isOptionEqualToValue={(opt, val) => opt?.sku === val?.sku}
renderInput={(params) => (
- Pflicht-Zubehör (mandated)
+ Fest
}
sx={{ ml: 0.5 }}
@@ -991,17 +950,9 @@ export default class ArticleViewLists extends PureComponent {
flexDirection: 'column',
gap: 0.5,
mt: 0.25,
- maxHeight: 150,
- overflowY: 'auto',
- pr: 0.25,
}}
>
- {optionsItems.length === 0 ? (
-
- Noch keine Optionen hinterlegt.
-
- ) : (
- optionsItems.map((opt) => (
+ {optionsItems.map((opt) => (
{opt.mandated ? (
))
- )}
+ }
);
}
- renderOptionsCreator(selectableOtherArticles, isStueckliste) {
- const { busy, optionTargetArticle, optionCount, optionMandated } = this.state;
+
+ renderCompoundManager(
+ isStueckliste,
+ hasOptions,
+ stuecklisteItems,
+ optionsItems,
+ selectableOtherArticles
+ ) {
+ const { activeListTab } = this.state;
+
return (
-
-
- Erste Option hinzufügen:
-
-
- this.setState({ optionTargetArticle: val })}
- getOptionLabel={(opt) =>
- typeof opt === 'string' ? opt : `${opt.sku} - ${opt.name || ''}`
- }
- isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
- renderInput={(params) => (
-
- )}
- sx={{ width: '100%' }}
- />
-
-
-
- this.setState({
- optionCount: Math.max(1, parseInt(e.target.value, 10) || 1),
- })
- }
- slotProps={{
- htmlInput: {
- min: 1,
- style: { textAlign: 'center' },
- },
- }}
- sx={{ width: 72 }}
+
+ 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"
+ />
+
- this.setState({ optionMandated: e.target.checked })}
- sx={{ p: 0.5 }}
- />
- }
- label={
-
- Pflicht
-
- }
- sx={{ ml: 0 }}
+ {activeListTab === 'stueckliste'
+ ? this.renderStuecklisteContent(stuecklisteItems, selectableOtherArticles, isStueckliste)
+ : this.renderOptionsContent(optionsItems, selectableOtherArticles, hasOptions)}
+
+ );
+ }
+
+ renderChildView(fatherSku, fatherArticle, attributes, assignedAttributes) {
+ const { article: articleData } = this.props;
+ const fatherAttrName =
+ fatherArticle?.fatherAttributeName ||
+ (fatherArticle?.fatherAttributeNameId
+ ? attributes.find((a) => a.id === fatherArticle.fatherAttributeNameId)?.name
+ : null) ||
+ 'Varianten';
+
+ const selfInFather = (fatherArticle?.children || []).find(
+ (c) => c.sku === articleData.sku || c.id === articleData.id
+ );
+ const assignedMatch = (assignedAttributes || []).find(
+ (a) =>
+ (fatherArticle?.fatherAttributeNameId != null &&
+ Number(a.attributeId) === Number(fatherArticle.fatherAttributeNameId)) ||
+ (a.attributeName &&
+ fatherAttrName &&
+ String(a.attributeName).toLowerCase() === String(fatherAttrName).toLowerCase())
+ );
+
+ const variationValue =
+ selfInFather?.attributeValue ||
+ assignedMatch?.attributeValue ||
+ null;
+
+ const siblings = (fatherArticle?.children || []).filter(
+ (c) => c.sku !== articleData.sku && c.id !== articleData.id
+ );
+
+ return (
+
+ {/* Header: Variation [Grün] für [Farbe] */}
+
+
+ Variation
+
+ {variationValue ? (
+
+ ) : null}
+
+ für
+
+
-
-
-
+
+
+ {fatherArticle?.name || fatherSku}
+
+
+
+ {fatherSku}
+
+
+
+
+
+
+
+ {/* Link(s) Geschwister */}
+
+
+ Geschwister:
+
+ {siblings.length === 0 ? (
+
+ Keine weiteren Varianten vorhanden.
+
+ ) : (
+
+ {siblings.map((sibling) => (
+
+
+
+ {sibling.attributeValue || sibling.name || sibling.sku}
+
+
+
+ {sibling.sku}
+
+
+
+
+
+ ))}
+
+ )}
);
@@ -1195,136 +1319,92 @@ export default class ArticleViewLists extends PureComponent {
const { busy, activeCreatorMode, selectedAttributeId, attributes } = this.state;
return (
-
-
- Typ:
-
-
- Standard-Artikel
-
-
-
{activeCreatorMode === 'father' ? (
-
+
+
+
+
+
+ Vaterartikel aktivieren
+
+
+
+
+
{selectableAttributes.length === 0 ? (
Alle vorhandenen Merkmale sind diesem Artikel bereits zugewiesen.
) : (
- this.setState({ selectedAttributeId: e.target.value })}
- >
- {attributes.map((a) => {
- const isAssigned = assignedAttributeIds.has(a.id);
- return (
-
-
-
-
-
+
+
+ )}
) : (
}
+ color="primary"
+ startIcon={}
onClick={() => this.setState({ activeCreatorMode: 'father' })}
- sx={{
- fontSize: '0.78rem',
- textTransform: 'none',
- bgcolor: 'background.paper',
- borderColor: 'divider',
- py: 0.5,
- justifyContent: 'flex-start',
- }}
>
Als Vaterartikel aktivieren
}
+ color="primary"
+ startIcon={}
onClick={() =>
this.setState({ activeCreatorMode: 'stueckliste', activeListTab: 'stueckliste' })
}
- sx={{
- fontSize: '0.78rem',
- textTransform: 'none',
- bgcolor: 'background.paper',
- borderColor: 'divider',
- py: 0.5,
- justifyContent: 'flex-start',
- }}
>
Als Stückliste anlegen
}
+ color="primary"
+ startIcon={}
onClick={() =>
this.setState({ activeCreatorMode: 'options', activeListTab: 'options' })
}
- sx={{
- fontSize: '0.78rem',
- textTransform: 'none',
- bgcolor: 'background.paper',
- borderColor: 'divider',
- py: 0.5,
- justifyContent: 'flex-start',
- }}
>
Optionen hinzufügen
@@ -1385,52 +1465,42 @@ export default class ArticleViewLists extends PureComponent {
);
const availableFatherValues = allFatherValues.filter((v) => !assignedValueIds.has(v.id));
+ const fatherSku = articleData.fatherSku;
+ const fatherArticle = fatherSku ? getArticle(fatherSku) : null;
+
return (
- {/* If article is a Child / Variant of another article, show link to its father */}
- {isChild && articleData.fatherSku ? (
-
-
-
-
- Variante von:
-
-
-
- {articleData.fatherSku}
-
-
- ) : null}
-
{/* ── Mode 1: Father Article ──────────────────────────────────────── */}
{isFather ? (
+ {isChild && fatherSku ? (
+
+
+
+ Übergeordnete Variante von:
+
+
+ {fatherSku}
+
+
+ ) : null}
+
- Vaterartikel
+ Variantsvater für
{children.length === 0 ? (
@@ -1569,121 +1636,133 @@ export default class ArticleViewLists extends PureComponent {
alignItems: 'center',
justifyContent: 'space-between',
px: 1,
- py: 0.4,
+ py: 0.6,
borderRadius: 1,
bgcolor: 'background.paper',
border: '1px solid',
borderColor: 'divider',
+ transition: 'all 0.15s ease-in-out',
+ '&:hover': {
+ borderColor: 'primary.main',
+ boxShadow: '0 1px 4px rgba(0,0,0,0.06)',
+ },
}}
>
-
+
{child.attributeValue || child.name || child.sku}
-
- {child.sku}
-
-
+
+
+ {child.sku}
+
+
+
+
this.handleOpenRemoveDialog(child)}
+ title="Variante entfernen"
sx={{
- p: 0.25,
+ p: 0.35,
color: 'text.secondary',
'&:hover': { color: 'error.main' },
}}
>
-
+
))
)}
- ) : isStueckliste || hasOptions || activeCreatorMode === 'stueckliste' || activeCreatorMode === 'options' ? (
- /* ── Combined Compound Manager (Stückliste & Optionen) ─────────── */
-
- {/* Sub-tabs: Stückliste & Optionen */}
-
- }
- onClick={() => this.setState({ activeListTab: 'stueckliste' })}
- sx={{
- py: 0.2,
- px: 1,
- fontSize: '0.75rem',
- textTransform: 'none',
- height: 26,
- borderRadius: 1.5,
- borderColor: activeListTab === 'stueckliste' ? 'primary.main' : 'divider',
- bgcolor: activeListTab === 'stueckliste' ? 'primary.main' : 'background.paper',
- }}
- >
- {isStueckliste ? `Stückliste (${stuecklisteItems.length})` : '+ Stückliste'}
-
- }
- onClick={() => this.setState({ activeListTab: 'options' })}
- sx={{
- py: 0.2,
- px: 1,
- fontSize: '0.75rem',
- textTransform: 'none',
- height: 26,
- borderRadius: 1.5,
- borderColor: activeListTab === 'options' ? 'primary.main' : 'divider',
- bgcolor: activeListTab === 'options' ? 'primary.main' : 'background.paper',
- }}
- >
- {hasOptions ? `Optionen (${optionsItems.length})` : '+ Optionen'}
-
+ ) : isChild && fatherSku ? (
+ /* ── Mode 2: Child Variant Article ───────────────────────────────── */
+
+ {this.renderChildView(
+ fatherSku,
+ fatherArticle,
+ attributes,
+ assignedAttributes
+ )}
+
+ {isStueckliste ||
+ hasOptions ||
+ activeCreatorMode === 'stueckliste' ||
+ activeCreatorMode === 'options'
+ ? this.renderCompoundManager(
+ isStueckliste,
+ hasOptions,
+ stuecklisteItems,
+ optionsItems,
+ selectableOtherArticles
+ )
+ : this.renderStandardView(selectableAttributes, assignedAttributeIds)}
-
- {activeListTab === 'stueckliste'
- ? isStueckliste
- ? this.renderStuecklisteContent(stuecklisteItems, selectableOtherArticles)
- : this.renderStuecklisteCreator(selectableOtherArticles, hasOptions)
- : hasOptions
- ? this.renderOptionsContent(optionsItems, selectableOtherArticles)
- : this.renderOptionsCreator(selectableOtherArticles, isStueckliste)}
+ ) : 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)
)}
diff --git a/src/components/article/tabs/ArticleAttributesTab.jsx b/src/components/article/tabs/ArticleAttributesTab.jsx
index 229bc97..303d81b 100644
--- a/src/components/article/tabs/ArticleAttributesTab.jsx
+++ b/src/components/article/tabs/ArticleAttributesTab.jsx
@@ -13,17 +13,54 @@ import { enqueueSnackbar } from 'notistack';
import { StorageManagerContext } from '../../../api/storageManagerContext';
import attributeAutomat, { selectAttributes } from '../../../automats/attributeAutomat';
import propertyAutomat, { selectProperties } from '../../../automats/propertyAutomat';
+import artikelAutomat, { getArticle } from '../../../automats/artikelAutomat';
import { article as articleApi } from '../../../api';
export default class ArticleAttributesTab extends PureComponent {
static contextType = StorageManagerContext;
+ getArticle() {
+ return this.props.article || getArticle(this.props.sku);
+ }
+
+ isFatherVariantAttribute(attr) {
+ const article = this.getArticle();
+ const isFather = Boolean(article?.isFather ?? article?.is_father);
+ if (!isFather || !attr) return false;
+ const fatherAttrId = article?.fatherAttributeNameId ?? article?.attributeNameId ?? null;
+ if (fatherAttrId != null && Number(attr.id) === Number(fatherAttrId)) return true;
+ if (
+ article?.fatherAttributeName &&
+ String(attr.name).trim().toLowerCase() === String(article.fatherAttributeName).trim().toLowerCase()
+ ) {
+ return true;
+ }
+ return false;
+ }
+
constructor(props) {
super(props);
const initialAttributes = selectAttributes() || [];
const initialProperties = selectProperties() || [];
- const firstAttr = initialAttributes[0];
+ const article = props.article || getArticle(props.sku);
+ const isFather = Boolean(article?.isFather ?? article?.is_father);
+ const fatherAttrId = article?.fatherAttributeNameId ?? article?.attributeNameId ?? null;
+ const fatherAttrName = article?.fatherAttributeName;
+
+ const selectableAttrs = initialAttributes.filter((a) => {
+ if (!isFather) return true;
+ if (fatherAttrId != null && Number(a.id) === Number(fatherAttrId)) return false;
+ if (
+ fatherAttrName &&
+ String(a.name).trim().toLowerCase() === String(fatherAttrName).trim().toLowerCase()
+ ) {
+ return false;
+ }
+ return true;
+ });
+
+ const firstAttr = selectableAttrs[0];
const initialKey = firstAttr
? `attr-${firstAttr.id}`
: initialProperties[0]
@@ -53,11 +90,24 @@ export default class ArticleAttributesTab extends PureComponent {
let nextKey = prev.selectedMerkmalKey;
let nextItemId = prev.selectedItemId;
- if (!nextKey) {
- const first = state.attributes[0];
+ const selectableAttrs = state.attributes;
+
+ const isCurrentKeyValid =
+ nextKey &&
+ (selectableAttrs.some((a) => `attr-${a.id}` === nextKey) ||
+ prev.globalProperties.some((p) => `prop-${p.id}` === nextKey));
+
+ if (!isCurrentKeyValid) {
+ const first = selectableAttrs[0];
if (first) {
nextKey = `attr-${first.id}`;
nextItemId = first.values?.[0]?.id ?? '';
+ } else if (prev.globalProperties[0]) {
+ nextKey = `prop-${prev.globalProperties[0].id}`;
+ nextItemId = '';
+ } else {
+ nextKey = '';
+ nextItemId = '';
}
}
return {
@@ -71,8 +121,9 @@ export default class ArticleAttributesTab extends PureComponent {
this.unsubProp = propertyAutomat.subscribe((state) => {
if (this._cancelled) return;
this.setState((prev) => {
+ const selectableAttrs = prev.globalAttributes;
let nextKey = prev.selectedMerkmalKey;
- if (!nextKey && !prev.globalAttributes.length && state.properties[0]) {
+ if (!nextKey && !selectableAttrs.length && state.properties[0]) {
nextKey = `prop-${state.properties[0].id}`;
}
return {
@@ -82,6 +133,11 @@ export default class ArticleAttributesTab extends PureComponent {
});
});
+ this.unsubArtikel = artikelAutomat.subscribe(() => {
+ if (this._cancelled) return;
+ this.forceUpdate();
+ });
+
if (attributeAutomat.state.status === 'idle') {
void attributeAutomat.actions.load();
}
@@ -102,6 +158,7 @@ export default class ArticleAttributesTab extends PureComponent {
this._cancelled = true;
this.unsubAttr?.();
this.unsubProp?.();
+ this.unsubArtikel?.();
}
async loadArticleData(sku) {
@@ -164,16 +221,30 @@ export default class ArticleAttributesTab extends PureComponent {
saving,
} = this.state;
- if (saving || !sku || !selectedMerkmalKey) return;
+ const selectableAttributes = globalAttributes;
+ const merkmalOptions = [
+ ...selectableAttributes.map((a) => ({ key: `attr-${a.id}`, id: a.id, type: 'attr' })),
+ ...globalProperties.map((p) => ({ key: `prop-${p.id}`, id: p.id, type: 'prop' })),
+ ];
+ const selectedOption =
+ merkmalOptions.find((m) => m.key === selectedMerkmalKey) ?? merkmalOptions[0] ?? null;
+ const effectiveKey = selectedOption?.key || selectedMerkmalKey;
+
+ if (saving || !sku || !effectiveKey) return;
const api = this.context?.article || articleApi;
- if (selectedMerkmalKey.startsWith('attr-')) {
- const attrId = Number(selectedMerkmalKey.replace('attr-', ''));
- const targetAttr = globalAttributes.find((a) => a.id === attrId);
- if (!targetAttr || !selectedItemId) return;
+ if (effectiveKey.startsWith('attr-')) {
+ const attrId = Number(effectiveKey.replace('attr-', ''));
+ const targetAttr = selectableAttributes.find((a) => a.id === attrId);
+ if (!targetAttr || this.isFatherVariantAttribute(targetAttr)) return;
+
+ const effectiveItemId = targetAttr.values?.some((v) => v.id === selectedItemId)
+ ? selectedItemId
+ : (targetAttr.values?.[0]?.id ?? '');
+ if (!effectiveItemId) return;
const isExclusive = Boolean(targetAttr.exclusive);
- const targetValId = Number(selectedItemId);
+ const targetValId = Number(effectiveItemId);
const isAlreadyAssigned = assignedAttributes.some((a) => a.attributeValueId === targetValId);
if (isAlreadyAssigned) {
@@ -320,8 +391,9 @@ export default class ArticleAttributesTab extends PureComponent {
}
// Unified list of selectable Merkmale
+ const selectableAttributes = globalAttributes;
const merkmalOptions = [
- ...globalAttributes.map((a) => ({
+ ...selectableAttributes.map((a) => ({
key: `attr-${a.id}`,
id: a.id,
type: 'attr',
@@ -339,9 +411,17 @@ export default class ArticleAttributesTab extends PureComponent {
})),
];
- const selectedOption = merkmalOptions.find((m) => m.key === selectedMerkmalKey) ?? null;
+ const selectedOption =
+ merkmalOptions.find((m) => m.key === selectedMerkmalKey) ?? merkmalOptions[0] ?? null;
+ const currentMerkmalKey = selectedOption?.key ?? '';
const isAttr = selectedOption?.type === 'attr';
+ const isVariantBlocked = isAttr && this.isFatherVariantAttribute(selectedOption);
const availableItems = selectedOption?.values || [];
+ const currentItemId = isAttr
+ ? (availableItems.some((v) => v.id === selectedItemId)
+ ? selectedItemId
+ : (availableItems[0]?.id ?? ''))
+ : '';
// Unified list of assigned groups sorted by definition order
const assignedGroups = [];
@@ -397,7 +477,7 @@ export default class ArticleAttributesTab extends PureComponent {
- {isAttr ? (
+ {isVariantBlocked ? (
+
+ Ein Vaterartikel kann keine Werte für sein Varianten-Merkmal haben.
+
+ ) : isAttr ? (
0 && (
{assignedGroups.map((group) => (
-
+
{group.name}:
-
+
{group.items.map((item) => (