Add Kundennummer/Nummernkreise settings and settings dialog.
Add number & setting API endpoints, StorageManager caches, and a Settings dialog (tabs for settings keys and number ranges) reachable from the user menu. Show Kundennummer and creation date in the Kunde list, and refactor Neuer Kunde to show an auto-generated number with group select.
This commit is contained in:
@@ -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 {
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Kundennummer</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Vorname</TableCell>
|
||||
<TableCell>Gruppe</TableCell>
|
||||
<TableCell>Erstellt am</TableCell>
|
||||
<TableCell padding="checkbox" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
@@ -161,9 +168,11 @@ class KundeListePage extends PureComponent {
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => this.handleEdit(row)}
|
||||
>
|
||||
<TableCell>{row.number}</TableCell>
|
||||
<TableCell>{row.name}</TableCell>
|
||||
<TableCell>{row.firstName}</TableCell>
|
||||
<TableCell>{this.groupName(row.customergroupId)}</TableCell>
|
||||
<TableCell>{formatDate(row.createdAt)}</TableCell>
|
||||
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
|
||||
<IconButton
|
||||
size="small"
|
||||
|
||||
@@ -1,84 +1,53 @@
|
||||
import { PureComponent } from 'react';
|
||||
import Autocomplete from '@mui/material/Autocomplete';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Select from '@mui/material/Select';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import BackendError from '../../components/BackendError';
|
||||
import SingleLineTextEdit from '../../components/SingleLineTextEdit';
|
||||
import { PanelTile, PanelTileGrid } from '../../components/panels';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { router } from '../../appRouter';
|
||||
import { requireDefined, requireObject } from '../../utils/apiShape';
|
||||
import { backendErrorFrom } from '../../utils/backendError';
|
||||
import { notifyError } from '../../utils/notify';
|
||||
|
||||
function sortGroups(groups) {
|
||||
return [...groups].sort((a, b) => {
|
||||
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 <BackendError error={error} onRetry={this.handleRetry} />;
|
||||
}
|
||||
|
||||
const selected =
|
||||
groups.find((row) => row.id === customergroupId) ?? null;
|
||||
const canSave =
|
||||
customergroupId != null && name.trim() !== '' && !saving;
|
||||
|
||||
@@ -143,31 +97,32 @@ class KundeNeuPage extends PureComponent {
|
||||
<PanelTileGrid>
|
||||
<PanelTile wide={2} minHeight={220} color="#bfdbfe" title="Neuer Kunde">
|
||||
<Stack spacing={2}>
|
||||
<Autocomplete
|
||||
<TextField
|
||||
size="small"
|
||||
options={groups}
|
||||
value={selected}
|
||||
loading={status === 'loading'}
|
||||
disabled={saving || status === 'loading'}
|
||||
getOptionLabel={(option) => String(option.name ?? '')}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
onChange={this.handleGroupChange}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Kundengruppe"
|
||||
required
|
||||
slotProps={{
|
||||
...params.slotProps,
|
||||
htmlInput: {
|
||||
...params.slotProps?.htmlInput,
|
||||
spellCheck: false,
|
||||
autoComplete: 'off',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
label="Kundennummer"
|
||||
value={number}
|
||||
disabled
|
||||
slotProps={{
|
||||
htmlInput: { spellCheck: false, autoComplete: 'off' },
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
size="small"
|
||||
value={customergroupId ?? ''}
|
||||
disabled={saving}
|
||||
onChange={this.handleGroupChange}
|
||||
displayEmpty
|
||||
inputProps={{
|
||||
spellCheck: false,
|
||||
autoComplete: 'off',
|
||||
}}
|
||||
>
|
||||
{groups.map((row) => (
|
||||
<MenuItem key={row.id} value={row.id}>
|
||||
{row.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<SingleLineTextEdit
|
||||
size="small"
|
||||
label="Name"
|
||||
|
||||
Reference in New Issue
Block a user