This commit is contained in:
seb
2026-10-04 11:25:10 +02:00
parent dd8566eabc
commit 874d6c010f
16 changed files with 345 additions and 346 deletions

View File

@@ -0,0 +1,139 @@
import { Component } from 'react';
import TextField from '@mui/material/TextField';
function normalizeSx(s) {
if (!s || typeof s !== 'object' || Array.isArray(s)) return s;
const bg = s.bgcolor ?? s.backgroundColor;
if (bg && bg !== 'transparent') {
const { bgcolor: _bg, backgroundColor: _bgc, ...rest } = s;
return {
...rest,
bgcolor: 'transparent',
'& .MuiInputBase-root': {
bgcolor: bg,
...(typeof rest['& .MuiInputBase-root'] === 'object' ? rest['& .MuiInputBase-root'] : {}),
},
};
}
return s;
}
function resolveSx(s) {
if (!s) return undefined;
if (Array.isArray(s)) return s.map(resolveSx);
return normalizeSx(s);
}
/**
* TextInput:
* Reusable Class Component wrapping MUI TextField with the harmonized
* style of EANs (small outlined, shrink label by default, transparent root)
* and complete suppression of browser autocomplete/autofill popups.
*/
export default class TextInput extends Component {
state = {
// Starting as readOnly prevents Chrome/Safari/Firefox from attaching autofill popup listeners
readOnly: true,
};
handleFocus = (e) => {
if (this.state.readOnly && !this.props.readOnly && !this.props.slotProps?.htmlInput?.readOnly) {
this.setState({ readOnly: false });
}
if (this.props.onFocus) {
this.props.onFocus(e);
}
};
handleMouseDown = (e) => {
if (this.state.readOnly && !this.props.readOnly && !this.props.slotProps?.htmlInput?.readOnly) {
this.setState({ readOnly: false });
}
if (this.props.onMouseDown) {
this.props.onMouseDown(e);
}
};
render() {
const {
slotProps,
inputProps,
InputProps,
InputLabelProps,
inputRef,
label,
size = 'small',
onFocus,
onMouseDown,
readOnly: propReadOnly,
sx,
...rest
} = this.props;
const isSelect = Boolean(rest.select);
const effectiveReadOnly = isSelect
? (propReadOnly ?? slotProps?.htmlInput?.readOnly ?? inputProps?.readOnly)
: (propReadOnly ?? slotProps?.htmlInput?.readOnly ?? inputProps?.readOnly ?? this.state.readOnly);
const htmlInput = isSelect
? {
readOnly: effectiveReadOnly,
...slotProps?.htmlInput,
...inputProps,
}
: {
autoComplete: 'one-time-code',
autoCorrect: 'off',
autoCapitalize: 'off',
spellCheck: false,
'aria-autocomplete': 'none',
'data-lpignore': 'true',
'data-1p-ignore': 'true',
'data-bwignore': 'true',
'data-form-type': 'other',
readOnly: effectiveReadOnly,
...slotProps?.htmlInput,
...inputProps,
};
const resolvedInputProps = (InputProps || slotProps?.input) ? {
...slotProps?.input,
...InputProps,
startAdornment: (slotProps?.input?.startAdornment && InputProps?.startAdornment)
? <>{slotProps.input.startAdornment}{InputProps.startAdornment}</>
: (InputProps?.startAdornment ?? slotProps?.input?.startAdornment),
endAdornment: (slotProps?.input?.endAdornment && InputProps?.endAdornment)
? <>{InputProps.endAdornment}{slotProps.input.endAdornment}</>
: (InputProps?.endAdornment ?? slotProps?.input?.endAdornment),
} : undefined;
const { input: _unusedInput, ...restSlotProps } = (slotProps || {});
const mergedSlotProps = {
...restSlotProps,
inputLabel: {
...(label ? { shrink: true } : {}),
...InputLabelProps,
...slotProps?.inputLabel,
},
htmlInput,
};
return (
<TextField
size={size}
label={label}
inputRef={inputRef}
autoComplete="one-time-code"
onFocus={this.handleFocus}
onMouseDown={this.handleMouseDown}
InputProps={resolvedInputProps}
slotProps={mergedSlotProps}
sx={resolveSx(sx)}
{...rest}
/>
);
}
}
export { TextInput };

View File

@@ -11,7 +11,7 @@ import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment'; import InputAdornment from '@mui/material/InputAdornment';
import LinearProgress from '@mui/material/LinearProgress'; import LinearProgress from '@mui/material/LinearProgress';
import Skeleton from '@mui/material/Skeleton'; import Skeleton from '@mui/material/Skeleton';
import TextField from '@mui/material/TextField'; import TextInput from '../TextInput';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Chip from '@mui/material/Chip'; import Chip from '@mui/material/Chip';
@@ -358,9 +358,9 @@ export default class ArticleImageSearchDialog extends PureComponent {
}} }}
sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }} sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}
> >
<TextField <TextInput
size="small"
fullWidth fullWidth
label="Suchbegriff"
value={query} value={query}
disabled={isSearching || Boolean(processingId)} disabled={isSearching || Boolean(processingId)}
onChange={(e) => this.setState({ query: e.target.value })} onChange={(e) => this.setState({ query: e.target.value })}

View File

