This commit is contained in:
seb
2026-10-04 09:37:09 +02:00
parent da318223c1
commit dd8566eabc
3 changed files with 284 additions and 281 deletions

View File

@@ -630,36 +630,44 @@ class ArtikelMerkmalePage extends PureComponent {
`}</style>
<PanelTileGrid>
{/* Tile 1: Merkmale (Freitext) */}
<PanelTile wide={2} minHeight={280} color="#fed7aa">
{/* Tile 1: Attribute (Auswahllisten) */}
<PanelTile wide={2} minHeight={280} color="#bfdbfe">
<Stack spacing={2}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<TuneIcon sx={{ color: 'text.secondary', fontSize: 20 }} />
<StyleIcon sx={{ color: 'text.secondary', fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
Merkmale
Auswahl-Merkmale
</Typography>
<Chip label={properties.length} size="small" sx={{ ml: 'auto', fontWeight: 600, height: 20 }} />
<Chip label={attributes.length} size="small" sx={{ ml: 'auto', fontWeight: 600, height: 20 }} />
</Box>
<Typography variant="body2" color="text.secondary">
Reihenfolge per Drag &amp; Drop anpassen.
</Typography>
<List dense disablePadding ref={this.propListRef}>
{properties.length === 0 ? (
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
Noch keine Merkmale angelegt.
</Typography>
) : (
properties.map((p) => (
<List dense disablePadding ref={this.attrListRef}>
{attributes.map((a) => {
const isSelected = selectedAttr?.id === a.id;
return (
<ListItem
key={p.id}
key={a.id}
disableGutters
sx={{ gap: 1, py: 0.75, alignItems: 'center' }}
data-id={p.id}
onClick={() => this.handleSelectAttribute(a.id)}
sx={{
gap: 1,
py: 0.75,
px: 0.5,
borderRadius: 1.5,
alignItems: 'center',
cursor: 'pointer',
bgcolor: isSelected ? 'rgba(59, 130, 246, 0.12)' : 'transparent',
border: isSelected ? '1px solid rgba(59, 130, 246, 0.3)' : '1px solid transparent',
transition: 'all 0.15s ease',
'&:hover': {
bgcolor: isSelected ? 'rgba(59, 130, 246, 0.16)' : 'rgba(0, 0, 0, 0.04)',
},
}}
data-id={a.id}
>
<Box
className="drag-handle"
onClick={(e) => e.stopPropagation()}
sx={{
display: 'flex',
alignItems: 'center',
@@ -671,46 +679,57 @@ class ArtikelMerkmalePage extends PureComponent {
>
<DragIndicatorIcon fontSize="small" />
</Box>
<TuneIcon sx={{ color: 'text.secondary', fontSize: 20, flexShrink: 0 }} />
<SingleLineTextEdit
size="small"
label=""
value={propDrafts[p.id] ?? ''}
disabled={busy}
onChange={(next) =>
this.setState((prev) => ({
propDrafts: { ...prev.propDrafts, [p.id]: next },
}))
}
onBlur={() => void this.handleRenameProperty(p)}
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault();
event.target.blur();
}
if (event.key === 'Escape') {
event.preventDefault();
this.setState((prev) => ({
propDrafts: { ...prev.propDrafts, [p.id]: p.name },
}));
event.target.blur();
}
<StyleIcon
sx={{
color: isSelected ? 'primary.main' : 'text.secondary',
fontSize: 20,
flexShrink: 0,
}}
sx={{ flex: 1 }}
/>
<Box onClick={(e) => e.stopPropagation()} sx={{ flex: 1, minWidth: 0 }}>
<SingleLineTextEdit
size="small"
label=""
value={attrDrafts[a.id] ?? ''}
disabled={busy}
onChange={(next) =>
this.setState((prev) => ({
attrDrafts: { ...prev.attrDrafts, [a.id]: next },
}))
}
onBlur={() => void this.handleRenameAttribute(a)}
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault();
event.target.blur();
}
if (event.key === 'Escape') {
event.preventDefault();
this.setState((prev) => ({
attrDrafts: { ...prev.attrDrafts, [a.id]: a.name },
}));
event.target.blur();
}
}}
sx={{ width: '100%' }}
/>
</Box>
<Tooltip
title={
p.exclusive
? 'Einzelauswahl (Artikel kann max. 1 Wert für dieses Merkmal haben)'
: 'Mehrfachauswahl (Artikel kann mehrere Werte für dieses Merkmal haben)'
a.exclusive
? 'Einzelauswahl (Artikel kann max. 1 Wert wählen)'
: 'Mehrfachauswahl (Artikel kann mehrere Werte wählen)'
}
>
<Chip
label={p.exclusive ? '1x' : 'Multi'}
label={a.exclusive ? '1x' : 'Multi'}
size="small"
color={p.exclusive ? 'primary' : 'default'}
variant={p.exclusive ? 'filled' : 'outlined'}
onClick={(e) => void this.handleTogglePropertyExclusive(p, e)}
color={a.exclusive ? 'primary' : 'default'}
variant={a.exclusive ? 'filled' : 'outlined'}
onClick={(e) => void this.handleToggleAttributeExclusive(a, e)}
sx={{
height: 22,
fontSize: '0.72rem',
@@ -720,206 +739,27 @@ class ArtikelMerkmalePage extends PureComponent {
}}
/>
</Tooltip>
<IconButton
edge="end"
size="small"
aria-label={`„${p.name}" löschen`}
aria-label={`„${a.name}" löschen`}
disabled={busy}
onClick={() => void this.handleDeleteProperty(p)}
onClick={(e) => void this.handleDeleteAttribute(a, e)}
sx={{ flexShrink: 0 }}
>
<DeleteOutlinedIcon fontSize="small" />
</IconButton>
</ListItem>
))
)}
);
})}
</List>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SingleLineTextEdit
size="small"
label="Neues Merkmal"
value={newPropName}
disabled={busy}
onChange={(next) => this.setState({ newPropName: next })}
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault();
void this.handleCreateProperty();
}
}}
sx={{ flex: 1 }}
/>
<Button
variant="contained"
size="small"
disabled={busy || !newPropName.trim()}
onClick={() => void this.handleCreateProperty()}
>
{busyKey === 'new-prop' ? '…' : 'Hinzufügen'}
</Button>
</Box>
<FormControlLabel
control={
<Checkbox
size="small"
checked={newPropExclusive}
disabled={busy}
onChange={(e) => this.setState({ newPropExclusive: e.target.checked })}
sx={{ p: 0.5 }}
/>
}
label={
<Typography variant="caption" color="text.secondary">
Einzelauswahl (max. 1 Wert pro Artikel)
</Typography>
}
sx={{ ml: 0.5, mt: -0.5 }}
/>
</Box>
</Stack>
</PanelTile>
{/* Tile 2: Attribute (Auswahllisten) */}
<PanelTile wide={2} minHeight={280} color="#bfdbfe">
<Stack spacing={2}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<StyleIcon sx={{ color: 'text.secondary', fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
Attribute
</Typography>
<Chip label={attributes.length} size="small" sx={{ ml: 'auto', fontWeight: 600, height: 20 }} />
</Box>
<Typography variant="body2" color="text.secondary">
Reihenfolge per Drag &amp; Drop anpassen.
</Typography>
<List dense disablePadding ref={this.attrListRef}>
{attributes.length === 0 ? (
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
Noch keine Attribute angelegt.
</Typography>
) : (
attributes.map((a) => {
const isSelected = selectedAttr?.id === a.id;
return (
<ListItem
key={a.id}
disableGutters
onClick={() => this.handleSelectAttribute(a.id)}
sx={{
gap: 1,
py: 0.75,
px: 0.5,
borderRadius: 1.5,
alignItems: 'center',
cursor: 'pointer',
bgcolor: isSelected ? 'rgba(59, 130, 246, 0.12)' : 'transparent',
border: isSelected ? '1px solid rgba(59, 130, 246, 0.3)' : '1px solid transparent',
transition: 'all 0.15s ease',
'&:hover': {
bgcolor: isSelected ? 'rgba(59, 130, 246, 0.16)' : 'rgba(0, 0, 0, 0.04)',
},
}}
data-id={a.id}
>
<Box
className="drag-handle"
onClick={(e) => e.stopPropagation()}
sx={{
display: 'flex',
alignItems: 'center',
color: 'text.disabled',
flexShrink: 0,
'&:hover': { color: 'text.secondary' },
transition: 'color 0.15s',
}}
>
<DragIndicatorIcon fontSize="small" />
</Box>
<StyleIcon
sx={{
color: isSelected ? 'primary.main' : 'text.secondary',
fontSize: 20,
flexShrink: 0,
}}
/>
<Box onClick={(e) => e.stopPropagation()} sx={{ flex: 1, minWidth: 0 }}>
<SingleLineTextEdit
size="small"
label=""
value={attrDrafts[a.id] ?? ''}
disabled={busy}
onChange={(next) =>
this.setState((prev) => ({
attrDrafts: { ...prev.attrDrafts, [a.id]: next },
}))
}
onBlur={() => void this.handleRenameAttribute(a)}
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault();
event.target.blur();
}
if (event.key === 'Escape') {
event.preventDefault();
this.setState((prev) => ({
attrDrafts: { ...prev.attrDrafts, [a.id]: a.name },
}));
event.target.blur();
}
}}
sx={{ width: '100%' }}
/>
</Box>
<Tooltip
title={
a.exclusive
? 'Einzelauswahl (Artikel kann max. 1 Wert wählen)'
: 'Mehrfachauswahl (Artikel kann mehrere Werte wählen)'
}
>
<Chip
label={a.exclusive ? '1x' : 'Multi'}
size="small"
color={a.exclusive ? 'primary' : 'default'}
variant={a.exclusive ? 'filled' : 'outlined'}
onClick={(e) => void this.handleToggleAttributeExclusive(a, e)}
sx={{
height: 22,
fontSize: '0.72rem',
fontWeight: 600,
cursor: 'pointer',
flexShrink: 0,
}}
/>
</Tooltip>
<IconButton
edge="end"
size="small"
aria-label={`„${a.name}" löschen`}
disabled={busy}
onClick={(e) => void this.handleDeleteAttribute(a, e)}
sx={{ flexShrink: 0 }}
>
<DeleteOutlinedIcon fontSize="small" />
</IconButton>
</ListItem>
);
})
)}
</List>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SingleLineTextEdit
size="small"
label="Neues Attribut"
label=""
value={newAttrName}
disabled={busy}
onChange={(next) => this.setState({ newAttrName: next })}
@@ -961,7 +801,7 @@ class ArtikelMerkmalePage extends PureComponent {
</Stack>
</PanelTile>
{/* Tile 3: Werte für das gewählte Attribut */}
{/* Tile 2: Werte für das gewählte Attribut */}
<PanelTile wide={2} minHeight={280} color="#ddd6fe">
<Stack spacing={2}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@@ -978,14 +818,8 @@ class ArtikelMerkmalePage extends PureComponent {
) : null}
</Box>
<Typography variant="body2" color="text.secondary">
{selectedAttr
? 'Reihenfolge per Drag & Drop anpassen.'
: 'Wähle links ein Attribut aus, um Werte zu bearbeiten.'}
</Typography>
{!selectedAttr ? (
<Typography variant="body2" color="text.secondary" sx={{ py: 3, textAlign: 'center', fontStyle: 'italic' }}>
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
Kein Attribut ausgewählt.
</Typography>
) : (
@@ -1060,7 +894,7 @@ class ArtikelMerkmalePage extends PureComponent {
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SingleLineTextEdit
size="small"
label={`Neuer Wert für „${selectedAttr.name}“`}
label=""
value={newValText}
disabled={busy}
onChange={(next) => this.setState({ newValText: next })}
@@ -1085,6 +919,146 @@ class ArtikelMerkmalePage extends PureComponent {
)}
</Stack>
</PanelTile>
{/* Tile 3: Merkmale (Freitext) */}
<PanelTile wide={2} minHeight={280} color="#fed7aa">
<Stack spacing={2}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<TuneIcon sx={{ color: 'text.secondary', fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
Text-Merkmale
</Typography>
<Chip label={properties.length} size="small" sx={{ ml: 'auto', fontWeight: 600, height: 20 }} />
</Box>
<List dense disablePadding ref={this.propListRef}>
{properties.map((p) => (
<ListItem
key={p.id}
disableGutters
sx={{ gap: 1, py: 0.75, alignItems: 'center' }}
data-id={p.id}
>
<Box
className="drag-handle"
sx={{
display: 'flex',
alignItems: 'center',
color: 'text.disabled',
flexShrink: 0,
'&:hover': { color: 'text.secondary' },
transition: 'color 0.15s',
}}
>
<DragIndicatorIcon fontSize="small" />
</Box>
<TuneIcon sx={{ color: 'text.secondary', fontSize: 20, flexShrink: 0 }} />
<SingleLineTextEdit
size="small"
label=""
value={propDrafts[p.id] ?? ''}
disabled={busy}
onChange={(next) =>
this.setState((prev) => ({
propDrafts: { ...prev.propDrafts, [p.id]: next },
}))
}
onBlur={() => void this.handleRenameProperty(p)}
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault();
event.target.blur();
}
if (event.key === 'Escape') {
event.preventDefault();
this.setState((prev) => ({
propDrafts: { ...prev.propDrafts, [p.id]: p.name },
}));
event.target.blur();
}
}}
sx={{ flex: 1 }}
/>
<Tooltip
title={
p.exclusive
? 'Einzelauswahl (Artikel kann max. 1 Wert für dieses Merkmal haben)'
: 'Mehrfachauswahl (Artikel kann mehrere Werte für dieses Merkmal haben)'
}
>
<Chip
label={p.exclusive ? '1x' : 'Multi'}
size="small"
color={p.exclusive ? 'primary' : 'default'}
variant={p.exclusive ? 'filled' : 'outlined'}
onClick={(e) => void this.handleTogglePropertyExclusive(p, e)}
sx={{
height: 22,
fontSize: '0.72rem',
fontWeight: 600,
cursor: 'pointer',
flexShrink: 0,
}}
/>
</Tooltip>
<IconButton
edge="end"
size="small"
aria-label={`„${p.name}" löschen`}
disabled={busy}
onClick={() => void this.handleDeleteProperty(p)}
>
<DeleteOutlinedIcon fontSize="small" />
</IconButton>
</ListItem>
))}
</List>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SingleLineTextEdit
size="small"
label=""
value={newPropName}
disabled={busy}
onChange={(next) => this.setState({ newPropName: next })}
onKeyDown={(event) => {
if (event.key === 'Enter') {
event.preventDefault();
void this.handleCreateProperty();
}
}}
sx={{ flex: 1 }}
/>
<Button
variant="contained"
size="small"
disabled={busy || !newPropName.trim()}
onClick={() => void this.handleCreateProperty()}
>
{busyKey === 'new-prop' ? '…' : 'Hinzufügen'}
</Button>
</Box>
<FormControlLabel
control={
<Checkbox
size="small"
checked={newPropExclusive}
disabled={busy}
onChange={(e) => this.setState({ newPropExclusive: e.target.checked })}
sx={{ p: 0.5 }}
/>
}
label={
<Typography variant="caption" color="text.secondary">
Einzelauswahl (max. 1 Wert pro Artikel)
</Typography>
}
sx={{ ml: 0.5, mt: -0.5 }}
/>
</Box>
</Stack>
</PanelTile>
</PanelTileGrid>
</>
);