265 lines
8.5 KiB
JavaScript
265 lines
8.5 KiB
JavaScript
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 TextInput from '../TextInput';
|
|
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 = {
|
|
isAdmin: current?.role === 'admin',
|
|
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);
|
|
}
|
|
return {
|
|
isAdmin: state.current?.role === 'admin',
|
|
};
|
|
});
|
|
if (companyAutomat.state.current) {
|
|
this.syncFormFromCompany(companyAutomat.state.current);
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.unsub?.();
|
|
}
|
|
|
|
syncFormFromCompany(companyRow) {
|
|
if (!companyRow) return;
|
|
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',
|
|
});
|
|
}
|
|
|
|
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?.();
|
|
if (!this.state.isAdmin) return;
|
|
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,
|
|
});
|
|
this._initial = {
|
|
name: name.trim(),
|
|
iso639,
|
|
taxrate: String(parsedTaxrate),
|
|
reducedtaxrate: String(parsedReducedtaxrate),
|
|
};
|
|
enqueueSnackbar('Unternehmen gespeichert.', { variant: 'success' });
|
|
} catch (err) {
|
|
this.setState({ error: backendErrorFrom(err) });
|
|
} finally {
|
|
this.setState({ busy: false });
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const { isAdmin, 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 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 =
|
|
isAdmin &&
|
|
isDirty &&
|
|
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={!isAdmin || 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={!isAdmin || 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}>
|
|
<TextInput
|
|
fullWidth
|
|
required
|
|
size="small"
|
|
type="number"
|
|
label="Normalsatz"
|
|
value={taxrate}
|
|
disabled={!isAdmin || 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 },
|
|
}}
|
|
/>
|
|
<TextInput
|
|
fullWidth
|
|
required
|
|
size="small"
|
|
type="number"
|
|
label="Ermäßigter Satz"
|
|
value={reducedtaxrate}
|
|
disabled={!isAdmin || 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"
|
|
color={canSave ? 'success' : 'inherit'}
|
|
disabled={!isAdmin || busy || !canSave}
|
|
startIcon={busy ? <CircularProgress size={16} color="inherit" /> : null}
|
|
sx={
|
|
canSave
|
|
? { bgcolor: 'success.main', color: '#fff', '&:hover': { bgcolor: 'success.dark' } }
|
|
: undefined
|
|
}
|
|
>
|
|
Speichern
|
|
</Button>
|
|
</Box>
|
|
</Stack>
|
|
</Box>
|
|
);
|
|
}
|
|
}
|