@@ -9,7 +9,7 @@ import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment'; import InputAdornment from '@mui/material/InputAdornment';
import TextField from '@mui/material/TextField'; import TextInput from '../TextInput';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { formatPriceAmount, parsePriceInput } from '../../utils/price'; import { formatPriceAmount, parsePriceInput } from '../../utils/price';
@@ -111,27 +111,24 @@ class ArticlePriceTiersDialog extends Component {
alignItems: 'flex-start', alignItems: 'flex-start',
}} }}
> >
<TextField <TextInput
label="Ab Menge" label="Ab Menge"
value={tier.minmultiplier} value={tier.minmultiplier}
onChange={(event) => onChange={(event) =>
this.handleChange(index, 'minmultiplier', event.target.value) this.handleChange(index, 'minmultiplier', event.target.value)
} }
size="small"
error={tier.minmultiplier !== '' && mult == null} error={tier.minmultiplier !== '' && mult == null}
slotProps={{ slotProps={{
inputLabel: { shrink: true },
htmlInput: { inputMode: 'numeric' }, htmlInput: { inputMode: 'numeric' },
}} }}
sx={{ width: 120 }} sx={{ width: 120 }}
/> />
<TextField <TextInput
label="Brutto" label="Brutto"
value={tier.gross} value={tier.gross}
onChange={(event) => onChange={(event) =>
this.handleChange(index, 'gross', event.target.value) this.handleChange(index, 'gross', event.target.value)
} }
size="small"
fullWidth fullWidth
error={tier.gross !== '' && gross == null} error={tier.gross !== '' && gross == null}
slotProps={{ slotProps={{

View File

@@ -16,7 +16,7 @@ import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select'; import Select from '@mui/material/Select';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField'; import TextInput from '../TextInput';
import ToggleButton from '@mui/material/ToggleButton'; import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
@@ -480,9 +480,8 @@ export default class ArticleStockAdjustDialog extends PureComponent {
{/* Amount input */} {/* Amount input */}
{mode === 'diff' ? ( {mode === 'diff' ? (
<TextField <TextInput
fullWidth fullWidth
size="small"
label="Menge (+ für Zugang, - für Abgang)" label="Menge (+ für Zugang, - für Abgang)"
type="number" type="number"
value={diffValue} value={diffValue}
@@ -509,9 +508,8 @@ export default class ArticleStockAdjustDialog extends PureComponent {
} }
/> />
) : ( ) : (
<TextField <TextInput
fullWidth fullWidth
size="small"
label="Tatsächlicher Zählbestand (Ist-Bestand)" label="Tatsächlicher Zählbestand (Ist-Bestand)"
type="number" type="number"
value={targetValue} value={targetValue}
@@ -560,9 +558,8 @@ export default class ArticleStockAdjustDialog extends PureComponent {
{/* Reason / Memo */} {/* Reason / Memo */}
<Box> <Box>
<TextField <TextInput
fullWidth fullWidth
size="small"
label="Grund / Buchungstext" label="Grund / Buchungstext"
value={reason} value={reason}
onChange={this.handleReasonChange} onChange={this.handleReasonChange}

View File

@@ -9,7 +9,7 @@ import DialogTitle from '@mui/material/DialogTitle';
import InputAdornment from '@mui/material/InputAdornment'; import InputAdornment from '@mui/material/InputAdornment';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField'; import TextInput from '../TextInput';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { companyTaxRateOptions } from '../../utils/articleDetail'; import { companyTaxRateOptions } from '../../utils/articleDetail';
import { formatPriceAmount, parsePriceInput } from '../../utils/price'; import { formatPriceAmount, parsePriceInput } from '../../utils/price';
@@ -135,7 +135,7 @@ export default class ArticleSupplierDialog extends PureComponent {
value={selectedSupplier} value={selectedSupplier}
onChange={(_e, val) => this.setState({ entityId: val ? val.id : null })} onChange={(_e, val) => this.setState({ entityId: val ? val.id : null })}
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextInput
{...params} {...params}
label="Lieferant auswählen" label="Lieferant auswählen"
required required
@@ -146,7 +146,7 @@ export default class ArticleSupplierDialog extends PureComponent {
/> />
)} )}
<TextField <TextInput
label="Lieferanten-Artikelnummer (SKU)" label="Lieferanten-Artikelnummer (SKU)"
value={sku} value={sku}
onChange={(e) => this.setState({ sku: e.target.value })} onChange={(e) => this.setState({ sku: e.target.value })}
@@ -155,22 +155,22 @@ export default class ArticleSupplierDialog extends PureComponent {
/> />
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}> <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
<TextField <TextInput
label="Verpackungseinheit (VPE / PU)" label="Verpackungseinheit (VPE / PU)"
type="number" type="number"
required required
error={!puValid} error={!puValid}
helperText={!puValid ? 'Muss größer als 0 sein' : undefined} helperText={!puValid ? 'Muss größer als 0 sein' : undefined}
inputProps={{ step: 'any', min: 0.0001 }} slotProps={{ htmlInput: { step: 'any', min: 0.0001 } }}
value={pu} value={pu}
onChange={(e) => this.setState({ pu: e.target.value })} onChange={(e) => this.setState({ pu: e.target.value })}
sx={{ flex: 1 }} sx={{ flex: 1 }}
/> />
<TextField <TextInput
label="Bestand beim Lieferanten" label="Bestand beim Lieferanten"
type="number" type="number"
inputProps={{ step: 'any', min: 0 }} slotProps={{ htmlInput: { step: 'any', min: 0 } }}
value={stock} value={stock}
onChange={(e) => this.setState({ stock: e.target.value })} onChange={(e) => this.setState({ stock: e.target.value })}
sx={{ flex: 1 }} sx={{ flex: 1 }}
@@ -179,7 +179,7 @@ export default class ArticleSupplierDialog extends PureComponent {
</Stack> </Stack>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}> <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
<TextField <TextInput
label="Einkaufspreis Netto" label="Einkaufspreis Netto"
value={netPrice} value={netPrice}
onChange={(e) => this.setState({ netPrice: e.target.value })} onChange={(e) => this.setState({ netPrice: e.target.value })}
@@ -193,7 +193,7 @@ export default class ArticleSupplierDialog extends PureComponent {
helperText="Optionaler Einkaufspreis (EK)" helperText="Optionaler Einkaufspreis (EK)"
/> />
<TextField <TextInput
select select
label="MwSt.-Satz" label="MwSt.-Satz"
value={taxRate} value={taxRate}
@@ -208,7 +208,7 @@ export default class ArticleSupplierDialog extends PureComponent {
{rate} % {rate} %
</MenuItem> </MenuItem>
))} ))}
</TextField> </TextInput>
</Stack> </Stack>
</Stack> </Stack>
</DialogContent> </DialogContent>

View File

@@ -13,7 +13,7 @@ import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem'; import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton'; import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText'; import ListItemText from '@mui/material/ListItemText';
import TextField from '@mui/material/TextField'; import TextInput from '../TextInput';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { StorageManagerContext } from '../../api/storageManagerContext'; import { StorageManagerContext } from '../../api/storageManagerContext';
import { requireObject } from '../../utils/apiShape'; import { requireObject } from '../../utils/apiShape';
@@ -241,47 +241,38 @@ export default class ArticleUnitEditorDialog extends Component {
</Box> </Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<TextField <TextInput
label="Name" label="Name"
value={form.name} value={form.name}
onChange={(e) => this.handleField('name', e.target.value)} onChange={(e) => this.handleField('name', e.target.value)}
size="small"
fullWidth fullWidth
disabled={busy} disabled={busy}
slotProps={{ inputLabel: { shrink: true } }}
/> />
<TextField <TextInput
label="Symbol" label="Symbol"
value={form.symbol} value={form.symbol}
onChange={(e) => this.handleField('symbol', e.target.value)} onChange={(e) => this.handleField('symbol', e.target.value)}
size="small"
fullWidth fullWidth
disabled={busy} disabled={busy}
slotProps={{ inputLabel: { shrink: true } }}
/> />
<TextField <TextInput
label="si" label="si"
value={form.si} value={form.si}
onChange={(e) => this.handleField('si', e.target.value)} onChange={(e) => this.handleField('si', e.target.value)}
size="small"
fullWidth fullWidth
disabled={busy} disabled={busy}
slotProps={{ inputLabel: { shrink: true } }}
/> />
<TextField <TextInput
label="basesi" label="basesi"
value={form.basesi} value={form.basesi}
onChange={(e) => this.handleField('basesi', e.target.value)} onChange={(e) => this.handleField('basesi', e.target.value)}
size="small"
fullWidth fullWidth
disabled={busy} disabled={busy}
slotProps={{ inputLabel: { shrink: true } }}
/> />
<TextField <TextInput
label="basesifactor" label="basesifactor"
value={form.basesifactor} value={form.basesifactor}
onChange={(e) => this.handleField('basesifactor', e.target.value)} onChange={(e) => this.handleField('basesifactor', e.target.value)}
size="small"
fullWidth fullWidth
disabled={busy} disabled={busy}
error={ error={
@@ -290,7 +281,6 @@ export default class ArticleUnitEditorDialog extends Component {
parseFactor(form.basesifactor) <= 0) parseFactor(form.basesifactor) <= 0)
} }
slotProps={{ slotProps={{
inputLabel: { shrink: true },
htmlInput: { inputMode: 'decimal' }, htmlInput: { inputMode: 'decimal' },
}} }}
/> />

View File

@@ -1,6 +1,6 @@
import { Component, PureComponent } from 'react'; import { Component, PureComponent } from 'react';
import Autocomplete from '@mui/material/Autocomplete'; import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField'; import TextInput from '../../TextInput';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import { EditablePanelTile } from 'dashgui/panels'; import { EditablePanelTile } from 'dashgui/panels';
@@ -86,22 +86,22 @@ class BrandEdit extends Component {
); );
}} }}
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextInput
{...params} {...params}
inputRef={inputRef} inputRef={inputRef}
size="small" label="Marke"
slotProps={{ InputProps={{
...params.slotProps, ...params.InputProps,
input: { startAdornment: (
...params.slotProps?.input, <>
startAdornment: matchingBrand ? ( {matchingBrand ? (
<InputAdornment position="start" sx={{ mr: 0.5 }}> <InputAdornment position="start" sx={{ mr: 0.5 }}>
<BrandThumb brand={matchingBrand} size={20} /> <BrandThumb brand={matchingBrand} size={20} />
</InputAdornment> </InputAdornment>
) : ( ) : null}
params.slotProps?.input?.startAdornment {params.InputProps?.startAdornment}
), </>
}, ),
}} }}
/> />
)} )}
@@ -182,6 +182,7 @@ export default class ArticleViewBrand extends PureComponent {
wide={1} wide={1}
minHeight={SECOND_ROW_MIN_HEIGHT} minHeight={SECOND_ROW_MIN_HEIGHT}
color="#e9d5ff" color="#e9d5ff"
contentAlignItems="flex-start"
value={value} value={value}
onSave={this.handleSave} onSave={this.handleSave}
onError={notifyError} onError={notifyError}

View File

@@ -1,6 +1,6 @@
import { Component, PureComponent } from 'react'; import { Component, PureComponent } from 'react';
import Autocomplete from '@mui/material/Autocomplete'; import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField'; import TextInput from '../../TextInput';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import { EditablePanelTile } from 'dashgui/panels'; import { EditablePanelTile } from 'dashgui/panels';
@@ -117,18 +117,10 @@ class EansEdit extends Component {
value={draft.eans ?? []} value={draft.eans ?? []}
onChange={(_event, eans) => onChange({ eans })} onChange={(_event, eans) => onChange({ eans })}
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextInput
{...params} {...params}
inputRef={inputRef} inputRef={inputRef}
label="EANs (Enter zum Hinzufügen)" label="EANs (Enter zum Hinzufügen)"
size="small"
slotProps={{
...params.slotProps,
inputLabel: {
...params.slotProps?.inputLabel,
shrink: true,
},
}}
/> />
)} )}
/> />

