This commit is contained in:
seb
2026-10-18 19:32:31 +02:00
parent 199bf869a6
commit 587170ce9b
9 changed files with 184 additions and 29 deletions

View File

@@ -579,14 +579,28 @@ class KundeEditPage extends PureComponent {
onChange={(next) => this.setState({ firstName: next })}
/>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
variant="contained"
size="small"
disabled={savingEntity || !name.trim()}
onClick={() => void this.handleSaveEntity()}
>
{savingEntity ? 'Speichern…' : 'Speichern'}
</Button>
{(() => {
const canSaveEntity =
Boolean(name.trim()) &&
((name.trim() !== (entity?.name ?? '').trim()) ||
(firstName.trim() !== (entity?.firstName ?? '').trim()));
return (
<Button
variant="contained"
size="small"
color={canSaveEntity ? 'success' : 'inherit'}
disabled={savingEntity || !canSaveEntity}
onClick={() => void this.handleSaveEntity()}
sx={
canSaveEntity
? { bgcolor: 'success.main', color: '#fff', '&:hover': { bgcolor: 'success.dark' } }
: undefined
}
>
{savingEntity ? 'Speichern…' : 'Speichern'}
</Button>
);
})()}
</Box>
</Stack>
</PanelTile>
@@ -600,6 +614,10 @@ class KundeEditPage extends PureComponent {
) : (
addresses.map((address) => {
const draft = addressDrafts[address.id] ?? addressDraftFrom(address);
const orig = addressDraftFrom(address);
const isAddressDirty = Object.keys(orig).some(
(k) => String(draft[k] ?? '').trim() !== String(orig[k] ?? '').trim(),
);
const busy = busyAddressKey === address.id;
return (
<Box key={address.id}>
@@ -626,9 +644,15 @@ class KundeEditPage extends PureComponent {
</IconButton>
<Button
size="small"
variant="outlined"
disabled={addressBusy}
variant={isAddressDirty ? 'contained' : 'outlined'}
color={isAddressDirty ? 'success' : 'inherit'}
disabled={addressBusy || !isAddressDirty}
onClick={() => void this.handleSaveAddress(address)}
sx={
isAddressDirty
? { bgcolor: 'success.main', color: '#fff', '&:hover': { bgcolor: 'success.dark' } }
: undefined
}
>
{busy ? '…' : 'Speichern'}
</Button>
@@ -674,6 +698,9 @@ class KundeEditPage extends PureComponent {
contactType: contact.contactType,
contact: contact.contact,
};
const isContactDirty =
String(draft.contact ?? '').trim() !== String(contact.contact ?? '').trim() ||
String(draft.contactType ?? '').trim() !== String(contact.contactType ?? '').trim();
const busy = busyContactKey === contact.id;
return (
<Stack
@@ -713,9 +740,15 @@ class KundeEditPage extends PureComponent {
</IconButton>
<Button
size="small"
variant="outlined"
disabled={contactBusy}
variant={isContactDirty ? 'contained' : 'outlined'}
color={isContactDirty ? 'success' : 'inherit'}
disabled={contactBusy || !isContactDirty}
onClick={() => void this.handleSaveContact(contact)}
sx={
isContactDirty
? { bgcolor: 'success.main', color: '#fff', '&:hover': { bgcolor: 'success.dark' } }
: undefined
}
>
{busy ? '…' : 'Speichern'}
</Button>

View File

@@ -178,8 +178,14 @@ class KundeNeuPage extends PureComponent {
<Button
variant="contained"
size="small"
disabled={!canSave}
color={canSave ? 'success' : 'inherit'}
disabled={!canSave || saving}
onClick={() => void this.handleSave()}
sx={
canSave
? { bgcolor: 'success.main', color: '#fff', '&:hover': { bgcolor: 'success.dark' } }
: undefined
}
>
{saving ? 'Speichern…' : 'Speichern'}
</Button>

View File

@@ -214,6 +214,7 @@ class LieferungNeuPage extends PureComponent {
this.unsubscribe = this.automat.subscribe(this);
this.setState({ ...this.automat.state });
this._initialLines = null;
this._initialDoc = null;
if (this.props.docId != null) {
void this.loadEdit();
} else {
@@ -296,11 +297,21 @@ class LieferungNeuPage extends PureComponent {
if (doc.externalNumber != null) this.automat.actions.setExternalNumber(String(doc.externalNumber));
this.automat.actions.setLineitems(lines);
this._initialLines = lines;
this._initialDoc = {
date: dateInputValue(doc.date),
partyId: doc[cfg.partyField] ?? null,
externalNumber: doc.externalNumber != null ? String(doc.externalNumber) : '',
lines,
attachments: [],
};
// Restore attachments with thumbnails
const apiFiles = doc.files ?? [];
const attachments = await restoreAttachmentsFromApi(apiFiles, this.state.attachments);
this.automat.actions.setAttachments(attachments);
if (this._initialDoc) {
this._initialDoc.attachments = attachments;
}
let ledgerSummary = null;
try {
@@ -1186,6 +1197,14 @@ class LieferungNeuPage extends PureComponent {
const attachments = await restoreAttachmentsFromApi(apiFiles, this.state.attachments);
this.automat.actions.setAttachments(attachments);
this._initialDoc = {
date: dateInputValue(doc.date),
partyId: doc[cfg.partyField] ?? null,
externalNumber: doc.externalNumber != null ? String(doc.externalNumber) : '',
lines,
attachments,
};
enqueueSnackbar(cfg.savedMsg, { variant: 'success' });
this.setState({ saving: false });
}
@@ -1259,6 +1278,27 @@ class LieferungNeuPage extends PureComponent {
}
}
const isEdit = this.props.docId != null;
let isDirty = false;
if (isEdit && this._initialDoc) {
const dateChanged = String(date ?? '').trim() !== String(this._initialDoc.date ?? '').trim();
const partyChanged = (partyId ?? null) !== (this._initialDoc.partyId ?? null);
const extChanged = String(externalNumber ?? '').trim() !== String(this._initialDoc.externalNumber ?? '').trim();
const linesChanged = !areLinesEqual(lines, this._initialDoc.lines);
const currentAtts = attachments || [];
const baseAtts = this._initialDoc.attachments || [];
let attsChanged = currentAtts.length !== baseAtts.length;
if (!attsChanged) {
for (const a of currentAtts) {
if (!a.isPersisted || a.isDirty) {
attsChanged = true;
break;
}
}
}
isDirty = dateChanged || partyChanged || extChanged || linesChanged || attsChanged;
}
return (
<>
<PanelTileGrid>
@@ -1279,6 +1319,7 @@ class LieferungNeuPage extends PureComponent {
onPdfCreate={this.handleCreatePdf}
attachments={attachments}
lines={lines}
isDirty={isDirty}
onPdfUpload={this.handlePdfUpload}
onPdfRescan={this.handlePdfRescan}
onPdfDelete={this.handlePdfDelete}