refactor: extract shared KatalogTab component, use it in both Bestellungen and Ausruestungsanfrage

This commit is contained in:
Matthias Hochmeister
2026-03-27 15:05:10 +01:00
parent 3f55990212
commit ae3f0c825b
3 changed files with 318 additions and 396 deletions

View File

@@ -26,11 +26,12 @@ import {
TextField,
MenuItem,
} from '@mui/material';
import { Add as AddIcon, ExpandMore as ExpandMoreIcon, FilterList as FilterListIcon, PictureAsPdf as PdfIcon, Search as SearchIcon } from '@mui/icons-material';
import { Add as AddIcon, ExpandMore as ExpandMoreIcon, FilterList as FilterListIcon, PictureAsPdf as PdfIcon } from '@mui/icons-material';
import { useQuery } from '@tanstack/react-query';
import { useNavigate, useSearchParams } from 'react-router-dom';
import DashboardLayout from '../components/dashboard/DashboardLayout';
import ChatAwareFab from '../components/shared/ChatAwareFab';
import { KatalogTab } from '../components/shared/KatalogTab';
import { usePermissionContext } from '../contexts/PermissionContext';
import { bestellungApi } from '../services/bestellung';
import { configApi } from '../services/config';
@@ -87,7 +88,6 @@ export default function Bestellungen() {
const { hasPermission } = usePermissionContext();
const canManageVendors = hasPermission('bestellungen:manage_vendors');
const canExport = hasPermission('bestellungen:export');
const canManageCatalog = hasPermission('ausruestungsanfrage:manage_catalog');
// Tab from URL
const [tab, setTab] = useState(() => {
@@ -116,26 +116,6 @@ export default function Bestellungen() {
queryFn: bestellungApi.getVendors,
});
// ── Katalog state ──
const [katalogSearch, setKatalogSearch] = useState('');
const [katalogKategorie, setKatalogKategorie] = useState('');
const { data: katalogItems = [], isLoading: katalogLoading } = useQuery({
queryKey: ['katalogItems', katalogSearch, katalogKategorie],
queryFn: () => bestellungApi.getKatalogItems({
search: katalogSearch || undefined,
kategorie: katalogKategorie || undefined,
}),
enabled: tab === 2,
});
const { data: katalogKategorien = [] } = useQuery({
queryKey: ['katalogKategorien'],
queryFn: bestellungApi.getKatalogKategorien,
enabled: tab === 2,
staleTime: 5 * 60 * 1000,
});
// ── Derive unique filter values from data ──
const uniqueVendors = useMemo(() => {
const map = new Map<string, string>();
@@ -485,78 +465,7 @@ export default function Bestellungen() {
{/* ── Tab 2: Katalog ── */}
<TabPanel value={tab} index={canManageVendors ? 2 : 1}>
<Box sx={{ display: 'flex', gap: 2, mb: 3 }}>
<TextField
size="small"
placeholder="Suche..."
value={katalogSearch}
onChange={(e) => setKatalogSearch(e.target.value)}
InputProps={{ startAdornment: <SearchIcon fontSize="small" sx={{ mr: 0.5, color: 'text.secondary' }} /> }}
sx={{ flex: 1, maxWidth: 400 }}
/>
<TextField
select
size="small"
label="Kategorie"
value={katalogKategorie}
onChange={(e) => setKatalogKategorie(e.target.value)}
sx={{ minWidth: 180 }}
>
<MenuItem value="">Alle Kategorien</MenuItem>
{katalogKategorien.map((k) => (
<MenuItem key={k} value={k}>{k}</MenuItem>
))}
</TextField>
</Box>
<TableContainer component={Paper}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Bezeichnung</TableCell>
<TableCell>Kategorie</TableCell>
<TableCell align="right">Geschätzter Preis</TableCell>
<TableCell>Bevorzugter Lieferant</TableCell>
<TableCell align="right">Eigenschaften</TableCell>
</TableRow>
</TableHead>
<TableBody>
{katalogLoading ? (
<TableRow><TableCell colSpan={5} align="center">Laden...</TableCell></TableRow>
) : katalogItems.length === 0 ? (
<TableRow><TableCell colSpan={5} align="center">Keine Artikel gefunden</TableCell></TableRow>
) : (
katalogItems.map((item) => (
<TableRow
key={item.id}
hover
sx={{ cursor: 'pointer' }}
onClick={() => navigate(`/ausruestungsanfrage/artikel/${item.id}`)}
>
<TableCell>
<Typography variant="body2" fontWeight={500}>{item.bezeichnung}</Typography>
{item.beschreibung && (
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', maxWidth: 300, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{item.beschreibung}
</Typography>
)}
</TableCell>
<TableCell>{item.kategorie_name || item.kategorie || ''}</TableCell>
<TableCell align="right">{item.geschaetzter_preis != null ? formatCurrency(item.geschaetzter_preis) : ''}</TableCell>
<TableCell>{item.bevorzugter_lieferant_name || ''}</TableCell>
<TableCell align="right">{item.eigenschaften_count ?? 0}</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
{canManageCatalog && (
<ChatAwareFab onClick={() => navigate('/ausruestungsanfrage/artikel/neu')} aria-label="Neuer Katalogartikel">
<AddIcon />
</ChatAwareFab>
)}
<KatalogTab />
</TabPanel>
</DashboardLayout>
);