catalog search/sort, edit-page characteristics, preferred vendor per article

This commit is contained in:
Matthias Hochmeister
2026-03-27 13:17:05 +01:00
parent eb82fe29b7
commit 35b3718e38
8 changed files with 277 additions and 29 deletions

View File

@@ -3,7 +3,7 @@ import {
Box, Tab, Tabs, Typography, Grid, Button, Chip,
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper,
Dialog, DialogTitle, DialogContent, DialogActions, TextField, IconButton,
MenuItem, Divider, Tooltip,
MenuItem, Divider, Tooltip, TableSortLabel,
} from '@mui/material';
import {
Add as AddIcon, Delete as DeleteIcon, Edit as EditIcon,
@@ -166,10 +166,16 @@ function KatalogTab() {
const [filterKategorie, setFilterKategorie] = useState<number | ''>('');
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 } = useQuery({
queryKey: ['ausruestungsanfrage', 'items', filterKategorie],
queryFn: () => ausruestungsanfrageApi.getItems(filterKategorie ? { kategorie_id: filterKategorie as number } : undefined),
queryKey: ['ausruestungsanfrage', 'items', filterKategorie, search],
queryFn: () => ausruestungsanfrageApi.getItems({
...(filterKategorie ? { kategorie_id: filterKategorie as number } : {}),
...(search.trim() ? { search: search.trim() } : {}),
}),
});
const { data: kategorien = [] } = useQuery({
@@ -195,9 +201,44 @@ function KatalogTab() {
onError: () => showError('Fehler beim Loeschen'),
});
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 (
<Box>
<Box sx={{ display: 'flex', gap: 2, mb: 3, alignItems: 'center', flexWrap: 'wrap' }}>
<TextField
size="small"
label="Suche"
value={search}
onChange={e => setSearch(e.target.value)}
sx={{ minWidth: 200 }}
placeholder="Artikel suchen..."
/>
<TextField
select
size="small"
@@ -227,14 +268,22 @@ function KatalogTab() {
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Bezeichnung</TableCell>
<TableCell>Kategorie</TableCell>
<TableCell>
<TableSortLabel active={sortField === 'bezeichnung'} direction={sortField === 'bezeichnung' ? sortDir : 'asc'} onClick={() => handleSort('bezeichnung')}>
Bezeichnung
</TableSortLabel>
</TableCell>
<TableCell>
<TableSortLabel active={sortField === 'kategorie'} direction={sortField === 'kategorie' ? sortDir : 'asc'} onClick={() => handleSort('kategorie')}>
Kategorie
</TableSortLabel>
</TableCell>
<TableCell>Beschreibung</TableCell>
{canManage && <TableCell align="right">Aktionen</TableCell>}
</TableRow>
</TableHead>
<TableBody>
{items.map(item => (
{sortedItems.map(item => (
<TableRow
key={item.id}
hover