diff --git a/src/api/ApiClient.js b/src/api/ApiClient.js
index 8ab5273..acb8283 100644
--- a/src/api/ApiClient.js
+++ b/src/api/ApiClient.js
@@ -146,6 +146,8 @@ export class ApiClient {
update: (id, body) => this.#json('PATCH', `/api/entity/${enc(id)}`, { body }),
/** DELETE /api/entity/{id} → 204 */
delete: (id) => this.#empty('DELETE', `/api/entity/${enc(id)}`),
+ /** GET /api/entity/next-number → { number: string } */
+ nextNumber: () => this.#json('GET', '/api/entity/next-number'),
/** GET /api/entity/{entityId}/address → { addresses } */
listAddresses: (entityId) =>
this.#json('GET', `/api/entity/${enc(entityId)}/address`),
@@ -287,6 +289,23 @@ export class ApiClient {
setOffAll: (id) => this.#json('DELETE', `/api/posterminal/${enc(id)}/all`),
};
+ this.number = {
+ /** GET /api/number → { numbers: [{ id, type, prefix, padding, next }] } */
+ list: () => this.#json('GET', '/api/number'),
+ /** PATCH /api/number/{id} — body: { prefix?, padding?, next? } (only present keys patched) */
+ update: (id, body) => this.#json('PATCH', `/api/number/${enc(id)}`, { body }),
+ };
+
+ this.setting = {
+ /** GET /api/company/setting?frontendOnly=1 → { settings: [{ key, value, frontend }] } */
+ list: (query) => this.#json('GET', '/api/company/setting', { query }),
+ /** GET /api/company/setting/{key} → { setting: { key, value, frontend } } */
+ get: (key) => this.#json('GET', `/api/company/setting/${enc(key)}`),
+ /** PUT /api/company/setting/{key} body { value, frontend } → { setting: { key, value, frontend } } */
+ upsert: (key, body) =>
+ this.#json('PUT', `/api/company/setting/${enc(key)}`, { body }),
+ };
+
this.image = {
/** GET /api/image/{fullHash} → AVIF blob */
get: (fullHash) => this.#blob('GET', `/api/image/${enc(fullHash)}`),
diff --git a/src/api/StorageManager.js b/src/api/StorageManager.js
index 7ab71e8..29b9499 100644
--- a/src/api/StorageManager.js
+++ b/src/api/StorageManager.js
@@ -297,6 +297,7 @@ export class StorageManager extends ApiClient {
this.#loadCustomergroups(),
);
const entityCache = createCache('GET /entity', () => this.#loadEntities());
+ const numberCache = createCache('GET /number', () => this.#loadNumbers());
const quoteCache = createCache('GET /quote', () => this.#loadQuotes());
const orderCache = createCache('GET /order', () => this.#loadOrders());
@@ -352,6 +353,11 @@ export class StorageManager extends ApiClient {
/** Settled GET /entity list, or undefined. */
entities: { get: () => entityCache.value },
+ numberPromise: { get: () => numberCache.promise },
+ updateNumber: { value: numberCache.update },
+ /** Settled GET /number list, or undefined. */
+ numbers: { get: () => numberCache.value },
+
quotePromise: { get: () => quoteCache.promise },
updateQuote: { value: quoteCache.update },
/** Settled GET /quote list, or undefined. */
@@ -506,6 +512,12 @@ export class StorageManager extends ApiClient {
return requireArray(data.entities, 'entities');
}
+ async #loadNumbers() {
+ const data = await this.number.list();
+ requireObject(data, 'GET /api/number');
+ return requireArray(data.numbers, 'numbers');
+ }
+
async #loadQuotes() {
const data = await this.quote.list();
requireObject(data, 'GET /api/quote');
diff --git a/src/api/index.js b/src/api/index.js
index 3facb18..69245c1 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -772,6 +772,11 @@ export const entity = {
return http(`/api/entity/${id}`, { method: 'DELETE' });
},
+ /** GET /api/entity/next-number → { number: string } */
+ nextNumber() {
+ return http('/api/entity/next-number');
+ },
+
/** GET /api/entity/:entityId/address → { addresses: EntityAddress[] } */
listAddresses(entityId) {
return http(`/api/entity/${entityId}/address`);
@@ -936,6 +941,25 @@ export const posterminal = {
},
};
+export const number = {
+ /**
+ * GET /api/number → { numbers: [{ id, type, prefix, padding, next }] }
+ * Lists the number sequences for the selected company.
+ */
+ list() {
+ return http('/api/number');
+ },
+
+ /**
+ * PATCH /api/number/:id
+ * Body: at least one of { prefix, padding, next } — only present keys are patched.
+ * → 200 { number: { id, type, prefix, padding, next } }
+ */
+ update(id, body) {
+ return http(`/api/number/${id}`, { method: 'PATCH', body });
+ },
+};
+
export const auth = {
async me() {
try {
@@ -997,6 +1021,7 @@ export default {
customergroup,
delivery,
entity,
+ number,
order,
posterminal,
quote,
diff --git a/src/components/UserMenu.jsx b/src/components/UserMenu.jsx
index 7afac24..96d8821 100644
--- a/src/components/UserMenu.jsx
+++ b/src/components/UserMenu.jsx
@@ -9,9 +9,11 @@ import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import LogoutIcon from '@mui/icons-material/Logout';
import BusinessIcon from '@mui/icons-material/Business';
+import SettingsIcon from '@mui/icons-material/Settings';
import { StorageManagerContext } from '../api/storageManagerContext';
import { logout } from '../store/authSlice';
import CompanySettingsDialog from './CompanySettingsDialog';
+import SettingsDialog from './settings/SettingsDialog';
import { notifyError } from '../utils/notify';
class UserMenu extends Component {
@@ -22,12 +24,15 @@ class UserMenu extends Component {
this.state = {
anchorEl: null,
settingsOpen: false,
+ numbersOpen: false,
user: null,
};
this.handleOpenMenu = this.handleOpenMenu.bind(this);
this.handleCloseMenu = this.handleCloseMenu.bind(this);
this.handleOpenSettings = this.handleOpenSettings.bind(this);
this.handleCloseSettings = this.handleCloseSettings.bind(this);
+ this.handleOpenNumbers = this.handleOpenNumbers.bind(this);
+ this.handleCloseNumbers = this.handleCloseNumbers.bind(this);
this.handleLogout = this.handleLogout.bind(this);
}
@@ -60,6 +65,14 @@ class UserMenu extends Component {
this.setState({ settingsOpen: false });
}
+ handleOpenNumbers() {
+ this.setState({ anchorEl: null, numbersOpen: true });
+ }
+
+ handleCloseNumbers() {
+ this.setState({ numbersOpen: false });
+ }
+
async handleLogout() {
this.setState({ anchorEl: null });
try {
@@ -71,7 +84,7 @@ class UserMenu extends Component {
}
render() {
- const { user, anchorEl, settingsOpen } = this.state;
+ const { user, anchorEl, settingsOpen, numbersOpen } = this.state;
const open = Boolean(anchorEl);
return (
@@ -101,6 +114,12 @@ class UserMenu extends Component {
Unternehmen
+
+
>
);
}
diff --git a/src/components/settings/NumberSettingsPanel.jsx b/src/components/settings/NumberSettingsPanel.jsx
new file mode 100644
index 0000000..271833b
--- /dev/null
+++ b/src/components/settings/NumberSettingsPanel.jsx
@@ -0,0 +1,286 @@
+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 TextField from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
+import { enqueueSnackbar } from 'notistack';
+import BackendError from '../BackendError';
+import { StorageManagerContext } from '../../api/storageManagerContext';
+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 {
+ static contextType = StorageManagerContext;
+
+ 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 (this.context.numbers ?? this.context.numberPromise),
+ '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 this.context.number.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 this.context.updateNumber();
+ 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 ;
+ }
+ if (numbers.length === 0) {
+ return (
+
+ Keine Nummernkreise.
+
+ );
+ }
+
+ 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)}
+ autoComplete="off"
+ />
+
+
+ this.handleChange(row.id, 'padding', e.target.value)}
+ />
+
+
+ this.handleChange(row.id, 'next', e.target.value)}
+ />
+
+
+
+ {draft.saveError ? (
+
+
+ {typeof draft.saveError === 'string'
+ ? draft.saveError
+ : draft.saveError.message}
+
+
+ ) : null}
+ void this.handleSave(row.id)}
+ >
+
+
+
+
+
+ );
+ })}
+
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/components/settings/SettingsDialog.jsx b/src/components/settings/SettingsDialog.jsx
new file mode 100644
index 0000000..e298172
--- /dev/null
+++ b/src/components/settings/SettingsDialog.jsx
@@ -0,0 +1,76 @@
+import { Component } from 'react';
+import Box from '@mui/material/Box';
+import Dialog from '@mui/material/Dialog';
+import DialogContent from '@mui/material/DialogContent';
+import DialogTitle from '@mui/material/DialogTitle';
+import Tab from '@mui/material/Tab';
+import Tabs from '@mui/material/Tabs';
+import NumbersIcon from '@mui/icons-material/Numbers';
+import TuneIcon from '@mui/icons-material/Tune';
+import NumberSettingsPanel from './NumberSettingsPanel';
+import SettingsKeysPanel from './SettingsKeysPanel';
+
+const TAB_KEYS = {
+ keys: 'keys',
+ numbers: 'numbers',
+};
+
+const TABS = [
+ { id: TAB_KEYS.keys, label: 'Einstellungen', icon: , Panel: SettingsKeysPanel },
+ { id: TAB_KEYS.numbers, label: 'Nummernkreise', icon: , Panel: NumberSettingsPanel },
+];
+
+export default class SettingsDialog extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { tab: TAB_KEYS.keys };
+ this.handleTabChange = this.handleTabChange.bind(this);
+ }
+
+ componentDidUpdate(prevProps) {
+ const { open } = this.props;
+ if (!open && prevProps.open) {
+ this.setState({ tab: TAB_KEYS.keys });
+ }
+ }
+
+ handleTabChange(_event, value) {
+ this.setState({ tab: value });
+ }
+
+ render() {
+ const { open, onClose } = this.props;
+ const { tab } = this.state;
+
+ return (
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/components/settings/SettingsKeysPanel.jsx b/src/components/settings/SettingsKeysPanel.jsx
new file mode 100644
index 0000000..7e8be2c
--- /dev/null
+++ b/src/components/settings/SettingsKeysPanel.jsx
@@ -0,0 +1,237 @@
+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 TableRow from '@mui/material/TableRow';
+import TextField from '@mui/material/TextField';
+import Typography from '@mui/material/Typography';
+import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
+import { enqueueSnackbar } from 'notistack';
+import BackendError from '../BackendError';
+import { StorageManagerContext } from '../../api/storageManagerContext';
+import { requireArray, requireDefined, requireObject } from '../../utils/apiShape';
+import { backendErrorFrom } from '../../utils/backendError';
+import { notifyError } from '../../utils/notify';
+
+/**
+ * Settings that may be set via PUT /api/company/setting/{key}.
+ * Only these keys are accepted by the backend.
+ */
+const SETTABLE_KEYS = [
+ 'ui.theme',
+ 'keys.tavily',
+ 'keys.openrouter',
+ 'labelprinter.addr',
+ 'labelprinter.type',
+];
+
+const KEY_LABELS = {
+ 'ui.theme': 'UI-Theme',
+ 'keys.tavily': 'Tavily API-Key',
+ 'keys.openrouter': 'OpenRouter API-Key',
+ 'labelprinter.addr': 'Labeldrucker-Adresse',
+ 'labelprinter.type': 'Labeldrucker-Typ',
+};
+
+function keyLabel(key) {
+ return KEY_LABELS[key] ?? key;
+}
+
+export default class SettingsKeysPanel extends Component {
+ static contextType = StorageManagerContext;
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ status: 'loading',
+ error: null,
+ settings: [],
+ // key → { value, 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 data = await this.context.setting.list();
+ requireObject(data, 'GET /api/company/setting');
+ const settings = requireArray(data.settings, 'settings');
+ const drafts = Object.fromEntries(
+ settings.map((s) => [
+ s.key,
+ {
+ value: s.value ?? '',
+ busy: false,
+ saveError: null,
+ },
+ ]),
+ );
+ this.setState({ status: 'ready', settings, drafts });
+ } catch (err) {
+ notifyError(err);
+ this.setState({ status: 'failed', error: backendErrorFrom(err) });
+ }
+ }
+
+ handleRetry() {
+ void this.load();
+ }
+
+ handleChange(key, field, value) {
+ this.setState((prev) => ({
+ drafts: {
+ ...prev.drafts,
+ [key]: { ...prev.drafts[key], [field]: value, saveError: null },
+ },
+ }));
+ }
+
+ async handleSave(key) {
+ const draft = this.state.drafts[key];
+ if (!draft || draft.busy) return;
+ const row = this.state.settings.find((s) => s.key === key);
+ if (!row) return;
+
+ const body = {};
+ if (draft.value !== (row.value ?? '')) {
+ body.value = draft.value;
+ }
+
+ if (Object.keys(body).length === 0) {
+ return;
+ }
+
+ this.setState((prev) => ({
+ drafts: {
+ ...prev.drafts,
+ [key]: { ...prev.drafts[key], busy: true, saveError: null },
+ },
+ }));
+ try {
+ const data = await this.context.setting.upsert(key, body);
+ requireObject(data, 'PUT /api/company/setting/:key');
+ requireObject(data.setting, 'setting');
+ const updated = requireDefined(data.setting, 'setting');
+
+ const settings = this.state.settings.map((s) =>
+ s.key === key ? { ...s, ...body } : s,
+ );
+ this.setState({
+ settings,
+ drafts: {
+ ...this.state.drafts,
+ [key]: {
+ value: updated.value ?? '',
+ busy: false,
+ saveError: null,
+ },
+ },
+ });
+ enqueueSnackbar('Einstellung gespeichert.', { variant: 'success' });
+ } catch (err) {
+ if (this._unmounted) return;
+ this.setState((prev) => ({
+ drafts: {
+ ...prev.drafts,
+ [key]: { ...prev.drafts[key], busy: false, saveError: backendErrorFrom(err) },
+ },
+ }));
+ }
+ }
+
+ componentWillUnmount() {
+ this._unmounted = true;
+ }
+
+ render() {
+ const { status, error, settings, drafts } = this.state;
+
+ if (status === 'loading') {
+ return (
+
+
+
+ );
+ }
+ if (status === 'failed') {
+ return ;
+ }
+ if (settings.length === 0) {
+ return (
+
+ Keine Einstellungen.
+
+ );
+ }
+
+ return (
+
+
+
+ {SETTABLE_KEYS.map((key) => {
+ const row = settings.find((s) => s.key === key);
+ const draft = row ? drafts[row.key] : null;
+ if (!row || !draft) return null;
+ return (
+
+
+ {keyLabel(row.key)}
+
+
+ this.handleChange(row.key, 'value', e.target.value)}
+ autoComplete="off"
+ />
+
+
+
+ {draft.saveError ? (
+
+
+ {typeof draft.saveError === 'string'
+ ? draft.saveError
+ : draft.saveError.message}
+
+
+ ) : null}
+ void this.handleSave(row.key)}
+ >
+
+
+
+
+
+ );
+ })}
+
+
+
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/components/settings/index.js b/src/components/settings/index.js
new file mode 100644
index 0000000..df96d02
--- /dev/null
+++ b/src/components/settings/index.js
@@ -0,0 +1,3 @@
+export { default as SettingsDialog } from './SettingsDialog';
+export { default as SettingsKeysPanel } from './SettingsKeysPanel';
+export { default as NumberSettingsPanel } from './NumberSettingsPanel';
\ No newline at end of file
diff --git a/src/pages/kunde/index.jsx b/src/pages/kunde/index.jsx
index f50d230..592df04 100644
--- a/src/pages/kunde/index.jsx
+++ b/src/pages/kunde/index.jsx
@@ -29,6 +29,11 @@ function sortEntities(entities) {
});
}
+function formatDate(value) {
+ if (!value) return '';
+ return new Intl.DateTimeFormat('de-DE', { dateStyle: 'medium' }).format(new Date(value));
+}
+
class KundeListePage extends PureComponent {
static contextType = StorageManagerContext;
@@ -147,9 +152,11 @@ class KundeListePage extends PureComponent {
+ Kundennummer
Name
Vorname
Gruppe
+ Erstellt am
@@ -161,9 +168,11 @@ class KundeListePage extends PureComponent {
sx={{ cursor: 'pointer' }}
onClick={() => this.handleEdit(row)}
>
+ {row.number}
{row.name}
{row.firstName}
{this.groupName(row.customergroupId)}
+ {formatDate(row.createdAt)}
e.stopPropagation()}>
{
- const bySort = a.sort - b.sort;
- if (bySort !== 0) return bySort;
- const byName = String(a.name).localeCompare(String(b.name), 'de');
- if (byName !== 0) return byName;
- return a.id - b.id;
- });
-}
-
class KundeNeuPage extends PureComponent {
static contextType = StorageManagerContext;
- constructor(props) {
- super(props);
+ constructor(props, context) {
+ super(props, context);
+ const groups = this.context.customergroups ?? [];
this.state = {
- status: 'loading',
- error: null,
- groups: [],
- customergroupId: null,
+ customergroupId: groups.length > 0 ? groups[0].id : null,
+ number: '',
name: '',
firstName: '',
saving: false,
};
- this._cancelled = false;
- this.handleRetry = this.handleRetry.bind(this);
this.handleGroupChange = this.handleGroupChange.bind(this);
this.handleSave = this.handleSave.bind(this);
}
componentDidMount() {
- this._cancelled = false;
- void this.loadGroups();
+ void this.loadNumber();
}
- componentWillUnmount() {
- this._cancelled = true;
- }
-
- async loadGroups() {
- this.setState({ status: 'loading', error: null });
+ async loadNumber() {
try {
- const list =
- this.context.customergroups ??
- (await this.context.customergroupPromise);
- if (this._cancelled) return;
- this.setState({
- status: 'ready',
- groups: sortGroups(list),
- });
+ const res = await this.context.entity.nextNumber();
+ requireObject(res, 'GET /api/entity/next-number');
+ requireDefined(res.number, 'number');
+ this.setState({ number: res.number });
} catch (err) {
- if (this._cancelled) return;
- this.setState({
- status: 'failed',
- error: backendErrorFrom(err),
- });
+ notifyError(err);
}
}
- handleRetry() {
- void this.loadGroups();
- }
-
- handleGroupChange(_event, option) {
- this.setState({ customergroupId: option?.id ?? null });
+ handleGroupChange(event) {
+ const value = event.target.value;
+ this.setState({ customergroupId: value === '' ? null : Number(value) });
}
async handleSave() {
@@ -109,33 +78,18 @@ class KundeNeuPage extends PureComponent {
requireObject(data.entity, 'entity');
requireDefined(data.entity.id, 'entity.id');
await this.context.updateEntity();
- if (this._cancelled) return;
enqueueSnackbar('Kunde angelegt.', { variant: 'success' });
void router.navigate(`/kunde/${data.entity.id}`);
} catch (err) {
- if (this._cancelled) return;
notifyError(err);
this.setState({ saving: false });
}
}
render() {
- const {
- status,
- error,
- groups,
- customergroupId,
- name,
- firstName,
- saving,
- } = this.state;
+ const { customergroupId, number, name, firstName, saving } = this.state;
+ const groups = this.context.customergroups ?? [];
- if (status === 'failed') {
- return ;
- }
-
- const selected =
- groups.find((row) => row.id === customergroupId) ?? null;
const canSave =
customergroupId != null && name.trim() !== '' && !saving;
@@ -143,31 +97,32 @@ class KundeNeuPage extends PureComponent {
- String(option.name ?? '')}
- isOptionEqualToValue={(option, value) => option.id === value.id}
- onChange={this.handleGroupChange}
- renderInput={(params) => (
-
- )}
+ label="Kundennummer"
+ value={number}
+ disabled
+ slotProps={{
+ htmlInput: { spellCheck: false, autoComplete: 'off' },
+ }}
/>
+