View File

@@ -11,14 +11,11 @@ import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent'; import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText'; import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import InputLabel from '@mui/material/InputLabel';
import Link from '@mui/material/Link'; import Link from '@mui/material/Link';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select'; import TextInput from '../../TextInput';
import TextField from '@mui/material/TextField';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
@@ -638,21 +635,18 @@ export default class ArticleViewLists extends PureComponent {
} }
isOptionEqualToValue={(opt, val) => opt.sku === val?.sku} isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextInput
{...params} {...params}
label="Artikel"
placeholder="Artikel suchen..." placeholder="Artikel suchen..."
sx={{
bgcolor: 'background.paper',
'& .MuiInputBase-input': { py: 0.5, px: 1, fontSize: '0.82rem' },
}}
/> />
)} )}
sx={{ flex: 1, minWidth: 0 }} sx={{ flex: 1, minWidth: 0 }}
/> />
<TextField <TextInput
size="small"
type="number" type="number"
label="Menge"
value={stuecklisteCount} value={stuecklisteCount}
disabled={busy} disabled={busy}
onChange={(e) => onChange={(e) =>
@@ -660,11 +654,13 @@ export default class ArticleViewLists extends PureComponent {
stuecklisteCount: Math.max(1, parseInt(e.target.value, 10) || 1), stuecklisteCount: Math.max(1, parseInt(e.target.value, 10) || 1),
}) })
} }
inputProps={{ slotProps={{
min: 1, htmlInput: {
style: { padding: '4px 6px', textAlign: 'center', fontSize: '0.82rem' }, min: 1,
style: { textAlign: 'center' },
},
}} }}
sx={{ width: 44, bgcolor: 'background.paper', flexShrink: 0 }} sx={{ width: 72, flexShrink: 0 }}
/> />
<Tooltip title="Komponente hinzufügen"> <Tooltip title="Komponente hinzufügen">
@@ -809,21 +805,17 @@ export default class ArticleViewLists extends PureComponent {
} }
isOptionEqualToValue={(opt, val) => opt.sku === val?.sku} isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextInput
{...params} {...params}
label="Artikel"
placeholder="Artikel suchen..." placeholder="Artikel suchen..."
sx={{
bgcolor: 'background.paper',
'& .MuiInputBase-input': { py: 0.5, px: 1, fontSize: '0.82rem' },
}}
/> />
)} )}
sx={{ width: '100%' }} sx={{ width: '100%' }}
/> />
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
<TextField <TextInput
size="small"
type="number" type="number"
label="Menge" label="Menge"
value={stuecklisteCount} value={stuecklisteCount}
@@ -833,11 +825,13 @@ export default class ArticleViewLists extends PureComponent {
stuecklisteCount: Math.max(1, parseInt(e.target.value, 10) || 1), stuecklisteCount: Math.max(1, parseInt(e.target.value, 10) || 1),
}) })
} }
inputProps={{ slotProps={{
min: 1, htmlInput: {
style: { padding: '4px 6px', textAlign: 'center', fontSize: '0.82rem' }, min: 1,
style: { textAlign: 'center' },
},
}} }}
sx={{ width: 70 }} sx={{ width: 72 }}
/> />
<Box sx={{ display: 'flex', gap: 0.75 }}> <Box sx={{ display: 'flex', gap: 0.75 }}>
@@ -917,21 +911,18 @@ export default class ArticleViewLists extends PureComponent {
} }
isOptionEqualToValue={(opt, val) => opt.sku === val?.sku} isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextInput
{...params} {...params}
label="Option"
placeholder="Option suchen..." placeholder="Option suchen..."
sx={{
bgcolor: 'background.paper',
'& .MuiInputBase-input': { py: 0.5, px: 1, fontSize: '0.82rem' },
}}
/> />
)} )}
sx={{ flex: 1, minWidth: 0 }} sx={{ flex: 1, minWidth: 0 }}
/> />
<TextField <TextInput
size="small"
type="number" type="number"
label="Menge"
value={optionCount} value={optionCount}
disabled={busy} disabled={busy}
onChange={(e) => onChange={(e) =>
@@ -939,11 +930,13 @@ export default class ArticleViewLists extends PureComponent {
optionCount: Math.max(1, parseInt(e.target.value, 10) || 1), optionCount: Math.max(1, parseInt(e.target.value, 10) || 1),
}) })
} }
inputProps={{ slotProps={{
min: 1, htmlInput: {
style: { padding: '4px 6px', textAlign: 'center', fontSize: '0.82rem' }, min: 1,
style: { textAlign: 'center' },
},
}} }}
sx={{ width: 44, bgcolor: 'background.paper', flexShrink: 0 }} sx={{ width: 72, flexShrink: 0 }}
/> />
<Tooltip title="Option hinzufügen"> <Tooltip title="Option hinzufügen">
@@ -1119,21 +1112,17 @@ export default class ArticleViewLists extends PureComponent {
} }
isOptionEqualToValue={(opt, val) => opt.sku === val?.sku} isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextInput
{...params} {...params}
label="Option"
placeholder="Option suchen..." placeholder="Option suchen..."
sx={{
bgcolor: 'background.paper',
'& .MuiInputBase-input': { py: 0.5, px: 1, fontSize: '0.82rem' },
}}
/> />
)} )}
sx={{ width: '100%' }} sx={{ width: '100%' }}
/> />
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
<TextField <TextInput
size="small"
type="number" type="number"
label="Menge" label="Menge"
value={optionCount} value={optionCount}
@@ -1143,11 +1132,13 @@ export default class ArticleViewLists extends PureComponent {
optionCount: Math.max(1, parseInt(e.target.value, 10) || 1), optionCount: Math.max(1, parseInt(e.target.value, 10) || 1),
}) })
} }
inputProps={{ slotProps={{
min: 1, htmlInput: {
style: { padding: '4px 6px', textAlign: 'center', fontSize: '0.82rem' }, min: 1,
style: { textAlign: 'center' },
},
}} }}
sx={{ width: 70 }} sx={{ width: 72 }}
/> />
<FormControlLabel <FormControlLabel
@@ -1231,34 +1222,27 @@ export default class ArticleViewLists extends PureComponent {
Alle vorhandenen Merkmale sind diesem Artikel bereits zugewiesen. Alle vorhandenen Merkmale sind diesem Artikel bereits zugewiesen.
</Typography> </Typography>
) : ( ) : (
<FormControl size="small" fullWidth> <TextInput
<InputLabel id="activate-attr-label" sx={{ fontSize: '0.8rem' }}> select
Merkmal für Varianten fullWidth
</InputLabel> label="Merkmal für Varianten"
<Select value={selectedAttributeId}
labelId="activate-attr-label" onChange={(e) => this.setState({ selectedAttributeId: e.target.value })}
label="Merkmal für Varianten" >
value={selectedAttributeId} {attributes.map((a) => {
size="small" const isAssigned = assignedAttributeIds.has(a.id);
onChange={(e) => this.setState({ selectedAttributeId: e.target.value })} return (
sx={{ fontSize: '0.82rem', height: 32 }} <MenuItem
> key={a.id}
{attributes.map((a) => { value={a.id}
const isAssigned = assignedAttributeIds.has(a.id); disabled={isAssigned}
return ( >
<MenuItem {a.name}
key={a.id} {isAssigned ? ' (bereits zugewiesen)' : ''}
value={a.id} </MenuItem>
disabled={isAssigned} );
sx={{ fontSize: '0.82rem' }} })}
> </TextInput>
{a.name}
{isAssigned ? ' (bereits zugewiesen)' : ''}
</MenuItem>
);
})}
</Select>
</FormControl>
)} )}
<Box sx={{ display: 'flex', gap: 0.75, justifyContent: 'flex-end' }}> <Box sx={{ display: 'flex', gap: 0.75, justifyContent: 'flex-end' }}>
@@ -1475,8 +1459,8 @@ export default class ArticleViewLists extends PureComponent {
{/* Add Child Variant Row */} {/* Add Child Variant Row */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 0.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 0.5 }}>
{isCustomChildValue || availableFatherValues.length === 0 ? ( {isCustomChildValue || availableFatherValues.length === 0 ? (
<TextField <TextInput
size="small" label="Neuer Wert"
placeholder="Neuer Wert..." placeholder="Neuer Wert..."
value={newChildText} value={newChildText}
disabled={busy} disabled={busy}
@@ -1487,38 +1471,26 @@ export default class ArticleViewLists extends PureComponent {
void this.handleAddChild(); void this.handleAddChild();
} }
}} }}
sx={{ sx={{ flex: 1, minWidth: 0 }}
flex: 1,
minWidth: 0,
bgcolor: 'background.paper',
'& .MuiInputBase-input': { py: 0.5, px: 1, fontSize: '0.82rem' },
}}
/> />
) : ( ) : (
<FormControl size="small" sx={{ flex: 1, minWidth: 0 }}> <TextInput
<Select select
size="small" label="Variante"
value={selectedChildValueId} value={selectedChildValueId}
displayEmpty disabled={busy}
disabled={busy} onChange={(e) => this.setState({ selectedChildValueId: e.target.value })}
onChange={(e) => this.setState({ selectedChildValueId: e.target.value })} sx={{ flex: 1, minWidth: 0 }}
sx={{ >
bgcolor: 'background.paper', <MenuItem value="" disabled>
fontSize: '0.82rem', <em>Variante wählen...</em>
height: 32, </MenuItem>
'& .MuiSelect-select': { py: 0.5, px: 1 }, {availableFatherValues.map((v) => (
}} <MenuItem key={v.id} value={v.id}>
> {v.value}
<MenuItem value="" disabled>
<em>Variante wählen...</em>
</MenuItem> </MenuItem>
{availableFatherValues.map((v) => ( ))}
<MenuItem key={v.id} value={v.id} sx={{ fontSize: '0.82rem' }}> </TextInput>
{v.value}
</MenuItem>
))}
</Select>
</FormControl>
)} )}
<Tooltip title="Variante hinzufügen"> <Tooltip title="Variante hinzufügen">

