Files
dashboard/frontend/src/pages/LieferantDetail.tsx

271 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import {
Box,
Typography,
Paper,
Button,
TextField,
Skeleton,
} from '@mui/material';
import { 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';
import { ConfirmDialog, PageHeader, InfoGrid } from '../components/templates';
const emptyForm: LieferantFormData = { name: '', kontakt_name: '', email: '', telefon: '', adresse: '', website: '', notizen: '' };
function ensureUrl(url: string): string {
if (!url) return url;
if (url.startsWith('http://') || url.startsWith('https://')) return url;
return `https://${url}`;
}
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);
// Sync edit mode when navigating between /neu and /:id within the same component instance
useEffect(() => {
if (isNew) {
setEditMode(true);
setForm({ ...emptyForm });
} else {
setEditMode(false);
}
}, [isNew]);
// ── Query ──
const { data: vendor, 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 && !vendor) {
if (isError) {
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>
);
}
return (
<DashboardLayout>
<PageHeader title="" backTo="/bestellungen?tab=1" />
<Skeleton width={300} height={40} />
<Paper sx={{ p: 3, mt: 2 }}>
<Skeleton height={40} />
<Skeleton height={40} />
<Skeleton height={40} />
</Paper>
</DashboardLayout>
);
}
const isSaving = createVendor.isPending || updateVendor.isPending;
return (
<DashboardLayout>
{/* ── Header ── */}
<PageHeader
title={isNew ? 'Neuer Lieferant' : vendor!.name}
backTo="/bestellungen?tab=1"
actions={
<>
{!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>
</>
)}
</>
}
/>
{/* ── 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>
) : (
<InfoGrid
columns={2}
fields={[
{ label: 'Name', value: vendor!.name },
{ label: 'Kontakt', value: vendor!.kontakt_name || '' },
{ label: 'E-Mail', value: vendor!.email ? <a href={`mailto:${vendor!.email}`}>{vendor!.email}</a> : '' },
{ label: 'Telefon', value: vendor!.telefon || '' },
{ label: 'Website', value: vendor!.website ? <a href={ensureUrl(vendor!.website)} target="_blank" rel="noopener noreferrer">{vendor!.website}</a> : '' },
{ label: 'Adresse', value: vendor!.adresse || '' },
...(vendor!.notizen ? [{ label: 'Notizen', value: <Typography sx={{ whiteSpace: 'pre-wrap' }}>{vendor!.notizen}</Typography>, fullWidth: true }] : []),
]}
/>
)}
{/* ── Delete Dialog ── */}
<ConfirmDialog
open={deleteDialogOpen}
onClose={() => setDeleteDialogOpen(false)}
onConfirm={() => deleteVendor.mutate()}
title="Lieferant löschen"
message={<Typography>Soll der Lieferant <strong>{vendor?.name}</strong> wirklich gelöscht werden?</Typography>}
confirmLabel="Löschen"
confirmColor="error"
isLoading={deleteVendor.isPending}
/>
</DashboardLayout>
);
}