260 lines
11 KiB
TypeScript
260 lines
11 KiB
TypeScript
import { useState } from 'react';
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Paper,
|
|
Button,
|
|
TextField,
|
|
IconButton,
|
|
Autocomplete,
|
|
Tooltip,
|
|
} from '@mui/material';
|
|
import {
|
|
Add as AddIcon,
|
|
RemoveCircleOutline as RemoveIcon,
|
|
} from '@mui/icons-material';
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
|
import { PageHeader, FormLayout } from '../components/templates';
|
|
import { useNotification } from '../contexts/NotificationContext';
|
|
import { bestellungApi } from '../services/bestellung';
|
|
import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage';
|
|
import type { BestellungFormData, BestellpositionFormData, LieferantFormData, Lieferant } from '../types/bestellung.types';
|
|
import type { AusruestungArtikel } from '../types/ausruestungsanfrage.types';
|
|
|
|
const emptyOrderForm: BestellungFormData = { bezeichnung: '', lieferant_id: undefined, besteller_id: '', mitglied_id: undefined, notizen: '', positionen: [] };
|
|
const emptyVendorForm: LieferantFormData = { name: '', kontakt_name: '', email: '', telefon: '', adresse: '', website: '', notizen: '' };
|
|
const emptyPosition: BestellpositionFormData = { bezeichnung: '', menge: 1, einheit: 'Stk' };
|
|
|
|
export default function BestellungNeu() {
|
|
const navigate = useNavigate();
|
|
const queryClient = useQueryClient();
|
|
const { showSuccess, showError } = useNotification();
|
|
|
|
const [orderForm, setOrderForm] = useState<BestellungFormData>({ ...emptyOrderForm });
|
|
const [inlineVendorOpen, setInlineVendorOpen] = useState(false);
|
|
const [inlineVendorForm, setInlineVendorForm] = useState<LieferantFormData>({ ...emptyVendorForm });
|
|
|
|
// ── Queries ──
|
|
const { data: vendors = [] } = useQuery({
|
|
queryKey: ['lieferanten'],
|
|
queryFn: bestellungApi.getVendors,
|
|
});
|
|
|
|
const { data: orderUsers = [] } = useQuery({
|
|
queryKey: ['bestellungen', 'order-users'],
|
|
queryFn: bestellungApi.getOrderUsers,
|
|
});
|
|
|
|
const { data: katalogItems = [] } = useQuery({
|
|
queryKey: ['katalog'],
|
|
queryFn: () => ausruestungsanfrageApi.getItems({ aktiv: true }),
|
|
staleTime: 5 * 60 * 1000,
|
|
});
|
|
|
|
// ── Mutations ──
|
|
const createOrder = useMutation({
|
|
mutationFn: (data: BestellungFormData) => bestellungApi.createOrder(data),
|
|
onSuccess: (created) => {
|
|
queryClient.invalidateQueries({ queryKey: ['bestellungen'] });
|
|
showSuccess('Bestellung erstellt');
|
|
navigate(`/bestellungen/${created.id}`);
|
|
},
|
|
onError: (error: any) => showError(error?.response?.data?.message || 'Fehler beim Erstellen der Bestellung'),
|
|
});
|
|
|
|
const createVendor = useMutation({
|
|
mutationFn: (data: LieferantFormData) => bestellungApi.createVendor(data),
|
|
onSuccess: (newVendor: Lieferant) => {
|
|
queryClient.invalidateQueries({ queryKey: ['lieferanten'] });
|
|
showSuccess('Lieferant erstellt');
|
|
setOrderForm((f) => ({ ...f, lieferant_id: newVendor.id }));
|
|
setInlineVendorOpen(false);
|
|
setInlineVendorForm({ ...emptyVendorForm });
|
|
},
|
|
onError: () => showError('Fehler beim Erstellen des Lieferanten'),
|
|
});
|
|
|
|
const handleSubmit = () => {
|
|
if (!orderForm.bezeichnung.trim()) return;
|
|
createOrder.mutate(orderForm);
|
|
};
|
|
|
|
return (
|
|
<DashboardLayout>
|
|
<PageHeader
|
|
title="Neue Bestellung"
|
|
backTo="/bestellungen"
|
|
/>
|
|
|
|
<FormLayout
|
|
actions={<>
|
|
<Button onClick={() => navigate('/bestellungen')}>Abbrechen</Button>
|
|
<Button
|
|
variant="contained"
|
|
onClick={handleSubmit}
|
|
disabled={!orderForm.bezeichnung.trim() || createOrder.isPending}
|
|
>
|
|
Erstellen
|
|
</Button>
|
|
</>}
|
|
>
|
|
<TextField
|
|
label="Bezeichnung"
|
|
required
|
|
InputLabelProps={{ shrink: true }}
|
|
value={orderForm.bezeichnung}
|
|
onChange={(e) => setOrderForm((f) => ({ ...f, bezeichnung: e.target.value }))}
|
|
/>
|
|
|
|
{/* Lieferant + inline create */}
|
|
<Box sx={{ display: 'flex', gap: 1, alignItems: 'flex-start' }}>
|
|
<Autocomplete
|
|
options={vendors}
|
|
getOptionLabel={(o) => o.name}
|
|
value={vendors.find((v) => v.id === orderForm.lieferant_id) || null}
|
|
onChange={(_e, v) => setOrderForm((f) => ({ ...f, lieferant_id: v?.id }))}
|
|
renderInput={(params) => <TextField {...params} label="Lieferant" />}
|
|
sx={{ flexGrow: 1 }}
|
|
/>
|
|
<Tooltip title="Neuen Lieferant anlegen">
|
|
<IconButton
|
|
onClick={() => setInlineVendorOpen(!inlineVendorOpen)}
|
|
color={inlineVendorOpen ? 'primary' : 'default'}
|
|
sx={{ mt: 1 }}
|
|
>
|
|
<AddIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</Box>
|
|
{inlineVendorOpen && (
|
|
<Paper variant="outlined" sx={{ p: 2, display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
|
<Typography variant="subtitle2">Neuer Lieferant</Typography>
|
|
<TextField size="small" label="Name *" value={inlineVendorForm.name} onChange={(e) => setInlineVendorForm((f) => ({ ...f, name: e.target.value }))} />
|
|
<TextField size="small" label="Kontakt-Name" value={inlineVendorForm.kontakt_name || ''} onChange={(e) => setInlineVendorForm((f) => ({ ...f, kontakt_name: e.target.value }))} />
|
|
<TextField size="small" label="E-Mail" value={inlineVendorForm.email || ''} onChange={(e) => setInlineVendorForm((f) => ({ ...f, email: e.target.value }))} />
|
|
<TextField size="small" label="Telefon" value={inlineVendorForm.telefon || ''} onChange={(e) => setInlineVendorForm((f) => ({ ...f, telefon: e.target.value }))} />
|
|
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'flex-end' }}>
|
|
<Button size="small" onClick={() => { setInlineVendorOpen(false); setInlineVendorForm({ ...emptyVendorForm }); }}>Abbrechen</Button>
|
|
<Button size="small" variant="contained" onClick={() => createVendor.mutate(inlineVendorForm)} disabled={!inlineVendorForm.name.trim() || createVendor.isPending}>Anlegen</Button>
|
|
</Box>
|
|
</Paper>
|
|
)}
|
|
|
|
<Autocomplete
|
|
options={orderUsers}
|
|
getOptionLabel={(o) => o.name}
|
|
value={orderUsers.find((u) => u.id === orderForm.besteller_id) || null}
|
|
onChange={(_e, v) => setOrderForm((f) => ({ ...f, besteller_id: v?.id || '' }))}
|
|
renderInput={(params) => <TextField {...params} label="Besteller" />}
|
|
/>
|
|
|
|
<Autocomplete
|
|
options={orderUsers}
|
|
getOptionLabel={(o) => o.name}
|
|
value={orderUsers.find((u) => u.id === orderForm.mitglied_id) || null}
|
|
onChange={(_e, v) => setOrderForm((f) => ({ ...f, mitglied_id: v?.id || '' }))}
|
|
renderInput={(params) => <TextField {...params} label="Für Mitglied" />}
|
|
/>
|
|
|
|
<TextField
|
|
label="Notizen"
|
|
multiline
|
|
rows={3}
|
|
value={orderForm.notizen || ''}
|
|
onChange={(e) => setOrderForm((f) => ({ ...f, notizen: e.target.value }))}
|
|
/>
|
|
|
|
{/* ── Positionen ── */}
|
|
<Typography variant="subtitle2" sx={{ mt: 1 }}>Positionen</Typography>
|
|
{(orderForm.positionen || []).map((pos, idx) => (
|
|
<Box key={idx} sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
|
|
<Autocomplete<AusruestungArtikel, false, false, true>
|
|
freeSolo
|
|
size="small"
|
|
options={katalogItems}
|
|
getOptionLabel={(o) => typeof o === 'string' ? o : o.bezeichnung}
|
|
value={pos.bezeichnung || ''}
|
|
onChange={(_, v) => {
|
|
const next = [...(orderForm.positionen || [])];
|
|
if (v && typeof v !== 'string') {
|
|
next[idx] = { ...next[idx], bezeichnung: v.bezeichnung, artikel_id: v.id };
|
|
} else if (typeof v === 'string') {
|
|
next[idx] = { ...next[idx], bezeichnung: v, artikel_id: undefined };
|
|
} else {
|
|
next[idx] = { ...next[idx], bezeichnung: '', artikel_id: undefined };
|
|
}
|
|
setOrderForm((f) => ({ ...f, positionen: next }));
|
|
}}
|
|
onInputChange={(_, val, reason) => {
|
|
if (reason === 'input') {
|
|
const next = [...(orderForm.positionen || [])];
|
|
next[idx] = { ...next[idx], bezeichnung: val, artikel_id: undefined };
|
|
setOrderForm((f) => ({ ...f, positionen: next }));
|
|
}
|
|
}}
|
|
renderInput={(params) => <TextField {...params} label="Bezeichnung" size="small" required InputLabelProps={{ shrink: true }} />}
|
|
renderOption={(props, option) => (
|
|
<li {...props} key={option.id}>
|
|
<Box>
|
|
<Typography variant="body2">{option.bezeichnung}</Typography>
|
|
{option.kategorie_name && <Typography variant="caption" color="text.secondary">{option.kategorie_name}</Typography>}
|
|
</Box>
|
|
</li>
|
|
)}
|
|
isOptionEqualToValue={(o, v) => o.id === (typeof v === 'object' ? v.id : undefined)}
|
|
sx={{ flexGrow: 1 }}
|
|
/>
|
|
<TextField
|
|
label="Menge"
|
|
size="small"
|
|
type="number"
|
|
InputLabelProps={{ shrink: true }}
|
|
value={pos.menge}
|
|
onChange={(e) => {
|
|
const next = [...(orderForm.positionen || [])];
|
|
next[idx] = { ...next[idx], menge: Math.max(1, Number(e.target.value) || 1) };
|
|
setOrderForm((f) => ({ ...f, positionen: next }));
|
|
}}
|
|
sx={{ width: 90 }}
|
|
inputProps={{ min: 1 }}
|
|
/>
|
|
<TextField
|
|
label="Einheit"
|
|
size="small"
|
|
InputLabelProps={{ shrink: true }}
|
|
value={pos.einheit || 'Stk'}
|
|
onChange={(e) => {
|
|
const next = [...(orderForm.positionen || [])];
|
|
next[idx] = { ...next[idx], einheit: e.target.value };
|
|
setOrderForm((f) => ({ ...f, positionen: next }));
|
|
}}
|
|
sx={{ width: 100 }}
|
|
/>
|
|
<IconButton
|
|
size="small"
|
|
color="error"
|
|
onClick={() => {
|
|
const next = (orderForm.positionen || []).filter((_, i) => i !== idx);
|
|
setOrderForm((f) => ({ ...f, positionen: next }));
|
|
}}
|
|
>
|
|
<RemoveIcon />
|
|
</IconButton>
|
|
</Box>
|
|
))}
|
|
<Button
|
|
size="small"
|
|
startIcon={<AddIcon />}
|
|
onClick={() => setOrderForm((f) => ({ ...f, positionen: [...(f.positionen || []), { ...emptyPosition }] }))}
|
|
>
|
|
Position hinzufügen
|
|
</Button>
|
|
|
|
{/* ── Submit ── */}
|
|
</FormLayout>
|
|
</DashboardLayout>
|
|
);
|
|
}
|