View File

@@ -8,7 +8,7 @@ import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select'; import Select from '@mui/material/Select';
import StraightenOutlinedIcon from '@mui/icons-material/StraightenOutlined'; import StraightenOutlinedIcon from '@mui/icons-material/StraightenOutlined';
import Switch from '@mui/material/Switch'; import Switch from '@mui/material/Switch';
import TextField from '@mui/material/TextField'; import TextInput from '../../TextInput';
import { findUnitBySi, unitLabel } from '../../../utils/units'; import { findUnitBySi, unitLabel } from '../../../utils/units';
const priceUnitCornerSx = { const priceUnitCornerSx = {
@@ -73,15 +73,13 @@ export class PriceUnitRow extends Component {
width: '100%', width: '100%',
}} }}
> >
<TextField <TextInput
label="Menge" label="Menge"
value={unitamount} value={unitamount}
onChange={(event) => onChangeAmount(event.target.value)} onChange={(event) => onChangeAmount(event.target.value)}
size="small"
error={amountError} error={amountError}
sx={{ width: 88, flexShrink: 0 }} sx={{ width: 88, flexShrink: 0 }}
slotProps={{ slotProps={{
inputLabel: { shrink: true },
htmlInput: { inputMode: 'decimal' }, htmlInput: { inputMode: 'decimal' },
}} }}
/> />

