Placeholder sub-pages are dropped from routing and nav until delivery/return flows exist; the new-customer group dropdown now matches the API sort order.
204 lines
5.8 KiB
JavaScript
204 lines
5.8 KiB
JavaScript
import { PureComponent } from 'react';
|
|
import Autocomplete from '@mui/material/Autocomplete';
|
|
import Box from '@mui/material/Box';
|
|
import Button from '@mui/material/Button';
|
|
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);
|
|
this.state = {
|
|
status: 'loading',
|
|
error: null,
|
|
groups: [],
|
|
customergroupId: null,
|
|
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();
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this._cancelled = true;
|
|
}
|
|
|
|
async loadGroups() {
|
|
this.setState({ status: 'loading', error: null });
|
|
try {
|
|
const list =
|
|
this.context.customergroups ??
|
|
(await this.context.customergroupPromise);
|
|
if (this._cancelled) return;
|
|
this.setState({
|
|
status: 'ready',
|
|
groups: sortGroups(list),
|
|
});
|
|
} catch (err) {
|
|
if (this._cancelled) return;
|
|
this.setState({
|
|
status: 'failed',
|
|
error: backendErrorFrom(err),
|
|
});
|
|
}
|
|
}
|
|
|
|
handleRetry() {
|
|
void this.loadGroups();
|
|
}
|
|
|
|
handleGroupChange(_event, option) {
|
|
this.setState({ customergroupId: option?.id ?? null });
|
|
}
|
|
|
|
async handleSave() {
|
|
if (this.state.saving) return;
|
|
const { customergroupId, name, firstName } = this.state;
|
|
const trimmedName = name.trim();
|
|
const trimmedFirst = firstName.trim();
|
|
|
|
if (customergroupId == null) {
|
|
notifyError(new Error('Kundengruppe fehlt.'));
|
|
return;
|
|
}
|
|
if (!trimmedName) {
|
|
notifyError(new Error('Name fehlt.'));
|
|
return;
|
|
}
|
|
|
|
const body = {
|
|
customergroupId,
|
|
name: trimmedName,
|
|
};
|
|
if (trimmedFirst) body.firstName = trimmedFirst;
|
|
|
|
this.setState({ saving: true });
|
|
try {
|
|
const data = await this.context.entity.create(body);
|
|
requireObject(data, 'POST /api/entity');
|
|
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;
|
|
|
|
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;
|
|
|
|
return (
|
|
<PanelTileGrid>
|
|
<PanelTile wide={2} minHeight={220} color="#bfdbfe" title="Neuer Kunde">
|
|
<Stack spacing={2}>
|
|
<Autocomplete
|
|
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',
|
|
},
|
|
}}
|
|
/>
|
|
)}
|
|
/>
|
|
<SingleLineTextEdit
|
|
size="small"
|
|
label="Name"
|
|
required
|
|
value={name}
|
|
disabled={saving}
|
|
onChange={(next) => this.setState({ name: next })}
|
|
/>
|
|
<SingleLineTextEdit
|
|
size="small"
|
|
label="Vorname"
|
|
value={firstName}
|
|
disabled={saving}
|
|
onChange={(next) => this.setState({ firstName: next })}
|
|
/>
|
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
|
<Button
|
|
variant="contained"
|
|
size="small"
|
|
disabled={!canSave}
|
|
onClick={() => void this.handleSave()}
|
|
>
|
|
{saving ? 'Speichern…' : 'Speichern'}
|
|
</Button>
|
|
</Box>
|
|
</Stack>
|
|
</PanelTile>
|
|
</PanelTileGrid>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default KundeNeuPage;
|