This commit is contained in:
seb
2026-10-11 23:42:20 +02:00
parent d0fed9a2d1
commit ed2ac2c2c3

View File

@@ -13,7 +13,7 @@ import { enqueueSnackbar } from 'notistack';
import BackendError from '../../components/BackendError'; import BackendError from '../../components/BackendError';
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit'; import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
import FillSpace from 'dashgui/FillSpace'; import FillSpace from 'dashgui/FillSpace';
import { PanelTile, PanelTileGrid } from 'dashgui/panels'; import { PanelTile, PanelTileGrid, TabbedPanelTileGrid } from 'dashgui/panels';
import withNavigate from '../../components/withNavigate'; import withNavigate from '../../components/withNavigate';
import { backendErrorFrom } from '../../utils/backendError'; import { backendErrorFrom } from '../../utils/backendError';
import { notifyError } from '../../utils/notify'; import { notifyError } from '../../utils/notify';
@@ -69,6 +69,7 @@ class ArtikelMerkmalePage extends PureComponent {
this._valSortable = null; this._valSortable = null;
this.handleRetry = this.handleRetry.bind(this); this.handleRetry = this.handleRetry.bind(this);
this.handleTabChange = this.handleTabChange.bind(this);
this.handleCreateAttribute = this.handleCreateAttribute.bind(this); this.handleCreateAttribute = this.handleCreateAttribute.bind(this);
this.handleCreateValue = this.handleCreateValue.bind(this); this.handleCreateValue = this.handleCreateValue.bind(this);
this.handleCreateProperty = this.handleCreateProperty.bind(this); this.handleCreateProperty = this.handleCreateProperty.bind(this);
@@ -340,6 +341,15 @@ class ArtikelMerkmalePage extends PureComponent {
void this.loadAll(); void this.loadAll();
} }
handleTabChange(nextTab) {
if (nextTab === 0) {
this._initAttrSortable();
this._initValSortable();
} else if (nextTab === 1) {
this._initPropSortable();
}
}
handleSelectAttribute(attrId) { handleSelectAttribute(attrId) {
this.setState({ selectedAttributeId: attrId, valFilter: '', valPage: 1 }); this.setState({ selectedAttributeId: attrId, valFilter: '', valPage: 1 });
} }
@@ -453,200 +463,206 @@ class ArtikelMerkmalePage extends PureComponent {
: properties; : properties;
return ( return (
<PanelTileGrid> <TabbedPanelTileGrid onChange={this.handleTabChange}>
{/* Tile 1: Auswahl-Merkmale */} {/* Tab 1: Auswahl-Merkmale & Werte */}
<PanelTile wide={2} minHeight={0} color={BLUE_PANEL}> <PanelTileGrid icon={<StyleIcon />} text="Auswahl-Merkmale">
<Stack spacing={1.5}> {/* Tile 1: Auswahl-Merkmale */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <PanelTile wide={2} minHeight={0} color={BLUE_PANEL}>
<StyleIcon sx={{ color: 'text.secondary', fontSize: 20 }} /> <Stack spacing={1.5}>
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
Auswahl-Merkmale <StyleIcon sx={{ color: 'text.secondary', fontSize: 20 }} />
</Typography> <Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
</Box> Auswahl-Merkmale
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SingleLineTextEdit <SingleLineTextEdit
sx={{ flex: 1 }} sx={{ flex: 1 }}
size="small" size="small"
search search
clear clear
value={attrFilter} value={attrFilter}
disabled={attrSubmitting} disabled={attrSubmitting}
placeholder="Suchen oder anlegen…" placeholder="Suchen oder anlegen…"
onChange={(next) => this.setState({ attrFilter: next })} onChange={(next) => this.setState({ attrFilter: next })}
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
event.preventDefault(); event.preventDefault();
void this.handleCreateAttribute(); void this.handleCreateAttribute();
} }
}} }}
/> />
<Button <Button
variant="contained" variant="contained"
disabled={!attrFilterTrimmed || attrSubmitting} disabled={!attrFilterTrimmed || attrSubmitting}
onClick={() => void this.handleCreateAttribute()} onClick={() => void this.handleCreateAttribute()}
> >
Hinzufügen Hinzufügen
</Button> </Button>
</Box> </Box>
{filteredAttributes.length === 0 ? ( {filteredAttributes.length === 0 ? (
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}> <Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
{attrFilterTrimmed {attrFilterTrimmed
? `Keine Treffer für „${attrFilterTrimmed}“.` ? `Keine Treffer für „${attrFilterTrimmed}“.`
: 'Noch keine Auswahl-Merkmale angelegt.'} : 'Noch keine Auswahl-Merkmale angelegt.'}
</Typography> </Typography>
) : ( ) : (
<FillSpace <FillSpace
items={filteredAttributes} items={filteredAttributes}
totalCount={attributes.length} totalCount={attributes.length}
minPageSize={8} minPageSize={8}
maxPageSize={8} maxPageSize={8}
listRef={this.attrListRef} listRef={this.attrListRef}
onPageChange={(page) => this.setState({ attrPage: page })} onPageChange={(page) => this.setState({ attrPage: page })}
renderItem={(a) => ( renderItem={(a) => (
<ArtikelMerkmalAuswahl <ArtikelMerkmalAuswahl
key={a.id} key={a.id}
attribute={a} attribute={a}
isSelected={selectedAttr?.id === a.id} isSelected={selectedAttr?.id === a.id}
onSelect={() => this.handleSelectAttribute(a.id)} onSelect={() => this.handleSelectAttribute(a.id)}
/> />
)} )}
/> />
)} )}
</Stack> </Stack>
</PanelTile> </PanelTile>
{/* Tile 2: Werte für das gewählte Auswahl-Merkmal */} {/* Tile 2: Werte für das gewählte Auswahl-Merkmal */}
<PanelTile wide={2} minHeight={0} color={PURPLE_PANEL}> <PanelTile wide={2} minHeight={0} color={PURPLE_PANEL}>
<Stack spacing={1.5}> <Stack spacing={1.5}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<LabelImportantIcon sx={{ color: 'text.secondary', fontSize: 20 }} /> <LabelImportantIcon sx={{ color: 'text.secondary', fontSize: 20 }} />
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}> <Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
{selectedAttr ? `Werte für Auswahl-Merkmal „${selectedAttr.name}“` : 'Werte'} {selectedAttr ? `Werte für Auswahl-Merkmal „${selectedAttr.name}“` : 'Werte'}
</Typography> </Typography>
</Box> </Box>
{!selectedAttr ? ( {!selectedAttr ? (
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}> <Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
Kein Attribut ausgewählt. Kein Attribut ausgewählt.
</Typography> </Typography>
) : ( ) : (
<> <>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SingleLineTextEdit <SingleLineTextEdit
sx={{ flex: 1 }} sx={{ flex: 1 }}
size="small" size="small"
search search
clear clear
value={valFilter} value={valFilter}
disabled={valSubmitting} disabled={valSubmitting}
placeholder="Suchen oder anlegen…" placeholder="Suchen oder anlegen…"
onChange={(next) => this.setState({ valFilter: next })} onChange={(next) => this.setState({ valFilter: next })}
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
event.preventDefault(); event.preventDefault();
void this.handleCreateValue(); void this.handleCreateValue();
} }
}} }}
/> />
<Button <Button
variant="contained" variant="contained"
disabled={!valFilterTrimmed || valSubmitting} disabled={!valFilterTrimmed || valSubmitting}
onClick={() => void this.handleCreateValue()} onClick={() => void this.handleCreateValue()}
> >
Hinzufügen Hinzufügen
</Button> </Button>
</Box> </Box>
{filteredValues.length === 0 ? ( {filteredValues.length === 0 ? (
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}> <Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
{valFilterTrimmed {valFilterTrimmed
? `Keine Treffer für „${valFilterTrimmed}“.` ? `Keine Treffer für „${valFilterTrimmed}“.`
: 'Noch keine Werte angelegt.'} : 'Noch keine Werte angelegt.'}
</Typography> </Typography>
) : ( ) : (
<FillSpace <FillSpace
items={filteredValues} items={filteredValues}
totalCount={selectedValues.length} totalCount={selectedValues.length}
minPageSize={8} minPageSize={8}
maxPageSize={8} maxPageSize={8}
listRef={this.valListRef} listRef={this.valListRef}
onPageChange={(page) => this.setState({ valPage: page })} onPageChange={(page) => this.setState({ valPage: page })}
renderItem={(v) => ( renderItem={(v) => (
<ArtikelMerkmalWert <ArtikelMerkmalWert
key={v.id} key={v.id}
value={v} value={v}
/> />
)} )}
/> />
)} )}
</> </>
)} )}
</Stack> </Stack>
</PanelTile> </PanelTile>
</PanelTileGrid>
{/* Tile 3: Text-Merkmale */} {/* Tab 2: Text-Merkmale */}
<PanelTile wide={2} minHeight={0} color={ORANGE_PANEL}> <PanelTileGrid icon={<TuneIcon />} text="Text-Merkmale">
<Stack spacing={1.5}> {/* Tile 3: Text-Merkmale */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <PanelTile wide={2} minHeight={0} color={ORANGE_PANEL}>
<TuneIcon sx={{ color: 'text.secondary', fontSize: 20 }} /> <Stack spacing={1.5}>
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
Text-Merkmale <TuneIcon sx={{ color: 'text.secondary', fontSize: 20 }} />
</Typography> <Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
</Box> Text-Merkmale
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SingleLineTextEdit <SingleLineTextEdit
sx={{ flex: 1 }} sx={{ flex: 1 }}
size="small" size="small"
search search
clear clear
value={propFilter} value={propFilter}
disabled={propSubmitting} disabled={propSubmitting}
placeholder="Suchen oder anlegen…" placeholder="Suchen oder anlegen…"
onChange={(next) => this.setState({ propFilter: next })} onChange={(next) => this.setState({ propFilter: next })}
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
event.preventDefault(); event.preventDefault();
void this.handleCreateProperty(); void this.handleCreateProperty();
} }
}} }}
/> />
<Button <Button
variant="contained" variant="contained"
disabled={!propFilterTrimmed || propSubmitting} disabled={!propFilterTrimmed || propSubmitting}
onClick={() => void this.handleCreateProperty()} onClick={() => void this.handleCreateProperty()}
> >
Hinzufügen Hinzufügen
</Button> </Button>
</Box> </Box>
{filteredProperties.length === 0 ? ( {filteredProperties.length === 0 ? (
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}> <Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
{propFilterTrimmed {propFilterTrimmed
? `Keine Treffer für „${propFilterTrimmed}“.` ? `Keine Treffer für „${propFilterTrimmed}“.`
: 'Noch keine Text-Merkmale angelegt.'} : 'Noch keine Text-Merkmale angelegt.'}
</Typography> </Typography>
) : ( ) : (
<FillSpace <FillSpace
items={filteredProperties} items={filteredProperties}
totalCount={properties.length} totalCount={properties.length}
minPageSize={8} minPageSize={8}
maxPageSize={8} maxPageSize={8}
listRef={this.propListRef} listRef={this.propListRef}
onPageChange={(page) => this.setState({ propPage: page })} onPageChange={(page) => this.setState({ propPage: page })}
renderItem={(p) => ( renderItem={(p) => (
<ArtikelMerkmalText <ArtikelMerkmalText
key={p.id} key={p.id}
property={p} property={p}
/> />
)} )}
/> />
)} )}
</Stack> </Stack>
</PanelTile> </PanelTile>
</PanelTileGrid> </PanelTileGrid>
</TabbedPanelTileGrid>
); );
} }
} }