View File

@@ -3,10 +3,7 @@ import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select'; import TextInput from '../../TextInput';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Chip from '@mui/material/Chip'; import Chip from '@mui/material/Chip';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
@@ -397,51 +394,45 @@ export default class ArticleAttributesTab extends PureComponent {
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{/* Unified Add Bar: [Merkmal ▼] [Wert ▼ / Input] [+] */} {/* Unified Add Bar: [Merkmal ▼] [Wert ▼ / Input] [+] */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<FormControl size="small" sx={{ minWidth: 170 }}> <TextInput
<InputLabel id="merkmal-select-label">Merkmal</InputLabel> select
<Select label="Merkmal"
labelId="merkmal-select-label" value={selectedMerkmalKey}
value={selectedMerkmalKey} disabled={saving || merkmalOptions.length === 0}
label="Merkmal" onChange={this.handleMerkmalChange}
disabled={saving || merkmalOptions.length === 0} sx={{ minWidth: 170 }}
onChange={this.handleMerkmalChange} >
sx={{ bgcolor: 'background.paper' }} {merkmalOptions.map((m) => (
> <MenuItem key={m.key} value={m.key}>
{merkmalOptions.map((m) => ( {m.name}
<MenuItem key={m.key} value={m.key}> </MenuItem>
{m.name} ))}
</MenuItem> </TextInput>
))}
</Select>
</FormControl>
{isAttr ? ( {isAttr ? (
<FormControl size="small" sx={{ minWidth: 170 }}> <TextInput
<InputLabel id="item-select-label">Wert</InputLabel> select
<Select label="Wert"
labelId="item-select-label" value={selectedItemId}
value={selectedItemId} disabled={saving || availableItems.length === 0}
label="Wert" onChange={this.handleItemChange}
disabled={saving || availableItems.length === 0} sx={{ minWidth: 170 }}
onChange={this.handleItemChange} >
sx={{ bgcolor: 'background.paper' }} {availableItems.length === 0 ? (
> <MenuItem value="" disabled>
{availableItems.length === 0 ? ( (Keine Werte)
<MenuItem value="" disabled> </MenuItem>
(Keine Werte) ) : (
availableItems.map((val) => (
<MenuItem key={val.id} value={val.id}>
{val.value}
</MenuItem> </MenuItem>
) : ( ))
availableItems.map((val) => ( )}
<MenuItem key={val.id} value={val.id}> </TextInput>
{val.value}
</MenuItem>
))
)}
</Select>
</FormControl>
) : ( ) : (
<TextField <TextInput
size="small" label="Wert"
placeholder="Wert..." placeholder="Wert..."
value={customValueText} value={customValueText}
disabled={saving || !selectedMerkmalKey} disabled={saving || !selectedMerkmalKey}
@@ -452,7 +443,7 @@ export default class ArticleAttributesTab extends PureComponent {
void this.handleAddItem(); void this.handleAddItem();
} }
}} }}
sx={{ minWidth: 170, bgcolor: 'background.paper' }} sx={{ minWidth: 170 }}
/> />
)} )}

View File

@@ -3,7 +3,7 @@ import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField'; import TextInput from '../../TextInput';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import Grid from '@mui/material/Grid'; import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper'; import Paper from '@mui/material/Paper';
@@ -210,22 +210,21 @@ export default class ArticleLabelTab extends PureComponent {
<Box> <Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 3, alignItems: 'flex-start' }}> <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 3, alignItems: 'flex-start' }}>
<TextField <TextInput
select select
label="Drucker" label="Drucker"
value={selectedPrinterId} value={selectedPrinterId}
onChange={(e) => this.setState({ selectedPrinterId: e.target.value })} onChange={(e) => this.setState({ selectedPrinterId: e.target.value })}
sx={{ minWidth: 200 }} sx={{ minWidth: 200 }}
size="small"
> >
{printers.map((p) => ( {printers.map((p) => (
<MenuItem key={p.id} value={p.id}> <MenuItem key={p.id} value={p.id}>
{p.desc ? `${p.desc} (${p.address})` : p.address} {p.desc ? `${p.desc} (${p.address})` : p.address}
</MenuItem> </MenuItem>
))} ))}
</TextField> </TextInput>
<TextField <TextInput
type="number" type="number"
label="Anzahl" label="Anzahl"
value={quantity} value={quantity}
@@ -233,9 +232,8 @@ export default class ArticleLabelTab extends PureComponent {
const val = parseInt(e.target.value, 10); const val = parseInt(e.target.value, 10);
this.setState({ quantity: isNaN(val) ? 1 : Math.max(1, val) }); this.setState({ quantity: isNaN(val) ? 1 : Math.max(1, val) });
}} }}
inputProps={{ min: 1, max: 9999 }} slotProps={{ htmlInput: { min: 1, max: 9999 } }}
sx={{ width: 100 }} sx={{ width: 100 }}
size="small"
/> />
<Button <Button

