import { Component } from 'react';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import TextInput from '../TextInput';
import Typography from '@mui/material/Typography';
import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
import { enqueueSnackbar } from 'notistack';
import BackendError from '../BackendError';
import { number as numberApi } from '../../api';
import companyAutomat from '../../automats/companyAutomat';
import { requireArray, requireDefined, requireObject } from '../../utils/apiShape';
import { backendErrorFrom } from '../../utils/backendError';
import { notifyError } from '../../utils/notify';
function parsePadding(value) {
const n = Number(value);
return Number.isInteger(n) && n > 0 ? n : null;
}
function parseNext(value) {
const n = Number(value);
return Number.isInteger(n) && n >= 0 ? n : null;
}
export default class NumberSettingsPanel extends Component {
constructor(props) {
super(props);
this.state = {
status: 'loading',
error: null,
numbers: [],
// id → { prefix, padding, next, busy, saveError }
drafts: {},
};
this.handleRetry = this.handleRetry.bind(this);
this.handleSave = this.handleSave.bind(this);
}
componentDidMount() {
void this.load();
}
async load() {
this.setState({ status: 'loading', error: null });
try {
const numbers = requireArray(
await companyAutomat.actions.reloadNumbers(),
'numbers',
);
const drafts = Object.fromEntries(
numbers.map((row) => [
row.id,
{
prefix: row.prefix ?? '',
padding: row.padding != null ? String(row.padding) : '',
next: row.next != null ? String(row.next) : '',
busy: false,
saveError: null,
},
]),
);
this.setState({ status: 'ready', numbers, drafts });
} catch (err) {
notifyError(err);
this.setState({ status: 'failed', error: backendErrorFrom(err) });
}
}
handleRetry() {
void this.load();
}
handleChange(id, field, value) {
this.setState((prev) => ({
drafts: {
...prev.drafts,
[id]: { ...prev.drafts[id], [field]: value, saveError: null },
},
}));
}
async handleSave(id) {
const draft = this.state.drafts[id];
if (!draft || draft.busy) return;
const row = this.state.numbers.find((r) => r.id === id);
if (!row) return;
const body = {};
let invalid = false;
let errorMessage = null;
if (draft.prefix !== (row.prefix ?? '')) {
body.prefix = draft.prefix;
}
if (draft.padding !== (row.padding != null ? String(row.padding) : '')) {
const padding = parsePadding(draft.padding);
if (padding == null) {
invalid = true;
errorMessage = 'Stellenzahl muss eine positive ganze Zahl sein.';
} else {
body.padding = padding;
}
}
if (draft.next !== (row.next != null ? String(row.next) : '')) {
const next = parseNext(draft.next);
if (next == null) {
invalid = true;
errorMessage = 'Nächste Nummer muss eine nicht-negative ganze Zahl sein.';
} else {
body.next = next;
}
}
if (Object.keys(body).length === 0) {
return;
}
if (invalid) {
this.setState((prev) => ({
drafts: {
...prev.drafts,
[id]: { ...prev.drafts[id], saveError: errorMessage },
},
}));
return;
}
this.setState((prev) => ({
drafts: {
...prev.drafts,
[id]: { ...prev.drafts[id], busy: true, saveError: null },
},
}));
try {
const data = await numberApi.update(id, body);
requireObject(data, 'PATCH /api/number');
requireObject(data.number, 'number');
const updated = requireDefined(data.number, 'number');
const numbers = this.state.numbers.map((n) => (n.id === id ? { ...n, ...body } : n));
await companyAutomat.actions.reloadNumbers();
this.setState({
numbers,
drafts: {
...this.state.drafts,
[id]: {
prefix: updated.prefix ?? '',
padding: updated.padding != null ? String(updated.padding) : '',
next: updated.next != null ? String(updated.next) : '',
busy: false,
saveError: null,
},
},
});
enqueueSnackbar('Nummernkreis gespeichert.', { variant: 'success' });
} catch (err) {
if (this._unmounted) return;
this.setState((prev) => ({
drafts: {
...prev.drafts,
[id]: { ...prev.drafts[id], busy: false, saveError: backendErrorFrom(err) },
},
}));
}
}
componentWillUnmount() {
this._unmounted = true;
}
render() {
const { status, error, numbers, drafts } = this.state;
if (status === 'loading') {
return (
);
}
if (status === 'failed') {
return ;
}
return (
Typ
Präfix
Stellenzahl
Nächste Nummer
{numbers.map((row) => {
const draft = drafts[row.id];
if (!draft) return null;
return (
{row.type}
this.handleChange(row.id, 'prefix', e.target.value)}
sx={{ input: { fontFamily: 'monospace' } }}
/>
this.handleChange(row.id, 'padding', e.target.value)}
sx={{ input: { fontFamily: 'monospace' } }}
/>
this.handleChange(row.id, 'next', e.target.value)}
sx={{ input: { fontFamily: 'monospace' } }}
/>
{draft.saveError ? (
{typeof draft.saveError === 'string'
? draft.saveError
: draft.saveError.message}
) : null}
void this.handleSave(row.id)}
>
);
})}
);
}
}