u
This commit is contained in:
@@ -5,12 +5,12 @@ import Tooltip from '@mui/material/Tooltip';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import TextSnippetOutlinedIcon from '@mui/icons-material/TextSnippetOutlined';
|
||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||
import SyncIcon from '@mui/icons-material/Sync';
|
||||
import ChatBubbleOutlineRoundedIcon from '@mui/icons-material/ChatBubbleOutlineRounded';
|
||||
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
|
||||
import { tileBaseSx, TILE_SIZE } from 'dashgui/tiles';
|
||||
import SkeleHashImage from '../settings/SkeleHashImage';
|
||||
import { generateTextThumbnail } from '../../utils/textThumbnail';
|
||||
import { notifyErrorMessage } from '../../utils/notify';
|
||||
import chatAutomat from '../../automats/chatAutomat';
|
||||
import {
|
||||
SCRIM_ACTION,
|
||||
SCRIM_ATTACHMENT,
|
||||
@@ -61,18 +61,40 @@ export class Attachment extends PureComponent {
|
||||
|
||||
handleRescan = (e) => {
|
||||
e?.stopPropagation?.();
|
||||
if (this.props.hasArticles) {
|
||||
notifyErrorMessage(
|
||||
'Neu analysieren nicht möglich, solange Artikel vorhanden sind. Bitte entfernen Sie zuerst alle Zeilen.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { attachment, onRescan } = this.props;
|
||||
if (attachment && onRescan) {
|
||||
onRescan(attachment);
|
||||
}
|
||||
};
|
||||
|
||||
handleMentionInChat = async (e) => {
|
||||
e?.stopPropagation?.();
|
||||
const { attachment, onMentionInChat } = this.props;
|
||||
if (!attachment) return;
|
||||
|
||||
if (onMentionInChat) {
|
||||
onMentionInChat(attachment);
|
||||
return;
|
||||
}
|
||||
|
||||
let textContent = attachment.text || '';
|
||||
if (!textContent && (attachment.file instanceof Blob || attachment.file instanceof File)) {
|
||||
try {
|
||||
textContent = await attachment.file.text();
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Lesen des Texts:', err);
|
||||
}
|
||||
}
|
||||
|
||||
const trimmed = (textContent || '').trim();
|
||||
if (!trimmed) return;
|
||||
|
||||
const title = attachment.name ? `Text aus „${attachment.name}“:` : 'Text:';
|
||||
const quote = `${title}\n\`\`\`\n${trimmed}\n\`\`\``;
|
||||
|
||||
chatAutomat.actions.insertPrompt(quote);
|
||||
};
|
||||
|
||||
handleDelete = (e) => {
|
||||
e?.stopPropagation?.();
|
||||
const { attachment, onDelete } = this.props;
|
||||
@@ -237,51 +259,51 @@ export class Attachment extends PureComponent {
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{kind === 'delivery' && Boolean(onRescan) && (isPdf || isText) ? (
|
||||
<Tooltip
|
||||
title={
|
||||
hasArticles
|
||||
? 'Neu analysieren nicht möglich, solange Artikel vorhanden sind'
|
||||
: 'Neu analysieren'
|
||||
}
|
||||
{kind === 'delivery' && Boolean(onRescan) && isPdf ? (
|
||||
<IconButton
|
||||
size="small"
|
||||
disabled={disabled}
|
||||
onClick={this.handleRescan}
|
||||
aria-label="Zu Text"
|
||||
sx={{
|
||||
bgcolor: SCRIM_ACTION,
|
||||
color: WHITE,
|
||||
p: 0.5,
|
||||
backdropFilter: 'blur(2px)',
|
||||
'&:hover': {
|
||||
bgcolor: SCRIM_HEAVY,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{ display: 'inline-block' }}
|
||||
onClick={
|
||||
hasArticles
|
||||
? (e) => {
|
||||
e.stopPropagation();
|
||||
notifyErrorMessage(
|
||||
'Neu analysieren nicht möglich, solange Artikel vorhanden sind. Bitte entfernen Sie zuerst alle Zeilen.',
|
||||
);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
size="small"
|
||||
disabled={disabled || hasArticles}
|
||||
onClick={this.handleRescan}
|
||||
sx={{
|
||||
bgcolor: SCRIM_ACTION,
|
||||
color: hasArticles ? VEIL_DISABLED : WHITE,
|
||||
p: 0.5,
|
||||
backdropFilter: 'blur(2px)',
|
||||
cursor: hasArticles ? 'not-allowed' : 'pointer',
|
||||
'&:hover': {
|
||||
bgcolor: hasArticles ? SCRIM_ACTION : SCRIM_HEAVY,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SyncIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<TextSnippetOutlinedIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
) : null}
|
||||
|
||||
{isText ? (
|
||||
<IconButton
|
||||
size="small"
|
||||
disabled={disabled}
|
||||
onClick={this.handleMentionInChat}
|
||||
aria-label="Im Chat erwähnen"
|
||||
sx={{
|
||||
bgcolor: SCRIM_ACTION,
|
||||
color: WHITE,
|
||||
p: 0.5,
|
||||
backdropFilter: 'blur(2px)',
|
||||
'&:hover': {
|
||||
bgcolor: SCRIM_HEAVY,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ChatBubbleOutlineRoundedIcon sx={{ fontSize: 16 }} />
|
||||
</IconButton>
|
||||
) : null}
|
||||
|
||||
<IconButton
|
||||
size="small"
|
||||
disabled={disabled}
|
||||
onClick={this.handleDelete}
|
||||
aria-label="Löschen"
|
||||
sx={{
|
||||
bgcolor: SCRIM_ACTION,
|
||||
color: WHITE,
|
||||
|
||||
@@ -6,7 +6,7 @@ import Attachment from './Attachment';
|
||||
|
||||
export class AttachmentList extends PureComponent {
|
||||
render() {
|
||||
const { attachments, onRescan, onDelete, onClick, disabled, hasArticles, kind, docId } = this.props;
|
||||
const { attachments, onRescan, onDelete, onClick, onMentionInChat, disabled, hasArticles, kind, docId } = this.props;
|
||||
if (!Array.isArray(attachments) || attachments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -31,6 +31,7 @@ export class AttachmentList extends PureComponent {
|
||||
onRescan={onRescan}
|
||||
onDelete={onDelete}
|
||||
onClick={onClick}
|
||||
onMentionInChat={onMentionInChat}
|
||||
disabled={disabled}
|
||||
hasArticles={hasArticles}
|
||||
/>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -243,6 +243,7 @@ class ManifestHeader extends PureComponent {
|
||||
onPdfDelete,
|
||||
onPdfClick,
|
||||
onAttachmentClick,
|
||||
onMentionInChat,
|
||||
onDateChange,
|
||||
onNumberChange,
|
||||
onExternalNumberChange,
|
||||
@@ -456,6 +457,7 @@ class ManifestHeader extends PureComponent {
|
||||
onRescan={onPdfRescan}
|
||||
onDelete={onPdfDelete}
|
||||
onClick={onAttachmentClick ?? onPdfClick}
|
||||
onMentionInChat={onMentionInChat}
|
||||
disabled={saving || pdfUploading}
|
||||
hasArticles={hasArticles}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user