u
This commit is contained in:
@@ -113,8 +113,9 @@ class CategoryNameDialog extends Component {
|
||||
const { open, mode = 'create' } = this.props;
|
||||
const { mode: formMode, name, bulkText, busy, error } = this.state;
|
||||
const isBulk = formMode === BULK_MODE;
|
||||
const canBulk = mode === 'create';
|
||||
const canSubmitSingle = name.trim() !== '';
|
||||
const canSubmitSingle =
|
||||
name.trim() !== '' &&
|
||||
(mode !== 'edit' || name.trim() !== (this.props.initialName ?? '').trim());
|
||||
const bulkNames = parseBulkNames(bulkText);
|
||||
const canSubmitBulk = bulkNames.length > 0;
|
||||
|
||||
@@ -157,7 +158,13 @@ class CategoryNameDialog extends Component {
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color={canSubmitSingle ? 'success' : 'inherit'}
|
||||
disabled={busy || !canSubmitSingle}
|
||||
sx={
|
||||
canSubmitSingle
|
||||
? { bgcolor: 'success.main', color: '#fff', '&:hover': { bgcolor: 'success.dark' } }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{SUBMIT_LABELS[mode]}
|
||||
</Button>
|
||||
|
||||
@@ -749,13 +749,27 @@ export default class CategoryTree extends PureComponent {
|
||||
>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button
|
||||
onClick={this.handleConfirmRename}
|
||||
variant="contained"
|
||||
disabled={!renameValue.trim() || isSubmitting}
|
||||
>
|
||||
Speichern
|
||||
</Button>
|
||||
{(() => {
|
||||
const canSave =
|
||||
Boolean(renameValue.trim()) &&
|
||||
!isSubmitting &&
|
||||
renameValue.trim() !== (renameTarget?.name ?? '').trim();
|
||||
return (
|
||||
<Button
|
||||
onClick={this.handleConfirmRename}
|
||||
variant="contained"
|
||||
color={canSave ? 'success' : 'inherit'}
|
||||
disabled={!canSave}
|
||||
sx={
|
||||
canSave
|
||||
? { bgcolor: 'success.main', color: '#fff', '&:hover': { bgcolor: 'success.dark' } }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
Speichern
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
</DialogActions>
|
||||
</AppDialog>
|
||||
|
||||
|
||||
@@ -272,6 +272,11 @@ class ManifestHeader extends PureComponent {
|
||||
} = this.props;
|
||||
|
||||
const hasArticles = this.hasArticles();
|
||||
const isEdit = docId != null;
|
||||
const hasAttachments = Array.isArray(attachments) && attachments.length > 0;
|
||||
const canSave = isEdit
|
||||
? Boolean(isDirty)
|
||||
: Boolean(hasArticles || hasAttachments);
|
||||
|
||||
// Delivery: number is server-allocated/read-only; the caller reference
|
||||
// lives in the editable externalNumber field.
|
||||
@@ -447,20 +452,33 @@ class ManifestHeader extends PureComponent {
|
||||
<Stack spacing={1.5} sx={{ pt: 0.5 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color={isDirty ? 'warning' : 'primary'}
|
||||
disabled={saving || orderCreating}
|
||||
color={canSave ? 'success' : 'inherit'}
|
||||
disabled={Boolean(saving || orderCreating || !canSave)}
|
||||
onClick={onSave}
|
||||
sx={{
|
||||
height: 40,
|
||||
fontWeight: 700,
|
||||
fontSize: '0.875rem',
|
||||
whiteSpace: 'nowrap',
|
||||
...(isDirty && {
|
||||
boxShadow: (theme) => `0 0 0 2px ${theme.palette.warning.main}`,
|
||||
...(canSave && {
|
||||
bgcolor: 'success.main',
|
||||
color: '#fff',
|
||||
'&:hover': {
|
||||
bgcolor: 'success.dark',
|
||||
},
|
||||
}),
|
||||
...(!canSave && {
|
||||
bgcolor: 'action.disabledBackground',
|
||||
color: 'text.disabled',
|
||||
boxShadow: 'none',
|
||||
'&.Mui-disabled': {
|
||||
bgcolor: 'action.disabledBackground',
|
||||
color: 'text.disabled',
|
||||
},
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{saving ? 'Speichern…' : isDirty ? 'Speichern *' : 'Speichern'}
|
||||
{saving ? 'Speichern…' : 'Speichern'}
|
||||
</Button>
|
||||
{isDirty && onRestore && (
|
||||
<Button
|
||||
|
||||
@@ -362,9 +362,15 @@ export class TextEditorPanel extends PureComponent {
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
color={isDirty ? 'success' : 'inherit'}
|
||||
startIcon={<SaveOutlinedIcon />}
|
||||
onClick={this.handleSave}
|
||||
disabled={loading || !isDirty}
|
||||
sx={
|
||||
isDirty
|
||||
? { bgcolor: 'success.main', color: '#fff', '&:hover': { bgcolor: 'success.dark' } }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
Speichern
|
||||
</Button>
|
||||
|
||||
@@ -52,11 +52,17 @@ export default class CompanySettingsPanel extends Component {
|
||||
|
||||
syncFormFromCompany(companyRow) {
|
||||
if (!companyRow) return;
|
||||
this.setState({
|
||||
this._initial = {
|
||||
name: companyRow.name ?? '',
|
||||
iso639: companyRow.iso639 ?? 'de',
|
||||
taxrate: companyRow.taxrate != null ? String(companyRow.taxrate) : '',
|
||||
reducedtaxrate: companyRow.reducedtaxrate != null ? String(companyRow.reducedtaxrate) : '',
|
||||
};
|
||||
this.setState({
|
||||
name: this._initial.name,
|
||||
iso639: this._initial.iso639,
|
||||
taxrate: this._initial.taxrate,
|
||||
reducedtaxrate: this._initial.reducedtaxrate,
|
||||
status: 'ready',
|
||||
});
|
||||
}
|
||||
@@ -100,6 +106,12 @@ export default class CompanySettingsPanel extends Component {
|
||||
taxrate: parsedTaxrate,
|
||||
reducedtaxrate: parsedReducedtaxrate,
|
||||
});
|
||||
this._initial = {
|
||||
name: name.trim(),
|
||||
iso639,
|
||||
taxrate: String(parsedTaxrate),
|
||||
reducedtaxrate: String(parsedReducedtaxrate),
|
||||
};
|
||||
enqueueSnackbar('Unternehmen gespeichert.', { variant: 'success' });
|
||||
} catch (err) {
|
||||
this.setState({ error: backendErrorFrom(err) });
|
||||
@@ -125,7 +137,14 @@ export default class CompanySettingsPanel extends Component {
|
||||
|
||||
const parsedTaxrate = parseTaxRate(taxrate);
|
||||
const parsedReducedtaxrate = parseTaxRate(reducedtaxrate);
|
||||
const isDirty =
|
||||
Boolean(this._initial) &&
|
||||
(name.trim() !== this._initial.name.trim() ||
|
||||
iso639 !== this._initial.iso639 ||
|
||||
taxrate.trim() !== this._initial.taxrate.trim() ||
|
||||
reducedtaxrate.trim() !== this._initial.reducedtaxrate.trim());
|
||||
const canSave =
|
||||
isDirty &&
|
||||
name.trim() !== '' &&
|
||||
ISO639_OPTIONS.includes(iso639) &&
|
||||
parsedTaxrate != null &&
|
||||
@@ -218,8 +237,14 @@ export default class CompanySettingsPanel extends Component {
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color={canSave ? 'success' : 'inherit'}
|
||||
disabled={busy || !canSave}
|
||||
startIcon={busy ? <CircularProgress size={16} color="inherit" /> : null}
|
||||
sx={
|
||||
canSave
|
||||
? { bgcolor: 'success.main', color: '#fff', '&:hover': { bgcolor: 'success.dark' } }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
Speichern
|
||||
</Button>
|
||||
|
||||
@@ -292,7 +292,7 @@ export default class VfsTextEditor extends PureComponent {
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
color={isDirty ? 'primary' : 'inherit'}
|
||||
color={isDirty ? 'success' : 'inherit'}
|
||||
startIcon={isSaving ? <CircularProgress size={14} color="inherit" /> : <SaveRoundedIcon />}
|
||||
onClick={onSave}
|
||||
disabled={!isDirty || isSaving}
|
||||
@@ -302,6 +302,11 @@ export default class VfsTextEditor extends PureComponent {
|
||||
py: 0.4,
|
||||
fontSize: '0.78rem',
|
||||
fontWeight: 600,
|
||||
...(isDirty && {
|
||||
bgcolor: 'success.main',
|
||||
color: '#fff',
|
||||
'&:hover': { bgcolor: 'success.dark' },
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{isSaving ? 'Speichern…' : 'Speichern (Strg+S)'}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user