import { useState, useMemo, useCallback } from 'react'; import { Box, Typography, Paper, Button, TextField, IconButton, Chip, MenuItem, Divider, Tooltip, TableSortLabel, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Dialog, DialogTitle, DialogContent, DialogActions, } from '@mui/material'; import { Add as AddIcon, Delete as DeleteIcon, Edit as EditIcon, Check as CheckIcon, Close as CloseIcon, Settings as SettingsIcon, } from '@mui/icons-material'; import { useQuery, useMutation, useQueryClient, keepPreviousData } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { useNotification } from '../../contexts/NotificationContext'; import { usePermissionContext } from '../../contexts/PermissionContext'; import { ausruestungsanfrageApi } from '../../services/ausruestungsanfrage'; import ChatAwareFab from './ChatAwareFab'; // ─── Category Management Dialog ────────────────────────────────────────────── interface KategorieDialogProps { open: boolean; onClose: () => void; } export function KategorieDialog({ open, onClose }: KategorieDialogProps) { const { showSuccess, showError } = useNotification(); const queryClient = useQueryClient(); const [newName, setNewName] = useState(''); const [newParentId, setNewParentId] = useState(null); const [editId, setEditId] = useState(null); const [editName, setEditName] = useState(''); const { data: kategorien = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'kategorien'], queryFn: () => ausruestungsanfrageApi.getKategorien(), enabled: open, }); const topLevel = useMemo(() => kategorien.filter(k => !k.parent_id), [kategorien]); const childrenOf = useCallback((parentId: number) => kategorien.filter(k => k.parent_id === parentId), [kategorien]); const createMut = useMutation({ mutationFn: ({ name, parentId }: { name: string; parentId?: number | null }) => ausruestungsanfrageApi.createKategorie(name, parentId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ausruestungsanfrage'] }); showSuccess('Kategorie erstellt'); setNewName(''); setNewParentId(null); }, 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'), }); const renderKategorie = (k: { id: number; name: string; parent_id?: number | null }, indent: number) => { const children = childrenOf(k.id); return ( {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)}> ) : ( <> {indent > 0 && '└ '}{k.name} setNewParentId(k.id)}> { setEditId(k.id); setEditName(k.name); }}> deleteMut.mutate(k.id)}> )} {children.map(c => renderKategorie(c, indent + 1))} ); }; return ( Kategorien verwalten setNewName(e.target.value)} sx={{ flexGrow: 1 }} onKeyDown={e => { if (e.key === 'Enter' && newName.trim()) createMut.mutate({ name: newName.trim(), parentId: newParentId }); }} /> {newParentId && ( k.id === newParentId)?.name}`} size="small" onDelete={() => setNewParentId(null)} /> )} {topLevel.length === 0 ? ( Keine Kategorien vorhanden. ) : ( topLevel.map(k => renderKategorie(k, 0)) )} ); } // ─── Catalog Tab ──────────────────────────────────────────────────────────── export function KatalogTab() { const { showSuccess, showError } = useNotification(); const { hasPermission } = usePermissionContext(); const queryClient = useQueryClient(); const navigate = useNavigate(); const canManage = hasPermission('ausruestungsanfrage:manage_catalog'); const canManageCategories = hasPermission('ausruestungsanfrage:manage_categories'); const [filterKategorie, setFilterKategorie] = useState(''); const [kategorieDialogOpen, setKategorieDialogOpen] = useState(false); const [search, setSearch] = useState(''); const [sortField, setSortField] = useState<'bezeichnung' | 'kategorie'>('bezeichnung'); const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc'); const { data: items = [], isLoading, isFetching } = useQuery({ queryKey: ['ausruestungsanfrage', 'items', filterKategorie, search], queryFn: () => ausruestungsanfrageApi.getItems({ ...(filterKategorie ? { kategorie_id: filterKategorie as number } : {}), ...(search.trim() ? { search: search.trim() } : {}), }), placeholderData: keepPreviousData, }); const { data: kategorien = [] } = useQuery({ queryKey: ['ausruestungsanfrage', 'kategorien'], queryFn: () => ausruestungsanfrageApi.getKategorien(), }); const kategorieOptions = useMemo(() => { const map = new Map(kategorien.map(k => [k.id, k])); const getDisplayName = (k: { id: number; name: string; parent_id?: number | null }): string => { if (k.parent_id) { const parent = map.get(k.parent_id); if (parent) return `${parent.name} > ${k.name}`; } return k.name; }; return kategorien.map(k => ({ id: k.id, name: getDisplayName(k), isChild: !!k.parent_id })); }, [kategorien]); 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 handleSort = (field: 'bezeichnung' | 'kategorie') => { if (sortField === field) { setSortDir(prev => prev === 'asc' ? 'desc' : 'asc'); } else { setSortField(field); setSortDir('asc'); } }; const sortedItems = useMemo(() => { const sorted = [...items]; sorted.sort((a, b) => { let aVal: string, bVal: string; if (sortField === 'bezeichnung') { aVal = a.bezeichnung.toLowerCase(); bVal = b.bezeichnung.toLowerCase(); } else { aVal = (kategorieOptions.find(k => k.id === a.kategorie_id)?.name || a.kategorie_name || a.kategorie || '').toLowerCase(); bVal = (kategorieOptions.find(k => k.id === b.kategorie_id)?.name || b.kategorie_name || b.kategorie || '').toLowerCase(); } if (aVal < bVal) return sortDir === 'asc' ? -1 : 1; if (aVal > bVal) return sortDir === 'asc' ? 1 : -1; return 0; }); return sorted; }, [items, sortField, sortDir, kategorieOptions]); return ( setSearch(e.target.value)} sx={{ minWidth: 200 }} placeholder="Artikel suchen..." /> setFilterKategorie(e.target.value as number | '')} sx={{ minWidth: 200 }} > Alle {kategorieOptions.map(k => {k.name})} {canManageCategories && ( setKategorieDialogOpen(true)}> )} {isLoading ? ( Lade Katalog... ) : items.length === 0 && !isFetching ? ( Keine Artikel vorhanden. ) : ( handleSort('bezeichnung')}> Bezeichnung handleSort('kategorie')}> Kategorie Beschreibung {canManage && Aktionen} {sortedItems.map(item => ( navigate(`/ausruestungsanfrage/artikel/${item.id}`)} > {item.bezeichnung} {(item.eigenschaften_count ?? 0) > 0 && ( )} {kategorieOptions.find(k => k.id === item.kategorie_id)?.name || item.kategorie_name || item.kategorie || '-'} {item.beschreibung || '-'} {canManage && ( { e.stopPropagation(); deleteItemMut.mutate(item.id); }}> )} ))}
)} setKategorieDialogOpen(false)} /> {canManage && ( navigate('/ausruestungsanfrage/artikel/neu')} aria-label="Artikel hinzufügen"> )}
); }