Add Kunde CRUD with addresses/contacts, delivery edit, and Auftrag sub-nav.
Wire customergroup/entity APIs, Lieferungen detail editing, and Angebot/Bestellung/Ausliefern/Retoure placeholders.
This commit is contained in:
203
src/pages/kunde/neu.jsx
Normal file
203
src/pages/kunde/neu.jsx
Normal file
@@ -0,0 +1,203 @@
|
||||
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) =>
|
||||
String(a.name).localeCompare(String(b.name), 'de'),
|
||||
);
|
||||
}
|
||||
|
||||
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) => {
|
||||
const { InputProps, inputProps, ...other } = params;
|
||||
return (
|
||||
<TextField
|
||||
{...other}
|
||||
label="Kundengruppe"
|
||||
required
|
||||
autoComplete="off"
|
||||
slotProps={{
|
||||
input: InputProps,
|
||||
htmlInput: {
|
||||
...inputProps,
|
||||
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;
|
||||
Reference in New Issue
Block a user