This commit is contained in:
seb
2026-10-08 01:44:40 +02:00
parent 8bc912c567
commit 243eba5fc6
7 changed files with 718 additions and 27 deletions

View File

@@ -0,0 +1,231 @@
import { Component } from 'react';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import CircularProgress from '@mui/material/CircularProgress';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
import InputAdornment from '@mui/material/InputAdornment';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import { enqueueSnackbar } from 'notistack';
import BackendError from '../BackendError';
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
import companyAutomat from '../../automats/companyAutomat';
import { ISO639_OPTIONS, parseTaxRate } from '../../utils/companyForm';
import { backendErrorFrom, isBackendFailure } from '../../utils/backendError';
import { notifyError } from '../../utils/notify';
export default class CompanySettingsPanel extends Component {
constructor(props) {
super(props);
const current = companyAutomat.state.current;
this.state = {
status: current ? 'ready' : 'loading',
busy: false,
error: null,
name: current?.name ?? '',
iso639: current?.iso639 ?? 'de',
taxrate: current?.taxrate != null ? String(current.taxrate) : '',
reducedtaxrate: current?.reducedtaxrate != null ? String(current.reducedtaxrate) : '',
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleRetry = this.handleRetry.bind(this);
this.loadCompany = this.loadCompany.bind(this);
this.syncFormFromCompany = this.syncFormFromCompany.bind(this);
}
componentDidMount() {
this.unsub = companyAutomat.subscribe(this, (state) => {
if (state.current && this.state.status === 'loading') {
this.syncFormFromCompany(state.current);
}
});
void this.loadCompany();
}
componentWillUnmount() {
this.unsub?.();
}
syncFormFromCompany(companyRow) {
if (!companyRow) return;
this.setState({
name: companyRow.name ?? '',
iso639: companyRow.iso639 ?? 'de',
taxrate: companyRow.taxrate != null ? String(companyRow.taxrate) : '',
reducedtaxrate: companyRow.reducedtaxrate != null ? String(companyRow.reducedtaxrate) : '',
status: 'ready',
});
}
async loadCompany() {
this.setState({ error: null });
try {
const companyRow = await companyAutomat.actions.load();
this.syncFormFromCompany(companyRow);
} catch (err) {
notifyError(err);
this.setState({ status: 'failed', error: backendErrorFrom(err) });
}
}
handleRetry() {
void this.loadCompany();
}
async handleSubmit(event) {
event?.preventDefault?.();
const { name, iso639, taxrate, reducedtaxrate } = this.state;
const parsedTaxrate = parseTaxRate(taxrate);
const parsedReducedtaxrate = parseTaxRate(reducedtaxrate);
const canSave =
name.trim() !== '' &&
ISO639_OPTIONS.includes(iso639) &&
parsedTaxrate != null &&
parsedReducedtaxrate != null;
if (!canSave) {
this.setState({ error: 'Bitte alle Pflichtfelder ausfüllen.' });
return;
}
this.setState({ busy: true, error: null });
try {
await companyAutomat.actions.update({
name: name.trim(),
iso639,
taxrate: parsedTaxrate,
reducedtaxrate: parsedReducedtaxrate,
});
enqueueSnackbar('Unternehmen gespeichert.', { variant: 'success' });
} catch (err) {
this.setState({ error: backendErrorFrom(err) });
} finally {
this.setState({ busy: false });
}
}
render() {
const { status, busy, error, name, iso639, taxrate, reducedtaxrate } = this.state;
if (status === 'loading' && !name) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 6 }}>
<CircularProgress />
</Box>
);
}
if (status === 'failed' && !name) {
return <BackendError error={error} onRetry={this.handleRetry} />;
}
const parsedTaxrate = parseTaxRate(taxrate);
const parsedReducedtaxrate = parseTaxRate(reducedtaxrate);
const canSave =
name.trim() !== '' &&
ISO639_OPTIONS.includes(iso639) &&
parsedTaxrate != null &&
parsedReducedtaxrate != null;
return (
<Box
component="form"
onSubmit={this.handleSubmit}
noValidate
sx={{ maxWidth: 540, mx: 'auto', py: 2 }}
>
{error ? (
isBackendFailure(error) ? (
<BackendError error={error} sx={{ mb: 2 }} />
) : (
<Alert severity="error" sx={{ mb: 2 }}>
{typeof error === 'string' ? error : error.message}
</Alert>
)
) : null}
<Stack spacing={2.5}>
<SingleLineTextEdit
required
size="small"
label="Name"
value={name}
disabled={busy}
onChange={(nextName) => this.setState({ name: nextName })}
autoComplete="off"
autoFocus
/>
<FormControl required>
<FormLabel sx={{ mb: 0.5 }}>Sprache</FormLabel>
<ToggleButtonGroup
exclusive
required
size="small"
value={iso639}
disabled={busy}
onChange={(_event, value) => {
if (value) this.setState({ iso639: value });
}}
>
<ToggleButton value="de">Deutsch</ToggleButton>
<ToggleButton value="en">English</ToggleButton>
</ToggleButtonGroup>
</FormControl>
<Stack direction="row" spacing={2}>
<TextField
fullWidth
required
size="small"
type="number"
label="Normalsatz"
value={taxrate}
disabled={busy}
onChange={(e) => this.setState({ taxrate: e.target.value })}
error={taxrate !== '' && parsedTaxrate == null}
slotProps={{
input: {
endAdornment: <InputAdornment position="end">%</InputAdornment>,
},
htmlInput: { min: 0, max: 100, step: 0.01, required: true },
}}
/>
<TextField
fullWidth
required
size="small"
type="number"
label="Ermäßigter Satz"
value={reducedtaxrate}
disabled={busy}
onChange={(e) => this.setState({ reducedtaxrate: e.target.value })}
error={reducedtaxrate !== '' && parsedReducedtaxrate == null}
slotProps={{
input: {
endAdornment: <InputAdornment position="end">%</InputAdornment>,
},
htmlInput: { min: 0, max: 100, step: 0.01, required: true },
}}
/>
</Stack>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', pt: 1 }}>
<Button
type="submit"
variant="contained"
disabled={busy || !canSave}
startIcon={busy ? <CircularProgress size={16} color="inherit" /> : null}
>
Speichern
</Button>
</Box>
</Stack>
</Box>
);
}
}