View File

@@ -11,7 +11,7 @@ import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment'; import InputAdornment from '@mui/material/InputAdornment';
import LinearProgress from '@mui/material/LinearProgress'; import LinearProgress from '@mui/material/LinearProgress';
import Skeleton from '@mui/material/Skeleton'; import Skeleton from '@mui/material/Skeleton';
import TextField from '@mui/material/TextField'; import TextInput from '../TextInput';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Chip from '@mui/material/Chip'; import Chip from '@mui/material/Chip';
@@ -276,9 +276,9 @@ export default class BrandImageSearchDialog extends PureComponent {
}} }}
sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1.5 }} sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1.5 }}
> >
<TextField <TextInput
size="small"
fullWidth fullWidth
label="Suchbegriff"
value={query} value={query}
disabled={isSearching || Boolean(processingId)} disabled={isSearching || Boolean(processingId)}
onChange={(e) => this.setState({ query: e.target.value })} onChange={(e) => this.setState({ query: e.target.value })}

View File

@@ -310,8 +310,11 @@ export class ArticleTile extends Tile {
} }
renderExtra() { renderExtra() {
const childCount = Number(this.props.article?.childCount) || 0; const { article } = this.props;
if (childCount <= 0) return null; if (!article) return null;
const isFather = Boolean(article.isFather || article.is_father);
const childCount = Number(article.childCount) || 0;
if (!isFather && childCount <= 0) return null;
return ( return (
<Box <Box
sx={{ sx={{

View File

@@ -166,7 +166,7 @@ export const SUB_NAV = {
{ {
segment: 'merkmale', segment: 'merkmale',
label: 'Merkmale', label: 'Merkmale',
title: 'Merkmale & Attribute', title: 'Merkmale',
icon: CategoryIcon, icon: CategoryIcon,
help: 'Globale Merkmale, Attribute und Werte verwalten', help: 'Globale Merkmale, Attribute und Werte verwalten',
}, },

View File

@@ -8,9 +8,6 @@ import ListItem from '@mui/material/ListItem';
import Stack from '@mui/material/Stack'; import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Chip from '@mui/material/Chip'; import Chip from '@mui/material/Chip';
import Tooltip from '@mui/material/Tooltip';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
import TuneIcon from '@mui/icons-material/Tune'; import TuneIcon from '@mui/icons-material/Tune';
@@ -47,12 +44,10 @@ class ArtikelMerkmalePage extends PureComponent {
properties: initialProperties, properties: initialProperties,
propDrafts: draftsFrom(initialProperties, 'name'), propDrafts: draftsFrom(initialProperties, 'name'),
newPropName: '', newPropName: '',
newPropExclusive: true,
attributes: initialAttributes, attributes: initialAttributes,
attrDrafts: draftsFrom(initialAttributes, 'name'), attrDrafts: draftsFrom(initialAttributes, 'name'),
newAttrName: '', newAttrName: '',
newAttrExclusive: true,
selectedAttributeId: initialSelectedAttrId, selectedAttributeId: initialSelectedAttrId,
valDrafts: draftsFrom( valDrafts: draftsFrom(
@@ -333,13 +328,13 @@ class ArtikelMerkmalePage extends PureComponent {
async handleCreateProperty() { async handleCreateProperty() {
const name = this.state.newPropName.trim(); const name = this.state.newPropName.trim();
if (!name || this.state.busyKey != null) return; if (!name) return;
this.setState({ busyKey: 'new-prop' }); this.setState({ busyKey: 'new-prop' });
try { try {
const row = await propertyAutomat.actions.createProperty({ const row = await propertyAutomat.actions.createProperty({
name, name,
exclusive: this.state.newPropExclusive, exclusive: true,
}); });
if (this._cancelled) return; if (this._cancelled) return;
this.setState({ newPropName: '', busyKey: null }); this.setState({ newPropName: '', busyKey: null });
@@ -353,7 +348,6 @@ class ArtikelMerkmalePage extends PureComponent {
async handleTogglePropertyExclusive(prop, e) { async handleTogglePropertyExclusive(prop, e) {
e?.stopPropagation(); e?.stopPropagation();
if (this.state.busyKey != null) return;
const nextExclusive = !prop.exclusive; const nextExclusive = !prop.exclusive;
this.setState({ busyKey: `prop-ex-${prop.id}` }); this.setState({ busyKey: `prop-ex-${prop.id}` });
@@ -380,7 +374,7 @@ class ArtikelMerkmalePage extends PureComponent {
})); }));
return; return;
} }
if (draft === prop.name || this.state.busyKey != null) return; if (draft === prop.name) return;
this.setState({ busyKey: `prop-${prop.id}` }); this.setState({ busyKey: `prop-${prop.id}` });
try { try {
@@ -399,7 +393,6 @@ class ArtikelMerkmalePage extends PureComponent {
} }
async handleDeleteProperty(prop) { async handleDeleteProperty(prop) {
if (this.state.busyKey != null) return;
if (!window.confirm(`Merkmal „${prop.name}" wirklich löschen?`)) { if (!window.confirm(`Merkmal „${prop.name}" wirklich löschen?`)) {
return; return;
} }
@@ -421,13 +414,13 @@ class ArtikelMerkmalePage extends PureComponent {
async handleCreateAttribute() { async handleCreateAttribute() {
const name = this.state.newAttrName.trim(); const name = this.state.newAttrName.trim();
if (!name || this.state.busyKey != null) return; if (!name) return;
this.setState({ busyKey: 'new-attr' }); this.setState({ busyKey: 'new-attr' });
try { try {
const row = await attributeAutomat.actions.createAttribute({ const row = await attributeAutomat.actions.createAttribute({
name, name,
exclusive: this.state.newAttrExclusive, exclusive: true,
}); });
if (this._cancelled) return; if (this._cancelled) return;
this.setState({ this.setState({
@@ -451,7 +444,7 @@ class ArtikelMerkmalePage extends PureComponent {
})); }));
return; return;
} }
if (draft === attr.name || this.state.busyKey != null) return; if (draft === attr.name) return;
this.setState({ busyKey: `attr-${attr.id}` }); this.setState({ busyKey: `attr-${attr.id}` });
try { try {
@@ -471,7 +464,6 @@ class ArtikelMerkmalePage extends PureComponent {
async handleToggleAttributeExclusive(attr, e) { async handleToggleAttributeExclusive(attr, e) {
e?.stopPropagation(); e?.stopPropagation();
if (this.state.busyKey != null) return;
const nextExclusive = !attr.exclusive; const nextExclusive = !attr.exclusive;
this.setState({ busyKey: `attr-ex-${attr.id}` }); this.setState({ busyKey: `attr-ex-${attr.id}` });
@@ -492,7 +484,6 @@ class ArtikelMerkmalePage extends PureComponent {
async handleDeleteAttribute(attr, e) { async handleDeleteAttribute(attr, e) {
e?.stopPropagation(); e?.stopPropagation();
if (this.state.busyKey != null) return;
if (!window.confirm(`Attribut „${attr.name}" und alle zugehörigen Werte wirklich löschen?`)) { if (!window.confirm(`Attribut „${attr.name}" und alle zugehörigen Werte wirklich löschen?`)) {
return; return;
} }
@@ -523,7 +514,7 @@ class ArtikelMerkmalePage extends PureComponent {
async handleCreateValue(attrId) { async handleCreateValue(attrId) {
const text = this.state.newValText.trim(); const text = this.state.newValText.trim();
if (!text || this.state.busyKey != null || !attrId) return; if (!text || !attrId) return;
this.setState({ busyKey: 'new-val' }); this.setState({ busyKey: 'new-val' });
try { try {
@@ -546,7 +537,7 @@ class ArtikelMerkmalePage extends PureComponent {
})); }));
return; return;
} }
if (draft === val.value || this.state.busyKey != null) return; if (draft === val.value) return;
this.setState({ busyKey: `val-${val.id}` }); this.setState({ busyKey: `val-${val.id}` });
try { try {
@@ -565,7 +556,6 @@ class ArtikelMerkmalePage extends PureComponent {
} }
async handleDeleteValue(val) { async handleDeleteValue(val) {
if (this.state.busyKey != null) return;
if (!window.confirm(`Wert „${val.value}" wirklich löschen?`)) { if (!window.confirm(`Wert „${val.value}" wirklich löschen?`)) {
return; return;
} }
@@ -591,18 +581,15 @@ class ArtikelMerkmalePage extends PureComponent {
properties, properties,
propDrafts, propDrafts,
newPropName, newPropName,
newPropExclusive,
attributes, attributes,
attrDrafts, attrDrafts,
newAttrName, newAttrName,
newAttrExclusive,
selectedAttributeId, selectedAttributeId,
valDrafts, valDrafts,
newValText, newValText,
busyKey, busyKey,
} = this.state; } = this.state;
const busy = busyKey != null;
if (attrStatus === 'loading' && propStatus === 'loading' && !properties.length && !attributes.length) { if (attrStatus === 'loading' && propStatus === 'loading' && !properties.length && !attributes.length) {
return ( return (
@@ -638,7 +625,6 @@ class ArtikelMerkmalePage extends PureComponent {
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}> <Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
Auswahl-Merkmale Auswahl-Merkmale
</Typography> </Typography>
<Chip label={attributes.length} size="small" sx={{ ml: 'auto', fontWeight: 600, height: 20 }} />
</Box> </Box>
<List dense disablePadding ref={this.attrListRef}> <List dense disablePadding ref={this.attrListRef}>
@@ -693,7 +679,6 @@ class ArtikelMerkmalePage extends PureComponent {
size="small" size="small"
label="" label=""
value={attrDrafts[a.id] ?? ''} value={attrDrafts[a.id] ?? ''}
disabled={busy}
onChange={(next) => onChange={(next) =>
this.setState((prev) => ({ this.setState((prev) => ({
attrDrafts: { ...prev.attrDrafts, [a.id]: next }, attrDrafts: { ...prev.attrDrafts, [a.id]: next },
@@ -717,34 +702,29 @@ class ArtikelMerkmalePage extends PureComponent {
/> />
</Box> </Box>
<Tooltip <Chip
title={ label="Multi"
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" size="small"
color={a.exclusive ? 'primary' : 'default'} color={a.exclusive ? 'default' : 'success'}
variant={a.exclusive ? 'filled' : 'outlined'} variant="filled"
onClick={(e) => void this.handleToggleAttributeExclusive(a, e)} onClick={(e) => void this.handleToggleAttributeExclusive(a, e)}
aria-pressed={!a.exclusive}
sx={{ sx={{
height: 22, height: 22,
fontSize: '0.72rem', fontSize: '0.72rem',
fontWeight: 600, fontWeight: 600,
cursor: 'pointer', cursor: 'pointer',
flexShrink: 0, flexShrink: 0,
...(a.exclusive
? { bgcolor: 'grey.300', color: 'text.secondary' }
: null),
}} }}
/> />
</Tooltip>
<IconButton <IconButton
edge="end" edge="end"
size="small" size="small"
aria-label={`„${a.name}" löschen`} aria-label={`„${a.name}" löschen`}
disabled={busy}
onClick={(e) => void this.handleDeleteAttribute(a, e)} onClick={(e) => void this.handleDeleteAttribute(a, e)}
sx={{ flexShrink: 0 }} sx={{ flexShrink: 0 }}
> >
@@ -761,7 +741,6 @@ class ArtikelMerkmalePage extends PureComponent {
size="small" size="small"
label="" label=""
value={newAttrName} value={newAttrName}
disabled={busy}
onChange={(next) => this.setState({ newAttrName: next })} onChange={(next) => this.setState({ newAttrName: next })}
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
@@ -774,29 +753,12 @@ class ArtikelMerkmalePage extends PureComponent {
<Button <Button
variant="contained" variant="contained"
size="small" size="small"
disabled={busy || !newAttrName.trim()} disabled={!newAttrName.trim()}
onClick={() => void this.handleCreateAttribute()} onClick={() => void this.handleCreateAttribute()}
> >
{busyKey === 'new-attr' ? '…' : 'Hinzufügen'} {busyKey === 'new-attr' ? '…' : 'Hinzufügen'}
</Button> </Button>
</Box> </Box>
<FormControlLabel
control={
<Checkbox
size="small"
checked={newAttrExclusive}
disabled={busy}
onChange={(e) => this.setState({ newAttrExclusive: 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> </Box>
</Stack> </Stack>
</PanelTile> </PanelTile>
@@ -807,15 +769,8 @@ class ArtikelMerkmalePage extends PureComponent {
<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 „${selectedAttr.name}“` : 'Werte'} {selectedAttr ? `Werte für Auswahl-Merkmal „${selectedAttr.name}“` : 'Werte'}
</Typography> </Typography>
{selectedAttr ? (
<Chip
label={selectedValues.length}
size="small"
sx={{ ml: 'auto', fontWeight: 600, height: 20 }}
/>
) : null}
</Box> </Box>
{!selectedAttr ? ( {!selectedAttr ? (
@@ -825,12 +780,7 @@ class ArtikelMerkmalePage extends PureComponent {
) : ( ) : (
<> <>
<List dense disablePadding ref={this.valListRef}> <List dense disablePadding ref={this.valListRef}>
{selectedValues.length === 0 ? ( {selectedValues.map((v) => (
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
Noch keine Werte für „{selectedAttr.name}“ hinterlegt.
</Typography>
) : (
selectedValues.map((v) => (
<ListItem <ListItem
key={v.id} key={v.id}
disableGutters disableGutters
@@ -855,7 +805,6 @@ class ArtikelMerkmalePage extends PureComponent {
size="small" size="small"
label="" label=""
value={valDrafts[v.id] ?? ''} value={valDrafts[v.id] ?? ''}
disabled={busy}
onChange={(next) => onChange={(next) =>
this.setState((prev) => ({ this.setState((prev) => ({
valDrafts: { ...prev.valDrafts, [v.id]: next }, valDrafts: { ...prev.valDrafts, [v.id]: next },
@@ -881,14 +830,12 @@ class ArtikelMerkmalePage extends PureComponent {
edge="end" edge="end"
size="small" size="small"
aria-label={`„${v.value}" löschen`} aria-label={`„${v.value}" löschen`}
disabled={busy}
onClick={() => void this.handleDeleteValue(v)} onClick={() => void this.handleDeleteValue(v)}
> >
<DeleteOutlinedIcon fontSize="small" /> <DeleteOutlinedIcon fontSize="small" />
</IconButton> </IconButton>
</ListItem> </ListItem>
)) ))}
)}
</List> </List>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@@ -896,7 +843,6 @@ class ArtikelMerkmalePage extends PureComponent {
size="small" size="small"
label="" label=""
value={newValText} value={newValText}
disabled={busy}
onChange={(next) => this.setState({ newValText: next })} onChange={(next) => this.setState({ newValText: next })}
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
@@ -909,7 +855,7 @@ class ArtikelMerkmalePage extends PureComponent {
<Button <Button
variant="contained" variant="contained"
size="small" size="small"
disabled={busy || !newValText.trim()} disabled={!newValText.trim()}
onClick={() => void this.handleCreateValue(selectedAttr.id)} onClick={() => void this.handleCreateValue(selectedAttr.id)}
> >
{busyKey === 'new-val' ? '…' : 'Hinzufügen'} {busyKey === 'new-val' ? '…' : 'Hinzufügen'}
@@ -928,7 +874,6 @@ class ArtikelMerkmalePage extends PureComponent {
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}> <Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
Text-Merkmale Text-Merkmale
</Typography> </Typography>
<Chip label={properties.length} size="small" sx={{ ml: 'auto', fontWeight: 600, height: 20 }} />
</Box> </Box>
<List dense disablePadding ref={this.propListRef}> <List dense disablePadding ref={this.propListRef}>
@@ -957,7 +902,6 @@ class ArtikelMerkmalePage extends PureComponent {
size="small" size="small"
label="" label=""
value={propDrafts[p.id] ?? ''} value={propDrafts[p.id] ?? ''}
disabled={busy}
onChange={(next) => onChange={(next) =>
this.setState((prev) => ({ this.setState((prev) => ({
propDrafts: { ...prev.propDrafts, [p.id]: next }, propDrafts: { ...prev.propDrafts, [p.id]: next },
@@ -979,33 +923,28 @@ class ArtikelMerkmalePage extends PureComponent {
}} }}
sx={{ flex: 1 }} sx={{ flex: 1 }}
/> />
<Tooltip <Chip
title={ label="Multi"
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" size="small"
color={p.exclusive ? 'primary' : 'default'} color={p.exclusive ? 'default' : 'success'}
variant={p.exclusive ? 'filled' : 'outlined'} variant="filled"
onClick={(e) => void this.handleTogglePropertyExclusive(p, e)} onClick={(e) => void this.handleTogglePropertyExclusive(p, e)}
aria-pressed={!p.exclusive}
sx={{ sx={{
height: 22, height: 22,
fontSize: '0.72rem', fontSize: '0.72rem',
fontWeight: 600, fontWeight: 600,
cursor: 'pointer', cursor: 'pointer',
flexShrink: 0, flexShrink: 0,
...(p.exclusive
? { bgcolor: 'grey.300', color: 'text.secondary' }
: null),
}} }}
/> />
</Tooltip>
<IconButton <IconButton
edge="end" edge="end"
size="small" size="small"
aria-label={`„${p.name}" löschen`} aria-label={`„${p.name}" löschen`}
disabled={busy}
onClick={() => void this.handleDeleteProperty(p)} onClick={() => void this.handleDeleteProperty(p)}
> >
<DeleteOutlinedIcon fontSize="small" /> <DeleteOutlinedIcon fontSize="small" />
@@ -1020,7 +959,6 @@ class ArtikelMerkmalePage extends PureComponent {
size="small" size="small"
label="" label=""
value={newPropName} value={newPropName}
disabled={busy}
onChange={(next) => this.setState({ newPropName: next })} onChange={(next) => this.setState({ newPropName: next })}
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
@@ -1033,29 +971,12 @@ class ArtikelMerkmalePage extends PureComponent {
<Button <Button
variant="contained" variant="contained"
size="small" size="small"
disabled={busy || !newPropName.trim()} disabled={!newPropName.trim()}
onClick={() => void this.handleCreateProperty()} onClick={() => void this.handleCreateProperty()}
> >
{busyKey === 'new-prop' ? '…' : 'Hinzufügen'} {busyKey === 'new-prop' ? '…' : 'Hinzufügen'}
</Button> </Button>
</Box> </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> </Box>
</Stack> </Stack>
</PanelTile> </PanelTile>