271 lines
9.1 KiB
TypeScript
271 lines
9.1 KiB
TypeScript
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>
|
||
);
|
||
}
|