diff --git a/src/components/TextInput.jsx b/src/components/TextInput.jsx
new file mode 100644
index 0000000..6ab9fb7
--- /dev/null
+++ b/src/components/TextInput.jsx
@@ -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 (
+
+ );
+ }
+}
+
+export { TextInput };
diff --git a/src/components/article/ArticleImageSearchDialog.jsx b/src/components/article/ArticleImageSearchDialog.jsx
index 2aa2b44..e74c485 100644
--- a/src/components/article/ArticleImageSearchDialog.jsx
+++ b/src/components/article/ArticleImageSearchDialog.jsx
@@ -11,7 +11,7 @@ import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import LinearProgress from '@mui/material/LinearProgress';
import Skeleton from '@mui/material/Skeleton';
-import TextField from '@mui/material/TextField';
+import TextInput from '../TextInput';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
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 }}
>
- this.setState({ query: e.target.value })}
diff --git a/src/components/article/ArticlePriceTiersDialog.jsx b/src/components/article/ArticlePriceTiersDialog.jsx
index 684b644..1aeb7b6 100644
--- a/src/components/article/ArticlePriceTiersDialog.jsx
+++ b/src/components/article/ArticlePriceTiersDialog.jsx
@@ -9,7 +9,7 @@ import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
-import TextField from '@mui/material/TextField';
+import TextInput from '../TextInput';
import Typography from '@mui/material/Typography';
import { formatPriceAmount, parsePriceInput } from '../../utils/price';
@@ -111,27 +111,24 @@ class ArticlePriceTiersDialog extends Component {
alignItems: 'flex-start',
}}
>
-
this.handleChange(index, 'minmultiplier', event.target.value)
}
- size="small"
error={tier.minmultiplier !== '' && mult == null}
slotProps={{
- inputLabel: { shrink: true },
htmlInput: { inputMode: 'numeric' },
}}
sx={{ width: 120 }}
/>
-
this.handleChange(index, 'gross', event.target.value)
}
- size="small"
fullWidth
error={tier.gross !== '' && gross == null}
slotProps={{
diff --git a/src/components/article/ArticleStockAdjustDialog.jsx b/src/components/article/ArticleStockAdjustDialog.jsx
index 3b9511f..3261a8f 100644
--- a/src/components/article/ArticleStockAdjustDialog.jsx
+++ b/src/components/article/ArticleStockAdjustDialog.jsx
@@ -16,7 +16,7 @@ import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import Stack from '@mui/material/Stack';
-import TextField from '@mui/material/TextField';
+import TextInput from '../TextInput';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import Typography from '@mui/material/Typography';
@@ -480,9 +480,8 @@ export default class ArticleStockAdjustDialog extends PureComponent {
{/* Amount input */}
{mode === 'diff' ? (
-
) : (
-
- this.setState({ entityId: val ? val.id : null })}
renderInput={(params) => (
-
)}
- this.setState({ sku: e.target.value })}
@@ -155,22 +155,22 @@ export default class ArticleSupplierDialog extends PureComponent {
/>
- this.setState({ pu: e.target.value })}
sx={{ flex: 1 }}
/>
- this.setState({ stock: e.target.value })}
sx={{ flex: 1 }}
@@ -179,7 +179,7 @@ export default class ArticleSupplierDialog extends PureComponent {
- this.setState({ netPrice: e.target.value })}
@@ -193,7 +193,7 @@ export default class ArticleSupplierDialog extends PureComponent {
helperText="Optionaler Einkaufspreis (EK)"
/>
-
))}
-
+
diff --git a/src/components/article/ArticleUnitEditorDialog.jsx b/src/components/article/ArticleUnitEditorDialog.jsx
index 5ebd889..0c26bb8 100644
--- a/src/components/article/ArticleUnitEditorDialog.jsx
+++ b/src/components/article/ArticleUnitEditorDialog.jsx
@@ -13,7 +13,7 @@ import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
-import TextField from '@mui/material/TextField';
+import TextInput from '../TextInput';
import Typography from '@mui/material/Typography';
import { StorageManagerContext } from '../../api/storageManagerContext';
import { requireObject } from '../../utils/apiShape';
@@ -241,47 +241,38 @@ export default class ArticleUnitEditorDialog extends Component {
- this.handleField('name', e.target.value)}
- size="small"
fullWidth
disabled={busy}
- slotProps={{ inputLabel: { shrink: true } }}
/>
- this.handleField('symbol', e.target.value)}
- size="small"
fullWidth
disabled={busy}
- slotProps={{ inputLabel: { shrink: true } }}
/>
- this.handleField('si', e.target.value)}
- size="small"
fullWidth
disabled={busy}
- slotProps={{ inputLabel: { shrink: true } }}
/>
- this.handleField('basesi', e.target.value)}
- size="small"
fullWidth
disabled={busy}
- slotProps={{ inputLabel: { shrink: true } }}
/>
- this.handleField('basesifactor', e.target.value)}
- size="small"
fullWidth
disabled={busy}
error={
@@ -290,7 +281,6 @@ export default class ArticleUnitEditorDialog extends Component {
parseFactor(form.basesifactor) <= 0)
}
slotProps={{
- inputLabel: { shrink: true },
htmlInput: { inputMode: 'decimal' },
}}
/>
diff --git a/src/components/article/boxes/ArticleViewBrand.jsx b/src/components/article/boxes/ArticleViewBrand.jsx
index 9fc4264..9af2365 100644
--- a/src/components/article/boxes/ArticleViewBrand.jsx
+++ b/src/components/article/boxes/ArticleViewBrand.jsx
@@ -1,6 +1,6 @@
import { Component, PureComponent } from 'react';
import Autocomplete from '@mui/material/Autocomplete';
-import TextField from '@mui/material/TextField';
+import TextInput from '../../TextInput';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import { EditablePanelTile } from 'dashgui/panels';
@@ -86,22 +86,22 @@ class BrandEdit extends Component {
);
}}
renderInput={(params) => (
-
-
-
- ) : (
- params.slotProps?.input?.startAdornment
- ),
- },
+ label="Marke"
+ InputProps={{
+ ...params.InputProps,
+ startAdornment: (
+ <>
+ {matchingBrand ? (
+
+
+
+ ) : null}
+ {params.InputProps?.startAdornment}
+ >
+ ),
}}
/>
)}
@@ -182,6 +182,7 @@ export default class ArticleViewBrand extends PureComponent {
wide={1}
minHeight={SECOND_ROW_MIN_HEIGHT}
color="#e9d5ff"
+ contentAlignItems="flex-start"
value={value}
onSave={this.handleSave}
onError={notifyError}
diff --git a/src/components/article/boxes/ArticleViewEANs.jsx b/src/components/article/boxes/ArticleViewEANs.jsx
index 4c8fe76..bd8b507 100644
--- a/src/components/article/boxes/ArticleViewEANs.jsx
+++ b/src/components/article/boxes/ArticleViewEANs.jsx
@@ -1,6 +1,6 @@
import { Component, PureComponent } from 'react';
import Autocomplete from '@mui/material/Autocomplete';
-import TextField from '@mui/material/TextField';
+import TextInput from '../../TextInput';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import { EditablePanelTile } from 'dashgui/panels';
@@ -117,18 +117,10 @@ class EansEdit extends Component {
value={draft.eans ?? []}
onChange={(_event, eans) => onChange({ eans })}
renderInput={(params) => (
-
)}
/>
diff --git a/src/components/article/boxes/ArticleViewLists.jsx b/src/components/article/boxes/ArticleViewLists.jsx
index 8cef473..0f1cfe3 100644
--- a/src/components/article/boxes/ArticleViewLists.jsx
+++ b/src/components/article/boxes/ArticleViewLists.jsx
@@ -11,14 +11,11 @@ import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
-import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import IconButton from '@mui/material/IconButton';
-import InputLabel from '@mui/material/InputLabel';
import Link from '@mui/material/Link';
import MenuItem from '@mui/material/MenuItem';
-import Select from '@mui/material/Select';
-import TextField from '@mui/material/TextField';
+import TextInput from '../../TextInput';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import AddIcon from '@mui/icons-material/Add';
@@ -638,21 +635,18 @@ export default class ArticleViewLists extends PureComponent {
}
isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
renderInput={(params) => (
-
)}
sx={{ flex: 1, minWidth: 0 }}
/>
-
@@ -660,11 +654,13 @@ export default class ArticleViewLists extends PureComponent {
stuecklisteCount: Math.max(1, parseInt(e.target.value, 10) || 1),
})
}
- inputProps={{
- min: 1,
- style: { padding: '4px 6px', textAlign: 'center', fontSize: '0.82rem' },
+ slotProps={{
+ htmlInput: {
+ min: 1,
+ style: { textAlign: 'center' },
+ },
}}
- sx={{ width: 44, bgcolor: 'background.paper', flexShrink: 0 }}
+ sx={{ width: 72, flexShrink: 0 }}
/>
@@ -809,21 +805,17 @@ export default class ArticleViewLists extends PureComponent {
}
isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
renderInput={(params) => (
-
)}
sx={{ width: '100%' }}
/>
-
@@ -917,21 +911,18 @@ export default class ArticleViewLists extends PureComponent {
}
isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
renderInput={(params) => (
-
)}
sx={{ flex: 1, minWidth: 0 }}
/>
-
@@ -939,11 +930,13 @@ export default class ArticleViewLists extends PureComponent {
optionCount: Math.max(1, parseInt(e.target.value, 10) || 1),
})
}
- inputProps={{
- min: 1,
- style: { padding: '4px 6px', textAlign: 'center', fontSize: '0.82rem' },
+ slotProps={{
+ htmlInput: {
+ min: 1,
+ style: { textAlign: 'center' },
+ },
}}
- sx={{ width: 44, bgcolor: 'background.paper', flexShrink: 0 }}
+ sx={{ width: 72, flexShrink: 0 }}
/>
@@ -1119,21 +1112,17 @@ export default class ArticleViewLists extends PureComponent {
}
isOptionEqualToValue={(opt, val) => opt.sku === val?.sku}
renderInput={(params) => (
-
)}
sx={{ width: '100%' }}
/>
-
) : (
-
-
- Merkmal für Varianten
-
-
-
+ this.setState({ selectedAttributeId: e.target.value })}
+ >
+ {attributes.map((a) => {
+ const isAssigned = assignedAttributeIds.has(a.id);
+ return (
+
+ );
+ })}
+
)}
@@ -1475,8 +1459,8 @@ export default class ArticleViewLists extends PureComponent {
{/* Add Child Variant Row */}
{isCustomChildValue || availableFatherValues.length === 0 ? (
-
) : (
-
-
-
+ ))}
+
)}
diff --git a/src/components/article/boxes/PriceUnitRow.jsx b/src/components/article/boxes/PriceUnitRow.jsx
index 9eddfd1..7768778 100644
--- a/src/components/article/boxes/PriceUnitRow.jsx
+++ b/src/components/article/boxes/PriceUnitRow.jsx
@@ -8,7 +8,7 @@ import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import StraightenOutlinedIcon from '@mui/icons-material/StraightenOutlined';
import Switch from '@mui/material/Switch';
-import TextField from '@mui/material/TextField';
+import TextInput from '../../TextInput';
import { findUnitBySi, unitLabel } from '../../../utils/units';
const priceUnitCornerSx = {
@@ -73,15 +73,13 @@ export class PriceUnitRow extends Component {
width: '100%',
}}
>
- onChangeAmount(event.target.value)}
- size="small"
error={amountError}
sx={{ width: 88, flexShrink: 0 }}
slotProps={{
- inputLabel: { shrink: true },
htmlInput: { inputMode: 'decimal' },
}}
/>
diff --git a/src/components/article/tabs/ArticleAttributesTab.jsx b/src/components/article/tabs/ArticleAttributesTab.jsx
index 57bce34..229bc97 100644
--- a/src/components/article/tabs/ArticleAttributesTab.jsx
+++ b/src/components/article/tabs/ArticleAttributesTab.jsx
@@ -3,10 +3,7 @@ import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import IconButton from '@mui/material/IconButton';
import MenuItem from '@mui/material/MenuItem';
-import Select from '@mui/material/Select';
-import FormControl from '@mui/material/FormControl';
-import InputLabel from '@mui/material/InputLabel';
-import TextField from '@mui/material/TextField';
+import TextInput from '../../TextInput';
import Typography from '@mui/material/Typography';
import Chip from '@mui/material/Chip';
import Tooltip from '@mui/material/Tooltip';
@@ -397,51 +394,45 @@ export default class ArticleAttributesTab extends PureComponent {
{/* Unified Add Bar: [Merkmal ▼] [Wert ▼ / Input] [+] */}
-
- Merkmal
-
-
+
+ {merkmalOptions.map((m) => (
+
+ {m.name}
+
+ ))}
+
{isAttr ? (
-
- Wert
-
-
+ ))
+ )}
+
) : (
-
)}
diff --git a/src/components/article/tabs/ArticleLabelTab.jsx b/src/components/article/tabs/ArticleLabelTab.jsx
index 5a3e242..ad5a9cd 100644
--- a/src/components/article/tabs/ArticleLabelTab.jsx
+++ b/src/components/article/tabs/ArticleLabelTab.jsx
@@ -3,7 +3,7 @@ import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import CircularProgress from '@mui/material/CircularProgress';
import Button from '@mui/material/Button';
-import TextField from '@mui/material/TextField';
+import TextInput from '../../TextInput';
import MenuItem from '@mui/material/MenuItem';
import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper';
@@ -210,22 +210,21 @@ export default class ArticleLabelTab extends PureComponent {
- this.setState({ selectedPrinterId: e.target.value })}
sx={{ minWidth: 200 }}
- size="small"
>
{printers.map((p) => (
{p.desc ? `${p.desc} (${p.address})` : p.address}
))}
-
+
-
-
- void this.handleToggleAttributeExclusive(a, e)}
+ aria-pressed={!a.exclusive}
sx={{
height: 22,
fontSize: '0.72rem',
fontWeight: 600,
cursor: 'pointer',
flexShrink: 0,
+ ...(a.exclusive
+ ? { bgcolor: 'grey.300', color: 'text.secondary' }
+ : null),
}}
/>
-
void this.handleDeleteAttribute(a, e)}
sx={{ flexShrink: 0 }}
>
@@ -761,7 +741,6 @@ class ArtikelMerkmalePage extends PureComponent {
size="small"
label=""
value={newAttrName}
- disabled={busy}
onChange={(next) => this.setState({ newAttrName: next })}
onKeyDown={(event) => {
if (event.key === 'Enter') {
@@ -774,29 +753,12 @@ class ArtikelMerkmalePage extends PureComponent {
- this.setState({ newAttrExclusive: e.target.checked })}
- sx={{ p: 0.5 }}
- />
- }
- label={
-
- Einzelauswahl (max. 1 Wert pro Artikel)
-
- }
- sx={{ ml: 0.5, mt: -0.5 }}
- />
@@ -807,15 +769,8 @@ class ArtikelMerkmalePage extends PureComponent {
- {selectedAttr ? `Werte für „${selectedAttr.name}“` : 'Werte'}
+ {selectedAttr ? `Werte für Auswahl-Merkmal „${selectedAttr.name}“` : 'Werte'}
- {selectedAttr ? (
-
- ) : null}
{!selectedAttr ? (
@@ -825,12 +780,7 @@ class ArtikelMerkmalePage extends PureComponent {
) : (
<>
- {selectedValues.length === 0 ? (
-
- Noch keine Werte für „{selectedAttr.name}“ hinterlegt.
-
- ) : (
- selectedValues.map((v) => (
+ {selectedValues.map((v) => (
this.setState((prev) => ({
valDrafts: { ...prev.valDrafts, [v.id]: next },
@@ -881,14 +830,12 @@ class ArtikelMerkmalePage extends PureComponent {
edge="end"
size="small"
aria-label={`„${v.value}" löschen`}
- disabled={busy}
onClick={() => void this.handleDeleteValue(v)}
>
- ))
- )}
+ ))}
@@ -896,7 +843,6 @@ class ArtikelMerkmalePage extends PureComponent {
size="small"
label=""
value={newValText}
- disabled={busy}
onChange={(next) => this.setState({ newValText: next })}
onKeyDown={(event) => {
if (event.key === 'Enter') {
@@ -909,7 +855,7 @@ class ArtikelMerkmalePage extends PureComponent {
@@ -957,7 +902,6 @@ class ArtikelMerkmalePage extends PureComponent {
size="small"
label=""
value={propDrafts[p.id] ?? ''}
- disabled={busy}
onChange={(next) =>
this.setState((prev) => ({
propDrafts: { ...prev.propDrafts, [p.id]: next },
@@ -979,33 +923,28 @@ class ArtikelMerkmalePage extends PureComponent {
}}
sx={{ flex: 1 }}
/>
-
- void this.handleTogglePropertyExclusive(p, e)}
+ aria-pressed={!p.exclusive}
sx={{
height: 22,
fontSize: '0.72rem',
fontWeight: 600,
cursor: 'pointer',
flexShrink: 0,
+ ...(p.exclusive
+ ? { bgcolor: 'grey.300', color: 'text.secondary' }
+ : null),
}}
/>
-
void this.handleDeleteProperty(p)}
>
@@ -1020,7 +959,6 @@ class ArtikelMerkmalePage extends PureComponent {
size="small"
label=""
value={newPropName}
- disabled={busy}
onChange={(next) => this.setState({ newPropName: next })}
onKeyDown={(event) => {
if (event.key === 'Enter') {
@@ -1033,29 +971,12 @@ class ArtikelMerkmalePage extends PureComponent {
- this.setState({ newPropExclusive: e.target.checked })}
- sx={{ p: 0.5 }}
- />
- }
- label={
-
- Einzelauswahl (max. 1 Wert pro Artikel)
-
- }
- sx={{ ml: 0.5, mt: -0.5 }}
- />