diff --git a/backend/src/services/bestellung.service.ts b/backend/src/services/bestellung.service.ts index 472b5fb..0e8ff92 100644 --- a/backend/src/services/bestellung.service.ts +++ b/backend/src/services/bestellung.service.ts @@ -222,7 +222,7 @@ async function createOrder(data: { bezeichnung: string; lieferant_id?: number; b } } -async function updateOrder(id: number, data: { bezeichnung?: string; lieferant_id?: number; notizen?: string; budget?: number; status?: string; steuersatz?: number }, userId: string) { +async function updateOrder(id: number, data: { bezeichnung?: string; lieferant_id?: number; besteller_id?: string | null; notizen?: string; budget?: number; status?: string; steuersatz?: number }, userId: string) { try { // Check current order for status change detection const current = await pool.query(`SELECT * FROM bestellungen WHERE id = $1`, [id]); @@ -247,22 +247,24 @@ async function updateOrder(id: number, data: { bezeichnung?: string; lieferant_i `UPDATE bestellungen SET bezeichnung = COALESCE($1, bezeichnung), lieferant_id = COALESCE($2, lieferant_id), - notizen = COALESCE($3, notizen), - budget = COALESCE($4, budget), - status = COALESCE($5, status), - bestellt_am = $6, - abgeschlossen_am = $7, - steuersatz = COALESCE($8, steuersatz), + besteller_id = CASE WHEN $3::text IS NOT NULL AND $3::text != '' THEN $3::uuid ELSE besteller_id END, + notizen = COALESCE($4, notizen), + budget = COALESCE($5, budget), + status = COALESCE($6, status), + bestellt_am = $7, + abgeschlossen_am = $8, + steuersatz = COALESCE($9, steuersatz), aktualisiert_am = NOW() - WHERE id = $9 + WHERE id = $10 RETURNING *`, - [data.bezeichnung, data.lieferant_id, data.notizen, data.budget, data.status, bestellt_am, abgeschlossen_am, data.steuersatz, id] + [data.bezeichnung, data.lieferant_id, data.besteller_id ?? null, data.notizen, data.budget, data.status, bestellt_am, abgeschlossen_am, data.steuersatz, id] ); if (result.rows.length === 0) return null; const changes: string[] = []; if (data.bezeichnung) changes.push(`Bezeichnung geändert`); if (data.lieferant_id) changes.push(`Lieferant geändert`); + if (data.besteller_id) changes.push('Besteller geändert'); if (data.status && data.status !== oldStatus) changes.push(`Status: ${oldStatus} → ${data.status}`); if (data.budget) changes.push(`Budget geändert`); if (data.steuersatz != null) changes.push(`Steuersatz: ${data.steuersatz}%`); diff --git a/frontend/src/pages/BestellungDetail.tsx b/frontend/src/pages/BestellungDetail.tsx index 75da7c2..354dcc8 100644 --- a/frontend/src/pages/BestellungDetail.tsx +++ b/frontend/src/pages/BestellungDetail.tsx @@ -27,21 +27,20 @@ import { Accordion, AccordionSummary, AccordionDetails, + Autocomplete, } from '@mui/material'; import { ArrowBack, Add as AddIcon, Delete as DeleteIcon, Edit as EditIcon, - Check as CheckIcon, - Close as CloseIcon, AttachFile, Alarm, History, Upload as UploadIcon, ArrowDropDown, - MoreVert, ExpandMore as ExpandMoreIcon, + Save as SaveIcon, } from '@mui/icons-material'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useParams, useNavigate } from 'react-router-dom'; @@ -50,7 +49,7 @@ import { useNotification } from '../contexts/NotificationContext'; import { usePermissionContext } from '../contexts/PermissionContext'; import { bestellungApi } from '../services/bestellung'; import { BESTELLUNG_STATUS_LABELS, BESTELLUNG_STATUS_COLORS } from '../types/bestellung.types'; -import type { BestellungStatus, BestellpositionFormData, ErinnerungFormData, Bestellposition } from '../types/bestellung.types'; +import type { BestellungStatus, BestellpositionFormData, ErinnerungFormData } from '../types/bestellung.types'; // ── Helpers ── @@ -99,8 +98,6 @@ export default function BestellungDetail() { // ── State ── const [newItem, setNewItem] = useState({ ...emptyItem }); - const [editingItemId, setEditingItemId] = useState(null); - const [editingItemData, setEditingItemData] = useState>({}); const [statusConfirmTarget, setStatusConfirmTarget] = useState(null); const [statusForce, setStatusForce] = useState(false); const [statusMenuAnchor, setStatusMenuAnchor] = useState(null); @@ -110,6 +107,24 @@ export default function BestellungDetail() { const [editMode, setEditMode] = useState(false); + const [editOrderData, setEditOrderData] = useState<{ + bezeichnung: string; + lieferant_id?: number; + besteller_id?: string; + notizen: string; + steuersatz: number; + }>({ bezeichnung: '', notizen: '', steuersatz: 20 }); + + const [editItemsData, setEditItemsData] = useState>({}); + + const [isSavingAll, setIsSavingAll] = useState(false); + const [reminderForm, setReminderForm] = useState({ faellig_am: '', nachricht: '' }); const [reminderFormOpen, setReminderFormOpen] = useState(false); const [deleteReminderTarget, setDeleteReminderTarget] = useState(null); @@ -127,6 +142,18 @@ export default function BestellungDetail() { const erinnerungen = data?.erinnerungen ?? []; const historie = data?.historie ?? []; + const { data: vendors = [] } = useQuery({ + queryKey: ['lieferanten'], + queryFn: bestellungApi.getVendors, + enabled: editMode, + }); + + const { data: orderUsers = [] } = useQuery({ + queryKey: ['bestellungen', 'order-users'], + queryFn: bestellungApi.getOrderUsers, + enabled: editMode, + }); + const canCreate = hasPermission('bestellungen:create'); const canDelete = hasPermission('bestellungen:delete'); const canManageReminders = hasPermission('bestellungen:manage_reminders'); @@ -150,14 +177,6 @@ export default function BestellungDetail() { onError: () => showError('Fehler beim Aktualisieren des Status'), }); - const updateOrder = useMutation({ - mutationFn: (data: Partial<{ steuersatz: number }>) => bestellungApi.updateOrder(orderId, data), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['bestellung', orderId] }); - }, - onError: () => showError('Fehler beim Aktualisieren'), - }); - const addItem = useMutation({ mutationFn: (data: BestellpositionFormData) => bestellungApi.addLineItem(orderId, data), onSuccess: () => { @@ -168,17 +187,6 @@ export default function BestellungDetail() { onError: () => showError('Fehler beim Hinzufügen der Position'), }); - const updateItem = useMutation({ - mutationFn: ({ itemId, data }: { itemId: number; data: Partial }) => - bestellungApi.updateLineItem(itemId, data), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['bestellung', orderId] }); - setEditingItemId(null); - showSuccess('Position aktualisiert'); - }, - onError: () => showError('Fehler beim Aktualisieren der Position'), - }); - const deleteItem = useMutation({ mutationFn: (itemId: number) => bestellungApi.deleteLineItem(itemId), onSuccess: () => { @@ -248,20 +256,58 @@ export default function BestellungDetail() { // ── Handlers ── - function startEditItem(item: Bestellposition) { - setEditingItemId(item.id); - setEditingItemData({ - bezeichnung: item.bezeichnung, - artikelnummer: item.artikelnummer || '', - menge: item.menge, - einheit: item.einheit, - einzelpreis: item.einzelpreis, + function enterEditMode() { + if (!bestellung) return; + setEditOrderData({ + bezeichnung: bestellung.bezeichnung, + lieferant_id: bestellung.lieferant_id, + besteller_id: bestellung.besteller_id || '', + notizen: bestellung.notizen || '', + steuersatz: parseFloat(String(bestellung.steuersatz ?? 20)), }); + setEditItemsData( + Object.fromEntries(positionen.map(p => [p.id, { + bezeichnung: p.bezeichnung, + artikelnummer: p.artikelnummer || '', + menge: parseFloat(String(p.menge)) || 1, + einheit: p.einheit, + einzelpreis: p.einzelpreis != null ? parseFloat(String(p.einzelpreis)) : undefined, + }])) + ); + setEditMode(true); } - function saveEditItem() { - if (editingItemId == null) return; - updateItem.mutate({ itemId: editingItemId, data: editingItemData }); + function cancelEditMode() { + setEditMode(false); + setEditItemsData({}); + } + + async function handleSaveAll() { + if (!bestellung) return; + setIsSavingAll(true); + try { + await bestellungApi.updateOrder(orderId, { + bezeichnung: editOrderData.bezeichnung, + lieferant_id: editOrderData.lieferant_id, + besteller_id: editOrderData.besteller_id || undefined, + notizen: editOrderData.notizen, + steuersatz: editOrderData.steuersatz, + }); + for (const item of positionen) { + const itemEdit = editItemsData[item.id]; + if (itemEdit) { + await bestellungApi.updateLineItem(item.id, itemEdit); + } + } + await queryClient.invalidateQueries({ queryKey: ['bestellung', orderId] }); + showSuccess('Änderungen gespeichert'); + setEditMode(false); + setEditItemsData({}); + } catch { + showError('Fehler beim Speichern'); + } finally { + setIsSavingAll(false); + } } function handleAddItem() { @@ -276,8 +322,15 @@ export default function BestellungDetail() { } // Compute totals (NUMERIC columns come as strings from PostgreSQL — parse to float) - const totalCost = positionen.reduce((sum, p) => sum + (parseFloat(String(p.einzelpreis)) || 0) * (parseFloat(String(p.menge)) || 0), 0); - const steuersatz = parseFloat(String(bestellung?.steuersatz ?? 20)); + const steuersatz = editMode ? editOrderData.steuersatz : parseFloat(String(bestellung?.steuersatz ?? 20)); + const totalCost = editMode + ? positionen.reduce((sum, p) => { + const d = editItemsData[p.id]; + const einzelpreis = d?.einzelpreis != null ? d.einzelpreis : (parseFloat(String(p.einzelpreis)) || 0); + const menge = d?.menge != null ? d.menge : (parseFloat(String(p.menge)) || 0); + return sum + einzelpreis * menge; + }, 0) + : positionen.reduce((sum, p) => sum + (parseFloat(String(p.einzelpreis)) || 0) * (parseFloat(String(p.menge)) || 0), 0); const taxAmount = totalCost * (steuersatz / 100); const totalBrutto = totalCost + taxAmount; const totalReceived = positionen.length > 0 @@ -321,6 +374,17 @@ export default function BestellungDetail() { {bestellung.bezeichnung} + {canCreate && !editMode && ( + + )} + {editMode && ( + <> + + + + )} {/* ── Info Cards ── */} - - - - Lieferant - {bestellung.lieferant_name || '–'} - + {editMode ? ( + + + + setEditOrderData(d => ({ ...d, bezeichnung: e.target.value }))} /> + + + o.name} + value={vendors.find(v => v.id === editOrderData.lieferant_id) ?? null} + onChange={(_, v) => setEditOrderData(d => ({ ...d, lieferant_id: v?.id }))} + renderInput={(params) => } + /> + + + o.name} + value={orderUsers.find(u => u.id === editOrderData.besteller_id) ?? null} + onChange={(_, v) => setEditOrderData(d => ({ ...d, besteller_id: v?.id || '' }))} + renderInput={(params) => } + /> + + + setEditOrderData(d => ({ ...d, notizen: e.target.value }))} /> + + + + ) : ( + + + + Lieferant + {bestellung.lieferant_name || '–'} + + + + + Besteller + {bestellung.besteller_name || '–'} + + + + + Erstellt am + {formatDate(bestellung.erstellt_am)} + + - - - Besteller - {bestellung.besteller_name || '–'} - - - - - Erstellt am - {formatDate(bestellung.erstellt_am)} - - - + )} {/* ── Status Action ── */} {canManageOrders && ( @@ -387,23 +486,21 @@ export default function BestellungDetail() { ) : null} {/* Manual override menu */} - {overrideStatuses.length > 0 && ( + {overrideStatuses.length > 0 && canManageOrders && ( <> - } onClick={(e) => setOverrideMenuAnchor(e.currentTarget)} > - - + Status manuell setzen + setOverrideMenuAnchor(null)} > - - Status manuell setzen - {overrideStatuses.map((s) => ( Positionen - {canCreate && ( - - )} @@ -465,34 +552,55 @@ export default function BestellungDetail() { EinzelpreisGesamtErhalten - {(canCreate || canDelete) && Aktionen} + {(editMode && (canCreate || canDelete)) && Aktionen} {positionen.map((p) => - editMode && editingItemId === p.id ? ( + editMode ? ( - setEditingItemData((d) => ({ ...d, bezeichnung: e.target.value }))} /> + setEditItemsData(d => ({ ...d, [p.id]: { ...(d[p.id] || { bezeichnung: p.bezeichnung, artikelnummer: p.artikelnummer || '', menge: parseFloat(String(p.menge)) || 1, einheit: p.einheit, einzelpreis: p.einzelpreis != null ? parseFloat(String(p.einzelpreis)) : undefined }), bezeichnung: e.target.value } }))} /> - setEditingItemData((d) => ({ ...d, artikelnummer: e.target.value }))} /> + setEditItemsData(d => ({ ...d, [p.id]: { ...(d[p.id] || { bezeichnung: p.bezeichnung, artikelnummer: p.artikelnummer || '', menge: parseFloat(String(p.menge)) || 1, einheit: p.einheit, einzelpreis: p.einzelpreis != null ? parseFloat(String(p.einzelpreis)) : undefined }), artikelnummer: e.target.value } }))} /> - setEditingItemData((d) => ({ ...d, menge: Number(e.target.value) }))} /> + setEditItemsData(d => ({ ...d, [p.id]: { ...(d[p.id] || { bezeichnung: p.bezeichnung, artikelnummer: p.artikelnummer || '', menge: parseFloat(String(p.menge)) || 1, einheit: p.einheit, einzelpreis: p.einzelpreis != null ? parseFloat(String(p.einzelpreis)) : undefined }), menge: Number(e.target.value) } }))} /> - setEditingItemData((d) => ({ ...d, einheit: e.target.value }))} /> + setEditItemsData(d => ({ ...d, [p.id]: { ...(d[p.id] || { bezeichnung: p.bezeichnung, artikelnummer: p.artikelnummer || '', menge: parseFloat(String(p.menge)) || 1, einheit: p.einheit, einzelpreis: p.einzelpreis != null ? parseFloat(String(p.einzelpreis)) : undefined }), einheit: e.target.value } }))} /> - setEditingItemData((d) => ({ ...d, einzelpreis: e.target.value ? Number(e.target.value) : undefined }))} /> + setEditItemsData(d => ({ ...d, [p.id]: { ...(d[p.id] || { bezeichnung: p.bezeichnung, artikelnummer: p.artikelnummer || '', menge: parseFloat(String(p.menge)) || 1, einheit: p.einheit, einzelpreis: p.einzelpreis != null ? parseFloat(String(p.einzelpreis)) : undefined }), einzelpreis: e.target.value ? Number(e.target.value) : undefined } }))} /> - {formatCurrency((editingItemData.einzelpreis ?? 0) * (editingItemData.menge ?? 0))} - {p.erhalten_menge} - - setEditingItemId(null)}> + {formatCurrency((editItemsData[p.id]?.einzelpreis ?? parseFloat(String(p.einzelpreis ?? 0))) * (editItemsData[p.id]?.menge ?? parseFloat(String(p.menge))))} + + {canManageOrders ? ( + updateReceived.mutate({ itemId: p.id, menge: Number(e.target.value) })} /> + ) : p.erhalten_menge} + + {(canCreate || canDelete) && ( + + {canDelete && ( + setDeleteItemTarget(p.id)}> + + + )} + + )} ) : ( @@ -516,12 +624,6 @@ export default function BestellungDetail() { p.erhalten_menge )} - {(canCreate || canDelete) && ( - - {editMode && canCreate && startEditItem(p)}>} - {editMode && canDelete && setDeleteItemTarget(p.id)}>} - - )} ), )} @@ -560,7 +662,7 @@ export default function BestellungDetail() { Netto {formatCurrency(totalCost)} - + @@ -571,12 +673,12 @@ export default function BestellungDetail() { size="small" type="number" sx={{ width: 70 }} - value={steuersatz} + value={editOrderData.steuersatz} inputProps={{ min: 0, max: 100, step: 0.5 }} onChange={(e) => { const val = parseFloat(e.target.value); if (!isNaN(val) && val >= 0 && val <= 100) { - updateOrder.mutate({ steuersatz: val }); + setEditOrderData(d => ({ ...d, steuersatz: val })); } }} /> @@ -587,12 +689,12 @@ export default function BestellungDetail() { {formatCurrency(taxAmount)} - + Brutto {formatCurrency(totalBrutto)} - + )} @@ -729,7 +831,7 @@ export default function BestellungDetail() { {/* ── Notizen ── */} - {bestellung.notizen && ( + {!editMode && bestellung.notizen && ( Notizen {bestellung.notizen} diff --git a/frontend/src/pages/Bestellungen.tsx b/frontend/src/pages/Bestellungen.tsx index 552f9c6..52711c4 100644 --- a/frontend/src/pages/Bestellungen.tsx +++ b/frontend/src/pages/Bestellungen.tsx @@ -1,6 +1,12 @@ import { useState, useEffect, useMemo } from 'react'; import { + Accordion, + AccordionSummary, + AccordionDetails, Box, + Card, + CardContent, + Grid, Tab, Tabs, Typography, @@ -16,12 +22,10 @@ import { Checkbox, FormControlLabel, FormGroup, - Popover, - Badge, LinearProgress, Divider, } from '@mui/material'; -import { Add as AddIcon, FilterList as FilterListIcon } from '@mui/icons-material'; +import { Add as AddIcon, ExpandMore as ExpandMoreIcon, FilterList as FilterListIcon } from '@mui/icons-material'; import { useQuery } from '@tanstack/react-query'; import { useNavigate, useSearchParams } from 'react-router-dom'; import DashboardLayout from '../components/dashboard/DashboardLayout'; @@ -78,6 +82,7 @@ export default function Bestellungen() { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const { hasPermission } = usePermissionContext(); + const canManageVendors = hasPermission('bestellungen:manage_vendors'); // Tab from URL const [tab, setTab] = useState(() => { @@ -90,10 +95,7 @@ export default function Bestellungen() { }, [searchParams]); // ── Filter state ── - const [filterAnchor, setFilterAnchor] = useState(null); - const [selectedVendors, setSelectedVendors] = useState | null>(null); // null = all - const [selectedOrderers, setSelectedOrderers] = useState | null>(null); const [selectedStatuses, setSelectedStatuses] = useState>( () => new Set(ALL_STATUSES.filter((s) => !DEFAULT_EXCLUDED_STATUSES.includes(s))) ); @@ -118,14 +120,6 @@ export default function Bestellungen() { return Array.from(map.entries()).sort((a, b) => a[1].localeCompare(b[1])); }, [orders]); - const uniqueOrderers = useMemo(() => { - const map = new Map(); - orders.forEach((o) => { - if (o.besteller_name) map.set(o.besteller_id ?? o.besteller_name, o.besteller_name); - }); - return Array.from(map.entries()).sort((a, b) => a[1].localeCompare(b[1])); - }, [orders]); - // ── Filtered orders ── const filteredOrders = useMemo(() => { return orders.filter((o) => { @@ -136,28 +130,21 @@ export default function Bestellungen() { const key = String(o.lieferant_id ?? o.lieferant_name ?? ''); if (!selectedVendors.has(key)) return false; } - // Orderer filter (null = all selected) - if (selectedOrderers !== null) { - const key = o.besteller_id ?? o.besteller_name ?? ''; - if (!selectedOrderers.has(key)) return false; - } return true; }); - }, [orders, selectedStatuses, selectedVendors, selectedOrderers]); + }, [orders, selectedStatuses, selectedVendors]); // ── Active filter count ── const activeFilterCount = useMemo(() => { let count = 0; if (selectedStatuses.size !== ALL_STATUSES.length - DEFAULT_EXCLUDED_STATUSES.length) count++; if (selectedVendors !== null) count++; - if (selectedOrderers !== null) count++; return count; - }, [selectedStatuses, selectedVendors, selectedOrderers]); + }, [selectedStatuses, selectedVendors]); // ── Filter handlers ── function resetFilters() { setSelectedVendors(null); - setSelectedOrderers(null); setSelectedStatuses(new Set(ALL_STATUSES.filter((s) => !DEFAULT_EXCLUDED_STATUSES.includes(s)))); } @@ -187,29 +174,10 @@ export default function Bestellungen() { }); } - function toggleOrderer(key: string) { - setSelectedOrderers((prev) => { - if (prev === null) { - const allKeys = new Set(uniqueOrderers.map(([k]) => k)); - allKeys.delete(key); - return allKeys; - } - const next = new Set(prev); - if (next.has(key)) next.delete(key); - else next.add(key); - if (next.size === uniqueOrderers.length) return null; - return next; - }); - } - function isVendorSelected(key: string) { return selectedVendors === null || selectedVendors.has(key); } - function isOrdererSelected(key: string) { - return selectedOrderers === null || selectedOrderers.has(key); - } - // ── Render ── return ( @@ -219,23 +187,84 @@ export default function Bestellungen() { { setTab(v); navigate(`/bestellungen?tab=${v}`, { replace: true }); }} variant="scrollable" scrollButtons="auto"> - + {canManageVendors && } {/* ── Tab 0: Orders ── */} + {/* ── Summary Cards ── */} + + {[ + { label: 'Noch nicht bestellt', count: orders.filter(o => o.status === 'entwurf' || o.status === 'erstellt').length, color: 'text.secondary' }, + { label: 'Bestellt', count: orders.filter(o => o.status === 'bestellt').length, color: 'primary.main' }, + { label: 'Teillieferung', count: orders.filter(o => o.status === 'teillieferung').length, color: 'warning.main' }, + { label: 'Vollständig', count: orders.filter(o => o.status === 'vollstaendig').length, color: 'success.main' }, + { label: 'Gesamt', count: orders.length, color: 'text.primary' }, + ].map(({ label, count, color }) => ( + + + + {count} + {label} + + + + ))} + + + {/* ── Filter ── */} + + }> + + + Filter + {activeFilterCount > 0 && ( + + )} + + + + + {/* Status */} + + Status + + {ALL_STATUSES.map((s) => ( + toggleStatus(s)} />} + label={BESTELLUNG_STATUS_LABELS[s]} + /> + ))} + + + + {/* Vendor */} + {uniqueVendors.length > 0 && ( + + Lieferant + + {uniqueVendors.map(([key, label]) => ( + toggleVendor(key)} />} + label={label} + /> + ))} + + + )} + + + + + + + + + {/* Active filter info */} - - - {activeFilterCount > 0 && ( )} @@ -244,69 +273,6 @@ export default function Bestellungen() { - {/* Filter Popover */} - setFilterAnchor(null)} - anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} - transformOrigin={{ vertical: 'top', horizontal: 'left' }} - slotProps={{ paper: { sx: { p: 2, maxWidth: 480, maxHeight: '70vh', overflow: 'auto' } } }} - > - - {/* Status */} - - Status - - {ALL_STATUSES.map((s) => ( - toggleStatus(s)} />} - label={BESTELLUNG_STATUS_LABELS[s]} - /> - ))} - - - - - {/* Vendor */} - {uniqueVendors.length > 0 && ( - - Lieferant - - {uniqueVendors.map(([key, label]) => ( - toggleVendor(key)} />} - label={label} - /> - ))} - - - )} - {uniqueVendors.length > 0 && } - - {/* Orderer */} - {uniqueOrderers.length > 0 && ( - - Besteller - - {uniqueOrderers.map(([key, label]) => ( - toggleOrderer(key)} />} - label={label} - /> - ))} - - - )} - - - - - -
@@ -360,7 +326,8 @@ export default function Bestellungen() { = 100 ? 'success' : 'primary'} sx={{ flexGrow: 1, height: 6, borderRadius: 3 }} /> @@ -386,6 +353,7 @@ export default function Bestellungen() { {/* ── Tab 1: Vendors ── */} + {canManageVendors && (
@@ -417,7 +385,7 @@ export default function Bestellungen() { {v.telefon || '–'} {v.website ? ( - e.stopPropagation()}>{v.website} + e.stopPropagation()}>{v.website} ) : '–'} @@ -427,12 +395,11 @@ export default function Bestellungen() {
- {hasPermission('bestellungen:manage_vendors') && ( navigate('/bestellungen/lieferanten/neu')} aria-label="Lieferant hinzufügen"> - )} + )} ); } diff --git a/frontend/src/pages/LieferantDetail.tsx b/frontend/src/pages/LieferantDetail.tsx index 354be79..e278756 100644 --- a/frontend/src/pages/LieferantDetail.tsx +++ b/frontend/src/pages/LieferantDetail.tsx @@ -26,6 +26,12 @@ import type { LieferantFormData } from '../types/bestellung.types'; 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(); @@ -265,7 +271,7 @@ export default function LieferantDetail() { Website - {vendor!.website ? {vendor!.website} : '–'} + {vendor!.website ? {vendor!.website} : '–'} diff --git a/frontend/src/types/bestellung.types.ts b/frontend/src/types/bestellung.types.ts index 1f8b679..5237b4c 100644 --- a/frontend/src/types/bestellung.types.ts +++ b/frontend/src/types/bestellung.types.ts @@ -77,7 +77,6 @@ export interface BestellungFormData { lieferant_id?: number; besteller_id?: string; status?: BestellungStatus; - budget?: number; steuersatz?: number; notizen?: string; positionen?: BestellpositionFormData[];