From 874d6c010f2621f238e90b22f7df4ad759f47f53 Mon Sep 17 00:00:00 2001 From: seb Date: Sun, 4 Oct 2026 11:25:10 +0200 Subject: [PATCH] u --- src/components/TextInput.jsx | 139 +++++++++++++ .../article/ArticleImageSearchDialog.jsx | 6 +- .../article/ArticlePriceTiersDialog.jsx | 9 +- .../article/ArticleStockAdjustDialog.jsx | 11 +- .../article/ArticleSupplierDialog.jsx | 20 +- .../article/ArticleUnitEditorDialog.jsx | 22 +-- .../article/boxes/ArticleViewBrand.jsx | 31 +-- .../article/boxes/ArticleViewEANs.jsx | 12 +- .../article/boxes/ArticleViewLists.jsx | 186 ++++++++---------- src/components/article/boxes/PriceUnitRow.jsx | 6 +- .../article/tabs/ArticleAttributesTab.jsx | 83 ++++---- .../article/tabs/ArticleLabelTab.jsx | 12 +- .../brand/BrandImageSearchDialog.jsx | 6 +- src/components/tiles/ArticleTile.jsx | 7 +- src/nav.js | 2 +- src/routes/artikel/merkmale.jsx | 139 +++---------- 16 files changed, 345 insertions(+), 346 deletions(-) create mode 100644 src/components/TextInput.jsx 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 ( + + {a.name} + {isAssigned ? ' (bereits zugewiesen)' : ''} + + ); + })} + )} @@ -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} ))} - + - - 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 { - this.setState({ newPropExclusive: e.target.checked })} - sx={{ p: 0.5 }} - /> - } - label={ - - Einzelauswahl (max. 1 Wert pro Artikel) - - } - sx={{ ml: 0.5, mt: -0.5 }} - />