refactor external orders

This commit is contained in:
Matthias Hochmeister
2026-03-25 14:26:41 +01:00
parent 561334791b
commit 5add6590e5
10 changed files with 740 additions and 259 deletions

View File

@@ -0,0 +1,306 @@
import { useState, useEffect } from 'react';
import {
Box,
Typography,
Paper,
Button,
TextField,
IconButton,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Grid,
Card,
CardContent,
Skeleton,
} from '@mui/material';
import { ArrowBack, Edit as EditIcon, Delete as DeleteIcon, Save as SaveIcon, Close as CloseIcon } from '@mui/icons-material';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useParams, useNavigate } from 'react-router-dom';
import DashboardLayout from '../components/dashboard/DashboardLayout';
import { useNotification } from '../contexts/NotificationContext';
import { usePermissionContext } from '../contexts/PermissionContext';
import { bestellungApi } from '../services/bestellung';
import type { LieferantFormData } from '../types/bestellung.types';
const emptyForm: LieferantFormData = { name: '', kontakt_name: '', email: '', telefon: '', adresse: '', website: '', notizen: '' };
export default function LieferantDetail() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const queryClient = useQueryClient();
const { showSuccess, showError } = useNotification();
const { hasPermission } = usePermissionContext();
const isNew = id === 'neu';
const vendorId = isNew ? 0 : Number(id);
const canManage = hasPermission('bestellungen:manage_vendors');
const [editMode, setEditMode] = useState(isNew);
const [form, setForm] = useState<LieferantFormData>({ ...emptyForm });
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
// ── Query ──
const { data: vendor, isLoading, isError } = useQuery({
queryKey: ['lieferant', vendorId],
queryFn: () => bestellungApi.getVendor(vendorId),
enabled: !isNew && !!vendorId,
});
// Sync form with loaded vendor data
useEffect(() => {
if (vendor) {
setForm({
name: vendor.name,
kontakt_name: vendor.kontakt_name || '',
email: vendor.email || '',
telefon: vendor.telefon || '',
adresse: vendor.adresse || '',
website: vendor.website || '',
notizen: vendor.notizen || '',
});
}
}, [vendor]);
// ── Mutations ──
const createVendor = useMutation({
mutationFn: (data: LieferantFormData) => bestellungApi.createVendor(data),
onSuccess: (created) => {
queryClient.invalidateQueries({ queryKey: ['lieferanten'] });
showSuccess('Lieferant erstellt');
navigate(`/bestellungen/lieferanten/${created.id}`, { replace: true });
},
onError: () => showError('Fehler beim Erstellen des Lieferanten'),
});
const updateVendor = useMutation({
mutationFn: (data: LieferantFormData) => bestellungApi.updateVendor(vendorId, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['lieferant', vendorId] });
queryClient.invalidateQueries({ queryKey: ['lieferanten'] });
showSuccess('Lieferant aktualisiert');
setEditMode(false);
},
onError: () => showError('Fehler beim Aktualisieren des Lieferanten'),
});
const deleteVendor = useMutation({
mutationFn: () => bestellungApi.deleteVendor(vendorId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['lieferanten'] });
showSuccess('Lieferant gelöscht');
navigate('/bestellungen?tab=1');
},
onError: () => showError('Fehler beim Löschen des Lieferanten'),
});
function handleSave() {
if (!form.name.trim()) return;
if (isNew) {
createVendor.mutate(form);
} else {
updateVendor.mutate(form);
}
}
function handleCancel() {
if (isNew) {
navigate('/bestellungen?tab=1');
} else if (vendor) {
setForm({
name: vendor.name,
kontakt_name: vendor.kontakt_name || '',
email: vendor.email || '',
telefon: vendor.telefon || '',
adresse: vendor.adresse || '',
website: vendor.website || '',
notizen: vendor.notizen || '',
});
setEditMode(false);
}
}
// ── Loading / Error ──
if (!isNew && isLoading) {
return (
<DashboardLayout>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 3 }}>
<IconButton onClick={() => navigate('/bestellungen?tab=1')}><ArrowBack /></IconButton>
<Skeleton width={300} height={40} />
</Box>
<Paper sx={{ p: 3 }}>
<Skeleton height={40} />
<Skeleton height={40} />
<Skeleton height={40} />
</Paper>
</DashboardLayout>
);
}
if (!isNew && (isError || !vendor)) {
return (
<DashboardLayout>
<Box sx={{ p: 4, textAlign: 'center' }}>
<Typography color="error">Lieferant nicht gefunden.</Typography>
<Button sx={{ mt: 2 }} onClick={() => navigate('/bestellungen?tab=1')}>Zurück</Button>
</Box>
</DashboardLayout>
);
}
const isSaving = createVendor.isPending || updateVendor.isPending;
return (
<DashboardLayout>
{/* ── Header ── */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 3 }}>
<IconButton onClick={() => navigate('/bestellungen?tab=1')}>
<ArrowBack />
</IconButton>
<Typography variant="h4" sx={{ flexGrow: 1 }}>
{isNew ? 'Neuer Lieferant' : vendor!.name}
</Typography>
{!isNew && canManage && !editMode && (
<>
<Button startIcon={<EditIcon />} onClick={() => setEditMode(true)}>
Bearbeiten
</Button>
<Button startIcon={<DeleteIcon />} color="error" onClick={() => setDeleteDialogOpen(true)}>
Löschen
</Button>
</>
)}
{editMode && (
<>
<Button
variant="contained"
startIcon={<SaveIcon />}
onClick={handleSave}
disabled={!form.name.trim() || isSaving}
>
Speichern
</Button>
<Button startIcon={<CloseIcon />} onClick={handleCancel}>
Abbrechen
</Button>
</>
)}
</Box>
{/* ── Content ── */}
{editMode ? (
<Paper sx={{ p: 3 }}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<TextField
label="Name"
required
value={form.name}
onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
/>
<TextField
label="Kontakt-Name"
value={form.kontakt_name || ''}
onChange={(e) => setForm((f) => ({ ...f, kontakt_name: e.target.value }))}
/>
<TextField
label="E-Mail"
type="email"
value={form.email || ''}
onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
/>
<TextField
label="Telefon"
value={form.telefon || ''}
onChange={(e) => setForm((f) => ({ ...f, telefon: e.target.value }))}
/>
<TextField
label="Adresse"
value={form.adresse || ''}
onChange={(e) => setForm((f) => ({ ...f, adresse: e.target.value }))}
/>
<TextField
label="Website"
value={form.website || ''}
onChange={(e) => setForm((f) => ({ ...f, website: e.target.value }))}
/>
<TextField
label="Notizen"
multiline
rows={4}
value={form.notizen || ''}
onChange={(e) => setForm((f) => ({ ...f, notizen: e.target.value }))}
/>
</Box>
</Paper>
) : (
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={4}>
<Card variant="outlined"><CardContent>
<Typography variant="caption" color="text.secondary">Name</Typography>
<Typography>{vendor!.name}</Typography>
</CardContent></Card>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<Card variant="outlined"><CardContent>
<Typography variant="caption" color="text.secondary">Kontakt</Typography>
<Typography>{vendor!.kontakt_name || ''}</Typography>
</CardContent></Card>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<Card variant="outlined"><CardContent>
<Typography variant="caption" color="text.secondary">E-Mail</Typography>
<Typography>
{vendor!.email ? <a href={`mailto:${vendor!.email}`}>{vendor!.email}</a> : ''}
</Typography>
</CardContent></Card>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<Card variant="outlined"><CardContent>
<Typography variant="caption" color="text.secondary">Telefon</Typography>
<Typography>{vendor!.telefon || ''}</Typography>
</CardContent></Card>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<Card variant="outlined"><CardContent>
<Typography variant="caption" color="text.secondary">Website</Typography>
<Typography>
{vendor!.website ? <a href={vendor!.website} target="_blank" rel="noopener noreferrer">{vendor!.website}</a> : ''}
</Typography>
</CardContent></Card>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<Card variant="outlined"><CardContent>
<Typography variant="caption" color="text.secondary">Adresse</Typography>
<Typography>{vendor!.adresse || ''}</Typography>
</CardContent></Card>
</Grid>
{vendor!.notizen && (
<Grid item xs={12}>
<Card variant="outlined"><CardContent>
<Typography variant="caption" color="text.secondary">Notizen</Typography>
<Typography sx={{ whiteSpace: 'pre-wrap' }}>{vendor!.notizen}</Typography>
</CardContent></Card>
</Grid>
)}
</Grid>
)}
{/* ── Delete Dialog ── */}
<Dialog open={deleteDialogOpen} onClose={() => setDeleteDialogOpen(false)}>
<DialogTitle>Lieferant löschen</DialogTitle>
<DialogContent>
<Typography>
Soll der Lieferant <strong>{vendor?.name}</strong> wirklich gelöscht werden?
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={() => setDeleteDialogOpen(false)}>Abbrechen</Button>
<Button color="error" variant="contained" onClick={() => deleteVendor.mutate()} disabled={deleteVendor.isPending}>
Löschen
</Button>
</DialogActions>
</Dialog>
</DashboardLayout>
);
}