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