import { useState, useMemo, useEffect, useCallback } from 'react'; import { Box, Tab, Tabs, Typography, Grid, Button, Chip, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Dialog, DialogTitle, DialogContent, DialogActions, TextField, IconButton, MenuItem, Select, FormControl, InputLabel, Autocomplete, Divider, Checkbox, FormControlLabel, Tooltip, } from '@mui/material'; import { Add as AddIcon, Delete as DeleteIcon, Edit as EditIcon, Check as CheckIcon, Close as CloseIcon, Link as LinkIcon, Settings as SettingsIcon, } from '@mui/icons-material'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useSearchParams } from 'react-router-dom'; import DashboardLayout from '../components/dashboard/DashboardLayout'; import ChatAwareFab from '../components/shared/ChatAwareFab'; import { useNotification } from '../contexts/NotificationContext'; import { usePermissionContext } from '../contexts/PermissionContext'; import { useAuth } from '../contexts/AuthContext'; import { ausruestungsanfrageApi } from '../services/ausruestungsanfrage'; import { bestellungApi } from '../services/bestellung'; import { AUSRUESTUNG_STATUS_LABELS, AUSRUESTUNG_STATUS_COLORS } from '../types/ausruestungsanfrage.types'; import type { AusruestungArtikel, AusruestungArtikelFormData, AusruestungAnfrageFormItem, AusruestungAnfrageDetailResponse, AusruestungAnfrageStatus, AusruestungAnfrage, AusruestungOverview, AusruestungEigenschaft, } from '../types/ausruestungsanfrage.types'; import type { Bestellung } from '../types/bestellung.types'; // ─── Helpers ───────────────────────────────────────────────────────────────── function formatOrderId(r: AusruestungAnfrage): string { if (r.bestell_jahr && r.bestell_nummer) { return `${r.bestell_jahr}/${String(r.bestell_nummer).padStart(3, '0')}`; } return `#${r.id}`; } const ACTIVE_STATUSES: AusruestungAnfrageStatus[] = ['offen', 'genehmigt', 'bestellt']; // ─── Eigenschaft Fields Component ──────────────────────────────────────────── interface EigenschaftFieldsProps { eigenschaften: AusruestungEigenschaft[]; values: Record; onChange: (eigenschaftId: number, wert: string) => void; } function EigenschaftFields({ eigenschaften, values, onChange }: EigenschaftFieldsProps) { if (eigenschaften.length === 0) return null; return ( {eigenschaften.map(e => ( {e.typ === 'options' && e.optionen && e.optionen.length > 0 ? ( {e.name} ) : ( onChange(e.id, ev.target.value)} required={e.pflicht} sx={{ minWidth: 160 }} /> )} ))} ); } // ─── Category Management Dialog ────────────────────────────────────────────── interface KategorieDialogProps { open: boolean; onClose: () => void; } function KategorieDialog({ open, onClose }: KategorieDialogProps) { const { showSuccess, showError } = useNotification(); const queryClient = useQueryClient(); const [newName, setNewName] = useState(''); const [editId, setEditId] = useState(null); const [editName, setEditName] = useState(''); const { data: kategorien = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'kategorien'], queryFn: () => ausruestungsanfrageApi.getKategorien(), enabled: open, }); const createMut = useMutation({ mutationFn: (name: string) => ausruestungsanfrageApi.createKategorie(name), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Kategorie erstellt'); setNewName(''); }, onError: () => showError('Fehler beim Erstellen'), }); const updateMut = useMutation({ mutationFn: ({ id, name }: { id: number; name: string }) => ausruestungsanfrageApi.updateKategorie(id, name), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Kategorie aktualisiert'); setEditId(null); }, onError: () => showError('Fehler beim Aktualisieren'), }); const deleteMut = useMutation({ mutationFn: (id: number) => ausruestungsanfrageApi.deleteKategorie(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Kategorie gelöscht'); }, onError: () => showError('Fehler beim Löschen'), }); return ( Kategorien verwalten setNewName(e.target.value)} sx={{ flexGrow: 1 }} onKeyDown={e => { if (e.key === 'Enter' && newName.trim()) createMut.mutate(newName.trim()); }} /> {kategorien.length === 0 ? ( Keine Kategorien vorhanden. ) : ( kategorien.map(k => ( {editId === k.id ? ( <> setEditName(e.target.value)} sx={{ flexGrow: 1 }} onKeyDown={e => { if (e.key === 'Enter' && editName.trim()) updateMut.mutate({ id: k.id, name: editName.trim() }); }} /> { if (editName.trim()) updateMut.mutate({ id: k.id, name: editName.trim() }); }}> setEditId(null)}> ) : ( <> {k.name} { setEditId(k.id); setEditName(k.name); }}> deleteMut.mutate(k.id)}> )} )) )} ); } // ─── Eigenschaften Editor (in Artikel dialog) ──────────────────────────────── interface EigenschaftenEditorProps { artikelId: number | null; } function EigenschaftenEditor({ artikelId }: EigenschaftenEditorProps) { const { showSuccess, showError } = useNotification(); const queryClient = useQueryClient(); const [newName, setNewName] = useState(''); const [newTyp, setNewTyp] = useState<'options' | 'freitext'>('options'); const [newOptionen, setNewOptionen] = useState(''); const [newPflicht, setNewPflicht] = useState(false); const { data: eigenschaften = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'eigenschaften', artikelId], queryFn: () => ausruestungsanfrageApi.getArtikelEigenschaften(artikelId!), enabled: artikelId != null, }); const upsertMut = useMutation({ mutationFn: (data: { eigenschaft_id?: number; name: string; typ: string; optionen?: string[]; pflicht?: boolean; sort_order?: number }) => ausruestungsanfrageApi.upsertArtikelEigenschaft(artikelId!, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage', 'eigenschaften', artikelId] }); showSuccess('Eigenschaft gespeichert'); }, onError: () => showError('Fehler beim Speichern'), }); const deleteMut = useMutation({ mutationFn: (id: number) => ausruestungsanfrageApi.deleteArtikelEigenschaft(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage', 'eigenschaften', artikelId] }); showSuccess('Eigenschaft gelöscht'); }, onError: () => showError('Fehler beim Löschen'), }); const handleAdd = () => { if (!newName.trim()) return; const optionen = newTyp === 'options' ? newOptionen.split(',').map(s => s.trim()).filter(Boolean) : undefined; upsertMut.mutate({ name: newName.trim(), typ: newTyp, optionen, pflicht: newPflicht, sort_order: eigenschaften.length, }); setNewName(''); setNewOptionen(''); setNewPflicht(false); }; if (artikelId == null) return Bitte speichern Sie den Artikel zuerst, bevor Sie Eigenschaften hinzufügen.; return ( Eigenschaften {eigenschaften.map(e => ( {e.name} ({e.typ === 'options' ? `Auswahl: ${e.optionen?.join(', ')}` : 'Freitext'}) {e.pflicht && } deleteMut.mutate(e.id)}> ))} setNewName(e.target.value)} sx={{ flexGrow: 1 }} /> Typ setNewPflicht(e.target.checked)} />} label="Pflicht" /> {newTyp === 'options' && ( setNewOptionen(e.target.value)} placeholder="S, M, L, XL" fullWidth /> )} ); } // ─── Detail Modal ───────────────────────────────────────────────────────────── interface DetailModalProps { requestId: number | null; onClose: () => void; showAdminActions?: boolean; showEditButton?: boolean; canEditAny?: boolean; currentUserId?: string; } function DetailModal({ requestId, onClose, showAdminActions, showEditButton, canEditAny, currentUserId }: DetailModalProps) { const { showSuccess, showError } = useNotification(); const queryClient = useQueryClient(); const { hasPermission } = usePermissionContext(); const [editing, setEditing] = useState(false); const [editBezeichnung, setEditBezeichnung] = useState(''); const [editNotizen, setEditNotizen] = useState(''); const [editItems, setEditItems] = useState([]); // Admin action state const [actionDialog, setActionDialog] = useState<{ action: 'genehmigt' | 'abgelehnt' } | null>(null); const [adminNotizen, setAdminNotizen] = useState(''); const [statusChangeValue, setStatusChangeValue] = useState(''); const [linkDialog, setLinkDialog] = useState(false); const [selectedBestellung, setSelectedBestellung] = useState(null); const { data: detail, isLoading } = useQuery({ queryKey: ['ausruestungsanfrage', 'request', requestId], queryFn: () => ausruestungsanfrageApi.getRequest(requestId!), enabled: requestId != null, }); const { data: catalogItems = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'items-for-edit'], queryFn: () => ausruestungsanfrageApi.getItems({ aktiv: true }), enabled: editing, }); const { data: bestellungen = [] } = useQuery({ queryKey: ['bestellungen'], queryFn: () => bestellungApi.getOrders(), enabled: linkDialog, }); const updateMut = useMutation({ mutationFn: (data: { bezeichnung?: string; notizen?: string; items?: AusruestungAnfrageFormItem[] }) => ausruestungsanfrageApi.updateRequest(requestId!, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Anfrage aktualisiert'); setEditing(false); }, onError: () => showError('Fehler beim Aktualisieren'), }); const statusMut = useMutation({ mutationFn: ({ status, notes }: { status: string; notes?: string }) => ausruestungsanfrageApi.updateRequestStatus(requestId!, status, notes), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Status aktualisiert'); setActionDialog(null); setAdminNotizen(''); setStatusChangeValue(''); }, onError: () => showError('Fehler beim Aktualisieren'), }); const linkMut = useMutation({ mutationFn: (bestellungId: number) => ausruestungsanfrageApi.linkToOrder(requestId!, bestellungId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Verknüpfung erstellt'); setLinkDialog(false); setSelectedBestellung(null); }, onError: () => showError('Fehler beim Verknüpfen'), }); const startEditing = () => { if (!detail) return; setEditBezeichnung(detail.anfrage.bezeichnung || ''); setEditNotizen(detail.anfrage.notizen || ''); setEditItems(detail.positionen.map(p => ({ artikel_id: p.artikel_id, bezeichnung: p.bezeichnung, menge: p.menge, notizen: p.notizen, eigenschaften: p.eigenschaften?.map(e => ({ eigenschaft_id: e.eigenschaft_id, wert: e.wert })), }))); setEditing(true); }; const handleSaveEdit = () => { if (editItems.length === 0) return; updateMut.mutate({ bezeichnung: editBezeichnung || undefined, notizen: editNotizen || undefined, items: editItems, }); }; const addEditItem = () => { setEditItems(prev => [...prev, { bezeichnung: '', menge: 1 }]); }; const removeEditItem = (idx: number) => { setEditItems(prev => prev.filter((_, i) => i !== idx)); }; const updateEditItem = (idx: number, field: string, value: unknown) => { setEditItems(prev => prev.map((item, i) => i === idx ? { ...item, [field]: value } : item)); }; if (!requestId) return null; const anfrage = detail?.anfrage; const canEdit = anfrage && ( canEditAny || (anfrage.anfrager_id === currentUserId && anfrage.status === 'offen') ); return ( <> Anfrage {anfrage ? formatOrderId(anfrage) : '...'} {anfrage?.bezeichnung && ` — ${anfrage.bezeichnung}`} {anfrage && ( )} {isLoading ? ( Lade Details... ) : !detail ? ( Anfrage nicht gefunden. ) : editing ? ( /* ── Edit Mode ── */ <> setEditBezeichnung(e.target.value)} fullWidth /> setEditNotizen(e.target.value)} multiline rows={2} fullWidth /> Positionen {editItems.map((item, idx) => ( typeof o === 'string' ? o : o.bezeichnung} value={item.artikel_id ? catalogItems.find(c => c.id === item.artikel_id) || item.bezeichnung : item.bezeichnung} onChange={(_, v) => { if (typeof v === 'string') { updateEditItem(idx, 'bezeichnung', v); updateEditItem(idx, 'artikel_id', undefined); } else if (v) { setEditItems(prev => prev.map((it, i) => i === idx ? { ...it, artikel_id: v.id, bezeichnung: v.bezeichnung } : it)); } }} onInputChange={(_, val, reason) => { if (reason === 'input') { setEditItems(prev => prev.map((it, i) => i === idx ? { ...it, bezeichnung: val, artikel_id: undefined } : it)); } }} renderInput={params => } sx={{ flexGrow: 1 }} /> updateEditItem(idx, 'menge', Math.max(1, Number(e.target.value)))} sx={{ width: 90 }} inputProps={{ min: 1 }} /> removeEditItem(idx)}> ))} ) : ( /* ── View Mode ── */ <> {anfrage!.anfrager_name && ( Anfrager: {anfrage!.anfrager_name} )} {anfrage!.notizen && ( Notizen: {anfrage!.notizen} )} {anfrage!.admin_notizen && ( Admin Notizen: {anfrage!.admin_notizen} )} Erstellt am: {new Date(anfrage!.erstellt_am).toLocaleDateString('de-AT')} Positionen {detail.positionen.map(p => ( - {p.menge}x {p.bezeichnung}{p.notizen ? ` (${p.notizen})` : ''} {p.eigenschaften && p.eigenschaften.length > 0 && ( {p.eigenschaften.map(e => ( {e.eigenschaft_name}: {e.wert} ))} )} ))} {detail.linked_bestellungen && detail.linked_bestellungen.length > 0 && ( <> Verknüpfte Bestellungen {detail.linked_bestellungen.map(b => ( ))} )} )} {editing ? ( <> ) : ( <> {/* Admin actions */} {showAdminActions && anfrage && anfrage.status === 'offen' && ( <> )} {showAdminActions && anfrage && hasPermission('ausruestungsanfrage:approve') && ( Status ändern )} {showAdminActions && anfrage && anfrage.status === 'genehmigt' && hasPermission('ausruestungsanfrage:link_orders') && ( )} {(showEditButton || canEditAny) && canEdit && !editing && ( )} )} {/* Approve/Reject sub-dialog */} setActionDialog(null)} maxWidth="sm" fullWidth> {actionDialog?.action === 'genehmigt' ? 'Anfrage genehmigen' : 'Anfrage ablehnen'} setAdminNotizen(e.target.value)} multiline rows={2} /> {/* Link to order sub-dialog */} { setLinkDialog(false); setSelectedBestellung(null); }} maxWidth="sm" fullWidth> Mit Bestellung verknüpfen `#${o.id} – ${o.bezeichnung}`} value={selectedBestellung} onChange={(_, v) => setSelectedBestellung(v)} renderInput={params => } /> ); } // ─── Catalog Tab ──────────────────────────────────────────────────────────── function KatalogTab() { const { showSuccess, showError } = useNotification(); const { hasPermission } = usePermissionContext(); const queryClient = useQueryClient(); const canManage = hasPermission('ausruestungsanfrage:manage_catalog'); const canManageCategories = hasPermission('ausruestungsanfrage:manage_categories'); const [filterKategorie, setFilterKategorie] = useState(''); const [artikelDialogOpen, setArtikelDialogOpen] = useState(false); const [editArtikel, setEditArtikel] = useState(null); const [artikelForm, setArtikelForm] = useState({ bezeichnung: '' }); const [kategorieDialogOpen, setKategorieDialogOpen] = useState(false); const { data: items = [], isLoading } = useQuery({ queryKey: ['ausruestungsanfrage', 'items', filterKategorie], queryFn: () => ausruestungsanfrageApi.getItems(filterKategorie ? { kategorie_id: filterKategorie as number } : undefined), }); const { data: kategorien = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'kategorien'], queryFn: () => ausruestungsanfrageApi.getKategorien(), }); const createItemMut = useMutation({ mutationFn: (data: AusruestungArtikelFormData) => ausruestungsanfrageApi.createItem(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Artikel erstellt'); setArtikelDialogOpen(false); }, onError: () => showError('Fehler beim Erstellen'), }); const updateItemMut = useMutation({ mutationFn: ({ id, data }: { id: number; data: Partial }) => ausruestungsanfrageApi.updateItem(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Artikel aktualisiert'); setArtikelDialogOpen(false); }, onError: () => showError('Fehler beim Aktualisieren'), }); const deleteItemMut = useMutation({ mutationFn: (id: number) => ausruestungsanfrageApi.deleteItem(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Artikel gelöscht'); }, onError: () => showError('Fehler beim Löschen'), }); const openNewArtikel = () => { setEditArtikel(null); setArtikelForm({ bezeichnung: '' }); setArtikelDialogOpen(true); }; const openEditArtikel = (a: AusruestungArtikel) => { setEditArtikel(a); setArtikelForm({ bezeichnung: a.bezeichnung, beschreibung: a.beschreibung, kategorie_id: a.kategorie_id ?? null }); setArtikelDialogOpen(true); }; const saveArtikel = () => { if (!artikelForm.bezeichnung.trim()) return; if (editArtikel) updateItemMut.mutate({ id: editArtikel.id, data: artikelForm }); else createItemMut.mutate(artikelForm); }; return ( {/* Filter */} Kategorie {canManageCategories && ( setKategorieDialogOpen(true)}> )} {/* Catalog table */} {isLoading ? ( Lade Katalog... ) : items.length === 0 ? ( Keine Artikel vorhanden. ) : ( Bezeichnung Kategorie Beschreibung {canManage && Aktionen} {items.map(item => ( {item.bezeichnung} {(item.eigenschaften_count ?? 0) > 0 && ( )} {item.kategorie_name || item.kategorie || '-'} {item.beschreibung || '-'} {canManage && ( openEditArtikel(item)}> deleteItemMut.mutate(item.id)}> )} ))}
)} {/* Artikel create/edit dialog */} setArtikelDialogOpen(false)} maxWidth="sm" fullWidth> {editArtikel ? 'Artikel bearbeiten' : 'Neuer Artikel'} setArtikelForm(f => ({ ...f, bezeichnung: e.target.value }))} fullWidth /> setArtikelForm(f => ({ ...f, beschreibung: e.target.value }))} /> Kategorie {canManage && } {/* Kategorie management dialog */} setKategorieDialogOpen(false)} /> {/* FAB for new catalog item */} {canManage && ( )}
); } // ─── My Requests Tab ──────────────────────────────────────────────────────── function MeineAnfragenTab() { const { hasPermission } = usePermissionContext(); const { user } = useAuth(); const queryClient = useQueryClient(); const { showSuccess, showError } = useNotification(); const canCreate = hasPermission('ausruestungsanfrage:create_request'); const canOrderForUser = hasPermission('ausruestungsanfrage:order_for_user'); const canEditAny = hasPermission('ausruestungsanfrage:edit'); const [detailId, setDetailId] = useState(null); const [statusFilter, setStatusFilter] = useState(ACTIVE_STATUSES); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [newBezeichnung, setNewBezeichnung] = useState(''); const [newNotizen, setNewNotizen] = useState(''); const [newFuerBenutzer, setNewFuerBenutzer] = useState<{ id: string; name: string } | null>(null); const [newItems, setNewItems] = useState([{ bezeichnung: '', menge: 1 }]); // Track loaded eigenschaften per item row (by artikel_id) const [itemEigenschaften, setItemEigenschaften] = useState>({}); // Track eigenschaft values per item row index const [itemEigenschaftValues, setItemEigenschaftValues] = useState>>({}); const { data: requests = [], isLoading } = useQuery({ queryKey: ['ausruestungsanfrage', 'myRequests'], queryFn: () => ausruestungsanfrageApi.getMyRequests(), }); const { data: catalogItems = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'items-for-create'], queryFn: () => ausruestungsanfrageApi.getItems({ aktiv: true }), enabled: createDialogOpen, }); const { data: orderUsers = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'orderUsers'], queryFn: () => ausruestungsanfrageApi.getOrderUsers(), enabled: createDialogOpen && canOrderForUser, }); const createMut = useMutation({ mutationFn: (args: { items: AusruestungAnfrageFormItem[]; notizen?: string; bezeichnung?: string; fuer_benutzer_id?: string }) => ausruestungsanfrageApi.createRequest(args.items, args.notizen, args.bezeichnung, args.fuer_benutzer_id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Anfrage erstellt'); setCreateDialogOpen(false); resetCreateForm(); }, onError: () => showError('Fehler beim Erstellen der Anfrage'), }); const resetCreateForm = () => { setNewBezeichnung(''); setNewNotizen(''); setNewFuerBenutzer(null); setNewItems([{ bezeichnung: '', menge: 1 }]); setItemEigenschaften({}); setItemEigenschaftValues({}); }; const loadEigenschaften = useCallback(async (artikelId: number) => { if (itemEigenschaften[artikelId]) return; try { const eigs = await ausruestungsanfrageApi.getArtikelEigenschaften(artikelId); setItemEigenschaften(prev => ({ ...prev, [artikelId]: eigs })); } catch { /* ignore */ } }, [itemEigenschaften]); const handleCreateSubmit = () => { const validItems = newItems.filter(i => i.bezeichnung.trim()).map((item, idx) => { const vals = itemEigenschaftValues[idx] || {}; const eigenschaften = Object.entries(vals) .filter(([, wert]) => wert.trim()) .map(([eid, wert]) => ({ eigenschaft_id: Number(eid), wert })); return { ...item, eigenschaften: eigenschaften.length > 0 ? eigenschaften : undefined }; }); if (validItems.length === 0) return; // Check required eigenschaften for (let idx = 0; idx < newItems.length; idx++) { const item = newItems[idx]; if (!item.bezeichnung.trim()) continue; if (item.artikel_id && itemEigenschaften[item.artikel_id]) { for (const e of itemEigenschaften[item.artikel_id]) { if (e.pflicht && !(itemEigenschaftValues[idx]?.[e.id]?.trim())) { showError(`Pflichtfeld "${e.name}" für "${item.bezeichnung}" fehlt`); return; } } } } createMut.mutate({ items: validItems, notizen: newNotizen || undefined, bezeichnung: newBezeichnung || undefined, fuer_benutzer_id: newFuerBenutzer?.id, }); }; const filteredRequests = useMemo(() => { if (statusFilter.length === 0) return requests; return requests.filter(r => statusFilter.includes(r.status)); }, [requests, statusFilter]); const handleStatusFilterChange = (value: string) => { if (value === 'all') { setStatusFilter([]); } else if (value === 'active') { setStatusFilter(ACTIVE_STATUSES); } else { setStatusFilter([value as AusruestungAnfrageStatus]); } }; const currentFilterValue = useMemo(() => { if (statusFilter.length === 0) return 'all'; if (statusFilter.length === ACTIVE_STATUSES.length && ACTIVE_STATUSES.every(s => statusFilter.includes(s))) return 'active'; return statusFilter[0] || 'all'; }, [statusFilter]); if (isLoading) return Lade Anfragen...; return ( Status {filteredRequests.length === 0 ? ( Keine Anfragen vorhanden. ) : ( Anfrage Bezeichnung Status Positionen Erstellt am {filteredRequests.map(r => ( setDetailId(r.id)}> {formatOrderId(r)} {r.bezeichnung || '-'} {r.positionen_count ?? r.items_count ?? '-'} {new Date(r.erstellt_am).toLocaleDateString('de-AT')} ))}
)} {/* Detail Modal */} setDetailId(null)} showEditButton canEditAny={canEditAny} currentUserId={user?.id} /> {/* Create Request Dialog */} { setCreateDialogOpen(false); resetCreateForm(); }} maxWidth="sm" fullWidth> Neue Bestellung setNewBezeichnung(e.target.value)} fullWidth /> {canOrderForUser && ( o.name} value={newFuerBenutzer} onChange={(_, v) => setNewFuerBenutzer(v)} renderInput={params => } /> )} setNewNotizen(e.target.value)} multiline rows={2} fullWidth /> Positionen {newItems.map((item, idx) => ( typeof o === 'string' ? o : o.bezeichnung} value={item.artikel_id ? catalogItems.find(c => c.id === item.artikel_id) || item.bezeichnung : item.bezeichnung} onChange={(_, v) => { if (typeof v === 'string') { setNewItems(prev => prev.map((it, i) => i === idx ? { ...it, bezeichnung: v, artikel_id: undefined } : it)); // Clear eigenschaften for this row setItemEigenschaftValues(prev => { const n = { ...prev }; delete n[idx]; return n; }); } else if (v) { setNewItems(prev => prev.map((it, i) => i === idx ? { ...it, artikel_id: v.id, bezeichnung: v.bezeichnung } : it)); loadEigenschaften(v.id); } }} onInputChange={(_, val, reason) => { if (reason === 'input') { setNewItems(prev => prev.map((it, i) => i === idx ? { ...it, bezeichnung: val, artikel_id: undefined } : it)); } }} renderInput={params => } sx={{ flexGrow: 1 }} /> setNewItems(prev => prev.map((it, i) => i === idx ? { ...it, menge: Math.max(1, Number(e.target.value)) } : it))} sx={{ width: 90 }} inputProps={{ min: 1 }} /> setNewItems(prev => prev.filter((_, i) => i !== idx))} disabled={newItems.length <= 1}> {/* Eigenschaft fields for this item */} {item.artikel_id && itemEigenschaften[item.artikel_id] && itemEigenschaften[item.artikel_id].length > 0 && ( setItemEigenschaftValues(prev => ({ ...prev, [idx]: { ...(prev[idx] || {}), [eid]: wert }, }))} /> )} ))} {/* FAB for creating new request */} {canCreate && ( setCreateDialogOpen(true)} aria-label="Neue Anfrage erstellen"> )}
); } // ─── Admin All Requests Tab (merged with overview) ────────────────────────── function AlleAnfragenTab() { const { hasPermission } = usePermissionContext(); const { user } = useAuth(); const [statusFilter, setStatusFilter] = useState(''); const [detailId, setDetailId] = useState(null); const canEditAny = hasPermission('ausruestungsanfrage:edit'); const { data: requests = [], isLoading } = useQuery({ queryKey: ['ausruestungsanfrage', 'requests', statusFilter], queryFn: () => ausruestungsanfrageApi.getRequests(statusFilter ? { status: statusFilter } : undefined), }); const { data: overview } = useQuery({ queryKey: ['ausruestungsanfrage', 'overview'], queryFn: () => ausruestungsanfrageApi.getOverview(), }); if (isLoading) return Lade Anfragen...; return ( {/* Summary cards */} {overview?.pending_count ?? '-'} Offene {overview?.approved_count ?? '-'} Genehmigte {overview?.unhandled_count ?? '-'} Neue (unbearbeitet) {overview?.total_items ?? '-'} Gesamt Artikel Status Filter {requests.length === 0 ? ( Keine Anfragen vorhanden. ) : ( Anfrage Bezeichnung Anfrager Status Positionen Erstellt am {requests.map(r => ( setDetailId(r.id)}> {formatOrderId(r)} {r.bezeichnung || '-'} {r.anfrager_name || r.anfrager_id} {r.positionen_count ?? r.items_count ?? '-'} {new Date(r.erstellt_am).toLocaleDateString('de-AT')} ))}
)} {/* Detail Modal with admin actions */} setDetailId(null)} showAdminActions showEditButton canEditAny={canEditAny} currentUserId={user?.id} />
); } // ─── Main Page ────────────────────────────────────────────────────────────── export default function Ausruestungsanfrage() { const [searchParams, setSearchParams] = useSearchParams(); const { hasPermission } = usePermissionContext(); const canView = hasPermission('ausruestungsanfrage:view'); const canCreate = hasPermission('ausruestungsanfrage:create_request'); const canApprove = hasPermission('ausruestungsanfrage:approve'); const tabCount = 1 + (canCreate ? 1 : 0) + (canApprove ? 1 : 0); const [activeTab, setActiveTab] = useState(() => { const t = Number(searchParams.get('tab')); return t >= 0 && t < tabCount ? t : 0; }); // Sync tab from URL changes (e.g. sidebar navigation) useEffect(() => { const t = Number(searchParams.get('tab')); if (t >= 0 && t < tabCount) setActiveTab(t); }, [searchParams, tabCount]); const handleTabChange = (_: React.SyntheticEvent, val: number) => { setActiveTab(val); setSearchParams({ tab: String(val) }, { replace: true }); }; const tabIndex = useMemo(() => { const map: Record = {}; let next = 0; if (canCreate) { map.meine = next; next++; } if (canApprove) { map.alle = next; next++; } map.katalog = next; return map; }, [canCreate, canApprove]); if (!canView) { return ( Keine Berechtigung. ); } return ( Interne Bestellungen {canCreate && } {canApprove && } {canCreate && activeTab === tabIndex.meine && } {canApprove && activeTab === tabIndex.alle && } {activeTab === tabIndex.katalog && } ); }