shared catalog in Bestellungen, catalog picker in line items, Ersatzbeschaffung flag, vendor detail flash fix
This commit is contained in:
@@ -23,8 +23,10 @@ import {
|
||||
FormGroup,
|
||||
LinearProgress,
|
||||
Divider,
|
||||
TextField,
|
||||
MenuItem,
|
||||
} from '@mui/material';
|
||||
import { Add as AddIcon, ExpandMore as ExpandMoreIcon, FilterList as FilterListIcon, PictureAsPdf as PdfIcon } from '@mui/icons-material';
|
||||
import { Add as AddIcon, ExpandMore as ExpandMoreIcon, FilterList as FilterListIcon, PictureAsPdf as PdfIcon, Search as SearchIcon } from '@mui/icons-material';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
@@ -52,7 +54,7 @@ function TabPanel({ children, value, index }: TabPanelProps) {
|
||||
return <Box sx={{ pt: 3 }}>{children}</Box>;
|
||||
}
|
||||
|
||||
const TAB_COUNT = 2;
|
||||
const TAB_COUNT = 3;
|
||||
|
||||
// ── Status options ──
|
||||
|
||||
@@ -85,6 +87,7 @@ 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(() => {
|
||||
@@ -113,6 +116,26 @@ 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>();
|
||||
@@ -251,6 +274,7 @@ export default function Bestellungen() {
|
||||
<Tabs value={tab} onChange={(_e, v) => { setTab(v); navigate(`/bestellungen?tab=${v}`, { replace: true }); }} variant="scrollable" scrollButtons="auto">
|
||||
<Tab label="Bestellungen" />
|
||||
{canManageVendors && <Tab label="Lieferanten" />}
|
||||
<Tab label="Katalog" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
@@ -458,6 +482,82 @@ export default function Bestellungen() {
|
||||
</ChatAwareFab>
|
||||
</TabPanel>
|
||||
)}
|
||||
|
||||
{/* ── 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>
|
||||
)}
|
||||
</TabPanel>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user