This commit is contained in:
seb
2026-10-08 01:44:38 +02:00
parent cba59532ef
commit 8bc912c567

View File

@@ -1,340 +0,0 @@
import { Component } from 'react';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import AppDialog from './AppDialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import Divider from '@mui/material/Divider';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
import ListItemText from '@mui/material/ListItemText';
import Stack from '@mui/material/Stack';
import Switch from '@mui/material/Switch';
import TextField from '@mui/material/TextField';
import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
import PointOfSaleIcon from '@mui/icons-material/PointOfSale';
import { enqueueSnackbar } from 'notistack';
import BackendError from './BackendError';
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
import companyAutomat from '../automats/companyAutomat';
import posterminalAutomat from '../automats/posterminalAutomat';
import { whenCategoryTreeReady } from '../automats/categoryAutomat';
import { ISO639_OPTIONS, parseTaxRate } from '../utils/companyForm';
import { backendErrorFrom, isBackendFailure } from '../utils/backendError';
import { notifyError } from '../utils/notify';
export default class CompanySettingsDialog extends Component {
constructor(props) {
super(props);
this.state = {
busy: false,
error: null,
name: '',
iso639: 'de',
taxrate: '',
reducedtaxrate: '',
newTerminalName: '',
creatingTerminal: false,
enableAllForNew: true,
terminals: [],
};
this.handleSubmit = this.handleSubmit.bind(this);
this.syncFormFromCompany = this.syncFormFromCompany.bind(this);
this.handleCreateTerminal = this.handleCreateTerminal.bind(this);
this.handleDeleteTerminal = this.handleDeleteTerminal.bind(this);
this.loadCompany = this.loadCompany.bind(this);
this.loadTerminals = this.loadTerminals.bind(this);
}
componentDidMount() {
if (this.props.open) {
this.setState({ error: null });
void this.loadCompany();
void this.loadTerminals();
}
}
componentDidUpdate(prevProps) {
const { open } = this.props;
if (open && !prevProps.open) {
this.setState({ error: null });
void this.loadCompany();
void this.loadTerminals();
}
}
syncFormFromCompany(companyRow) {
this.setState({
name: companyRow.name,
iso639: companyRow.iso639,
taxrate: companyRow.taxrate != null ? String(companyRow.taxrate) : '',
reducedtaxrate: companyRow.reducedtaxrate != null ? String(companyRow.reducedtaxrate) : '',
});
}
async loadCompany() {
try {
const companyRow = await companyAutomat.actions.load();
this.syncFormFromCompany(companyRow);
} catch (err) {
notifyError(err);
}
}
async loadTerminals() {
try {
const terminals = await posterminalAutomat.actions.load();
this.setState({ terminals: Array.isArray(terminals) ? terminals : [] });
} catch (err) {
notifyError(err);
}
}
async handleCreateTerminal() {
const { newTerminalName, enableAllForNew } = this.state;
const trimmed = newTerminalName.trim();
this.setState({ creatingTerminal: true });
try {
await posterminalAutomat.actions.create({ name: trimmed });
if (enableAllForNew) {
const created = posterminalAutomat.state.terminals.at(-1);
if (created?.id != null) {
await posterminalAutomat.actions.enableAll({ terminalId: created.id });
await whenCategoryTreeReady();
}
}
const terminals = await posterminalAutomat.actions.load();
this.setState({ newTerminalName: '', terminals: Array.isArray(terminals) ? terminals : [] });
} catch (err) {
notifyError(err);
} finally {
this.setState({ creatingTerminal: false });
}
}
async handleDeleteTerminal(terminal) {
try {
await posterminalAutomat.actions.delete(terminal.id);
const terminals = await posterminalAutomat.actions.load();
this.setState({ terminals: Array.isArray(terminals) ? terminals : [] });
} catch (err) {
notifyError(err);
}
}
async handleSubmit(event) {
event.preventDefault();
const { onClose } = this.props;
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' });
onClose();
} catch (err) {
this.setState({ error: backendErrorFrom(err) });
} finally {
this.setState({ busy: false });
}
}
render() {
const { open, onClose } = this.props;
const { busy, error, 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;
return (
<AppDialog open={open} onClose={busy ? undefined : onClose} maxWidth="sm" fullWidth>
<DialogTitle>Unternehmen bearbeiten</DialogTitle>
<Box component="form" onSubmit={this.handleSubmit} noValidate>
<DialogContent>
{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}>
<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={1}>
<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>
<Divider sx={{ my: 1 }} />
<Box>
<List dense sx={{ py: 0 }}>
{(this.state.terminals || []).map((terminal) => (
<ListItem
key={terminal.id}
sx={{ px: 0, py: 0.5 }}
>
<PointOfSaleIcon sx={{ mr: 1, color: 'text.secondary', fontSize: 20 }} />
<ListItemText
primary={terminal.name}
secondary={`PIN: ${terminal.pairingpin}`}
/>
<ListItemSecondaryAction>
<IconButton
edge="end"
size="small"
onClick={() => this.handleDeleteTerminal(terminal)}
disabled={busy || this.state.creatingTerminal}
>
<DeleteOutlinedIcon fontSize="small" />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
<Box sx={{ mt: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
<TextField
size="small"
placeholder="Neues Terminal..."
value={this.state.newTerminalName}
onChange={(e) => this.setState({ newTerminalName: e.target.value })}
disabled={busy || this.state.creatingTerminal}
autoComplete="off"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.handleCreateTerminal();
}
}}
sx={{ flex: 1 }}
/>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<PointOfSaleIcon sx={{ fontSize: 18, color: 'text.secondary' }} />
<Switch
size="small"
checked={this.state.enableAllForNew}
onChange={(e) => this.setState({ enableAllForNew: e.target.checked })}
disabled={busy || this.state.creatingTerminal}
/>
</Box>
<Button
variant="contained"
size="small"
onClick={this.handleCreateTerminal}
disabled={busy || this.state.creatingTerminal || !this.state.newTerminalName.trim()}
>
Hinzufügen
</Button>
</Box>
</Box>
</Stack>
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>
<Button onClick={onClose} disabled={busy}>
Abbrechen
</Button>
<Button type="submit" variant="contained" disabled={busy || !canSave}>
Speichern
</Button>
</DialogActions>
</Box>
</AppDialog>
);
}
}