This commit is contained in:
seb
2026-10-16 13:10:58 +02:00
parent 7bc1da4de6
commit d851c6e1e9
10 changed files with 326 additions and 445 deletions

View File

@@ -40,11 +40,14 @@ class ManifestFormLine extends PureComponent {
cachedArticle: sku ? getArticle(sku) : null,
loadingArticle: false,
fallbackSuppliers: [],
editingField: null,
};
this.handleRemove = this.handleRemove.bind(this);
this.handleMoveUp = this.handleMoveUp.bind(this);
this.handleMoveDown = this.handleMoveDown.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleStartEdit = this.handleStartEdit.bind(this);
this.handleCreateArticle = this.handleCreateArticle.bind(this);
this.handleSearchArticle = this.handleSearchArticle.bind(this);
this.handleRemoveSupplierArticle = this.handleRemoveSupplierArticle.bind(this);
@@ -134,7 +137,25 @@ class ManifestFormLine extends PureComponent {
this.props.onMove?.(this.props.line.key, 1);
}
handleStartEdit(columnId) {
const { saving } = this.props;
const isDisabled = saving || (columnId === 'sku' && Boolean(this.props.isOnShipment));
if (isDisabled) return;
if (this.state.editingField !== columnId) {
this.setState({ editingField: columnId });
}
}
handleKeyDown(e) {
if (e.key === 'Enter' || e.key === 'Escape') {
e.target.blur();
}
}
handleBlur(e, columnId) {
if (this.state.editingField === columnId) {
this.setState({ editingField: null });
}
if (columnId === 'sku' && this.props.onSkuBlur) {
this.props.onSkuBlur(this.props.line.key, e.target.value);
}
@@ -229,6 +250,8 @@ class ManifestFormLine extends PureComponent {
renderField(column, colIdx, totalCols) {
const { id, align, type } = column;
const { line, saving, onChange, columns } = this.props;
const isFirstLine = Boolean(this.props.isFirstLine ?? (this.props.index === 0));
const isLastLine = Boolean(this.props.isLastLine);
if (id === 'pos') {
const posVal =
@@ -240,8 +263,10 @@ class ManifestFormLine extends PureComponent {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
minHeight: 38,
height: isFirstLine ? 40 : 41,
minHeight: isFirstLine ? 40 : 41,
maxHeight: isFirstLine ? 40 : 41,
mt: isFirstLine ? 0 : '-1px',
fontWeight: 700,
fontSize: '0.82rem',
color: 'text.secondary',
@@ -297,65 +322,197 @@ class ManifestFormLine extends PureComponent {
const isFirstInput = hasPosCol ? colIdx === 1 : colIdx === 0;
const isLast = colIdx === totalCols - 1;
const topLeftRadius = isFirstInput && isFirstLine ? 4 : 0;
const topRightRadius = isLast && isFirstLine ? 4 : 0;
const bottomRightRadius = isLast && isLastLine ? 4 : 0;
const bottomLeftRadius = isFirstInput && isLastLine ? 4 : 0;
const borderRadius = `${topLeftRadius}px ${topRightRadius}px ${bottomRightRadius}px ${bottomLeftRadius}px`;
const { dirtyFields } = this.props;
const isFieldDirty = Boolean(
dirtyFields && (dirtyFields.__all__ || dirtyFields[id])
);
return (
<TextField
size="small"
fullWidth
type={type}
value={val ?? ''}
error={isError}
disabled={saving || (id === 'sku' && Boolean(this.props.isOnShipment))}
title={id === 'sku' && this.props.isOnShipment ? 'Artikel ist bereits auf einem Lieferschein und kann nicht geändert werden' : undefined}
autoComplete="off"
onChange={(e) => onChange(line.key, id, e.target.value)}
onBlur={(e) => this.handleBlur(e, id)}
slotProps={{
htmlInput: {
inputMode: type === 'number' ? 'decimal' : 'text',
style: { textAlign: align },
autoComplete: 'off',
step: type === 'number' ? 'any' : undefined,
},
}}
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: isFirstInput ? '4px 0 0 4px' : isLast ? '0 4px 4px 0' : 0,
const isEditing = this.state.editingField === id;
const isDisabled = saving || (id === 'sku' && Boolean(this.props.isOnShipment));
const titleText = id === 'sku' && this.props.isOnShipment
? 'Artikel ist bereits auf einem Lieferschein und kann nicht geändert werden'
: undefined;
if (isEditing) {
return (
<TextField
autoFocus
size="small"
type={type}
value={val ?? ''}
error={isError}
disabled={isDisabled}
title={titleText}
autoComplete="off"
onChange={(e) => onChange(line.key, id, e.target.value)}
onBlur={(e) => this.handleBlur(e, id)}
onKeyDown={this.handleKeyDown}
slotProps={{
htmlInput: {
inputMode: type === 'number' ? 'decimal' : 'text',
style: { textAlign: align },
autoComplete: 'off',
step: type === 'number' ? 'any' : undefined,
},
}}
sx={{
position: 'relative',
ml: isFirstInput ? 0 : '-1px',
...(isFieldDirty && {
bgcolor: 'rgba(237, 108, 2, 0.08)',
}),
'&:hover': {
zIndex: 1,
width: isFirstInput ? '100%' : 'calc(100% + 1px)',
mt: isFirstLine ? 0 : '-1px',
height: isFirstLine ? 40 : 41,
zIndex: 3,
'& .MuiOutlinedInput-root': {
height: isFirstLine ? 40 : 41,
borderRadius,
width: '100%',
...(isFieldDirty && {
bgcolor: 'rgba(237, 108, 2, 0.08)',
}),
'&:hover': {
zIndex: 4,
},
'&.Mui-focused': {
zIndex: 5,
},
},
'&.Mui-focused': {
zIndex: 2,
},
},
...(isFieldDirty && {
zIndex: 1,
'& .MuiOutlinedInput-notchedOutline': {
borderColor: 'warning.main',
borderWidth: 2,
borderWidth: '1px !important',
borderColor: isFieldDirty
? 'warning.main'
: isError
? 'error.main'
: 'rgba(0, 0, 0, 0.23)',
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderWidth: '1px !important',
borderColor: isFieldDirty
? 'warning.main'
: isError
? 'error.main'
: 'primary.main',
},
'&:hover .MuiOutlinedInput-notchedOutline': {
borderColor: 'warning.main',
borderWidth: '1px !important',
borderColor: isFieldDirty
? 'warning.main'
: isError
? 'error.main'
: 'rgba(0, 0, 0, 0.87)',
},
'& input[type=number]': {
MozAppearance: 'textfield',
},
'& input[type=number]::-webkit-outer-spin-button, & input[type=number]::-webkit-inner-spin-button':
{
WebkitAppearance: 'none',
margin: 0,
},
}}
/>
);
}
const hasValue = val != null && String(val).trim() !== '';
return (
<Box
tabIndex={isDisabled ? -1 : 0}
role={isDisabled ? undefined : 'button'}
onClick={() => this.handleStartEdit(id)}
onFocus={() => this.handleStartEdit(id)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.handleStartEdit(id);
}
}}
title={titleText}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: align === 'right' ? 'flex-end' : align === 'center' ? 'center' : 'flex-start',
height: isFirstLine ? 40 : 41,
minHeight: isFirstLine ? 40 : 41,
maxHeight: isFirstLine ? 40 : 41,
boxSizing: 'border-box',
position: 'relative',
ml: isFirstInput ? 0 : '-1px',
width: isFirstInput ? '100%' : 'calc(100% + 1px)',
mt: isFirstLine ? 0 : '-1px',
px: '14px',
py: 0,
borderRadius,
border: '1px solid',
borderColor: isFieldDirty
? 'warning.main'
: isError
? 'error.main'
: 'rgba(0, 0, 0, 0.23)',
cursor: isDisabled ? 'default' : 'text',
touchAction: 'manipulation',
userSelect: 'none',
zIndex: 1,
...(isFieldDirty && {
bgcolor: 'rgba(237, 108, 2, 0.08)',
zIndex: 2,
}),
'& input[type=number]': {
MozAppearance: 'textfield',
},
'& input[type=number]::-webkit-outer-spin-button, & input[type=number]::-webkit-inner-spin-button':
{
WebkitAppearance: 'none',
margin: 0,
...(isError && !isFieldDirty && {
bgcolor: 'rgba(211, 47, 47, 0.04)',
borderColor: 'error.main',
zIndex: 2,
}),
'&:hover': isDisabled
? undefined
: {
bgcolor: isFieldDirty
? 'rgba(237, 108, 2, 0.14)'
: isError
? 'rgba(211, 47, 47, 0.08)'
: 'action.hover',
borderColor: isFieldDirty
? 'warning.main'
: isError
? 'error.main'
: 'rgba(0, 0, 0, 0.87)',
zIndex: 3,
},
'&:focus-visible': {
outline: '2px solid',
outlineColor: 'primary.main',
outlineOffset: -2,
zIndex: 4,
},
}}
/>
>
<Typography
variant="body2"
sx={{
textAlign: align,
width: '100%',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
fontSize: '0.875rem',
lineHeight: '20px',
fontWeight: id === 'sku' ? 600 : 400,
color: isError
? 'error.main'
: hasValue
? 'text.primary'
: 'text.disabled',
fontStyle: 'normal',
}}
>
{hasValue ? String(val) : '—'}
</Typography>
</Box>
);
}
@@ -979,7 +1136,11 @@ class ManifestFormLine extends PureComponent {
sx={{
p: 0,
width: col.width,
verticalAlign: 'middle',
minWidth: col.width,
height: 40,
verticalAlign: 'top',
position: 'relative',
overflow: 'visible',
}}
>
{this.renderField(col, colIdx, columns.length)}