545 lines
23 KiB
TypeScript
545 lines
23 KiB
TypeScript
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, Divider, Tooltip,
|
|
} 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 } from '@tanstack/react-query';
|
|
import { useSearchParams, useNavigate } 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 { ausruestungsanfrageApi } from '../services/ausruestungsanfrage';
|
|
import { AUSRUESTUNG_STATUS_LABELS, AUSRUESTUNG_STATUS_COLORS } from '../types/ausruestungsanfrage.types';
|
|
import type {
|
|
AusruestungAnfrageStatus, AusruestungAnfrage,
|
|
AusruestungOverview,
|
|
} from '../types/ausruestungsanfrage.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'];
|
|
|
|
// ─── 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 [newParentId, setNewParentId] = useState<number | null>(null);
|
|
const [editId, setEditId] = useState<number | null>(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 (
|
|
<Box key={k.id}>
|
|
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', ml: indent * 3 }}>
|
|
{editId === k.id ? (
|
|
<>
|
|
<TextField
|
|
size="small"
|
|
value={editName}
|
|
onChange={e => setEditName(e.target.value)}
|
|
sx={{ flexGrow: 1 }}
|
|
onKeyDown={e => { if (e.key === 'Enter' && editName.trim()) updateMut.mutate({ id: k.id, name: editName.trim() }); }}
|
|
/>
|
|
<IconButton size="small" onClick={() => { if (editName.trim()) updateMut.mutate({ id: k.id, name: editName.trim() }); }}><CheckIcon fontSize="small" /></IconButton>
|
|
<IconButton size="small" onClick={() => setEditId(null)}><CloseIcon fontSize="small" /></IconButton>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Typography variant={indent === 0 ? 'body1' : 'body2'} sx={{ flexGrow: 1, fontWeight: indent === 0 ? 600 : 400 }}>
|
|
{indent > 0 && '└ '}{k.name}
|
|
</Typography>
|
|
<Tooltip title="Unterkategorie hinzufügen">
|
|
<IconButton size="small" onClick={() => setNewParentId(k.id)}><AddIcon fontSize="small" /></IconButton>
|
|
</Tooltip>
|
|
<IconButton size="small" onClick={() => { setEditId(k.id); setEditName(k.name); }}><EditIcon fontSize="small" /></IconButton>
|
|
<IconButton size="small" color="error" onClick={() => deleteMut.mutate(k.id)}><DeleteIcon fontSize="small" /></IconButton>
|
|
</>
|
|
)}
|
|
</Box>
|
|
{children.map(c => renderKategorie(c, indent + 1))}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
|
<DialogTitle>Kategorien verwalten</DialogTitle>
|
|
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: '20px !important' }}>
|
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
<TextField
|
|
size="small"
|
|
label={newParentId ? 'Neue Unterkategorie' : 'Neue Kategorie'}
|
|
value={newName}
|
|
onChange={e => setNewName(e.target.value)}
|
|
sx={{ flexGrow: 1 }}
|
|
onKeyDown={e => { if (e.key === 'Enter' && newName.trim()) createMut.mutate({ name: newName.trim(), parentId: newParentId }); }}
|
|
/>
|
|
{newParentId && (
|
|
<Chip
|
|
label={`Unter: ${kategorien.find(k => k.id === newParentId)?.name}`}
|
|
size="small"
|
|
onDelete={() => setNewParentId(null)}
|
|
/>
|
|
)}
|
|
<Button
|
|
variant="contained"
|
|
size="small"
|
|
onClick={() => { if (newName.trim()) createMut.mutate({ name: newName.trim(), parentId: newParentId }); }}
|
|
disabled={!newName.trim() || createMut.isPending}
|
|
>
|
|
Erstellen
|
|
</Button>
|
|
</Box>
|
|
<Divider />
|
|
{topLevel.length === 0 ? (
|
|
<Typography color="text.secondary">Keine Kategorien vorhanden.</Typography>
|
|
) : (
|
|
topLevel.map(k => renderKategorie(k, 0))
|
|
)}
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={onClose}>Schließen</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
// ─── Catalog Tab ────────────────────────────────────────────────────────────
|
|
|
|
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<number | ''>('');
|
|
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 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 geloescht'); },
|
|
onError: () => showError('Fehler beim Loeschen'),
|
|
});
|
|
|
|
return (
|
|
<Box>
|
|
<Box sx={{ display: 'flex', gap: 2, mb: 3, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
<TextField
|
|
select
|
|
size="small"
|
|
label="Kategorie"
|
|
value={filterKategorie}
|
|
onChange={e => setFilterKategorie(e.target.value as number | '')}
|
|
sx={{ minWidth: 200 }}
|
|
>
|
|
<MenuItem value="">Alle</MenuItem>
|
|
{kategorieOptions.map(k => <MenuItem key={k.id} value={k.id}>{k.name}</MenuItem>)}
|
|
</TextField>
|
|
{canManageCategories && (
|
|
<Tooltip title="Kategorien verwalten">
|
|
<IconButton size="small" onClick={() => setKategorieDialogOpen(true)}>
|
|
<SettingsIcon fontSize="small" />
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
</Box>
|
|
|
|
{isLoading ? (
|
|
<Typography color="text.secondary">Lade Katalog...</Typography>
|
|
) : items.length === 0 ? (
|
|
<Typography color="text.secondary">Keine Artikel vorhanden.</Typography>
|
|
) : (
|
|
<TableContainer component={Paper} variant="outlined">
|
|
<Table size="small">
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>Bezeichnung</TableCell>
|
|
<TableCell>Kategorie</TableCell>
|
|
<TableCell>Beschreibung</TableCell>
|
|
{canManage && <TableCell align="right">Aktionen</TableCell>}
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{items.map(item => (
|
|
<TableRow
|
|
key={item.id}
|
|
hover
|
|
sx={{ cursor: 'pointer' }}
|
|
onClick={() => navigate(`/ausruestungsanfrage/artikel/${item.id}`)}
|
|
>
|
|
<TableCell>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
{item.bezeichnung}
|
|
{(item.eigenschaften_count ?? 0) > 0 && (
|
|
<Chip label={`${item.eigenschaften_count} Eig.`} size="small" variant="outlined" />
|
|
)}
|
|
</Box>
|
|
</TableCell>
|
|
<TableCell>{kategorieOptions.find(k => k.id === item.kategorie_id)?.name || item.kategorie_name || item.kategorie || '-'}</TableCell>
|
|
<TableCell sx={{ maxWidth: 300, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
{item.beschreibung || '-'}
|
|
</TableCell>
|
|
{canManage && (
|
|
<TableCell align="right">
|
|
<IconButton size="small" color="error" onClick={(e) => { e.stopPropagation(); deleteItemMut.mutate(item.id); }}><DeleteIcon fontSize="small" /></IconButton>
|
|
</TableCell>
|
|
)}
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
)}
|
|
|
|
<KategorieDialog open={kategorieDialogOpen} onClose={() => setKategorieDialogOpen(false)} />
|
|
|
|
{canManage && (
|
|
<ChatAwareFab onClick={() => navigate('/ausruestungsanfrage/artikel/neu')} aria-label="Artikel hinzufuegen">
|
|
<AddIcon />
|
|
</ChatAwareFab>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
// ─── My Requests Tab ────────────────────────────────────────────────────────
|
|
|
|
function MeineAnfragenTab() {
|
|
const { hasPermission } = usePermissionContext();
|
|
const navigate = useNavigate();
|
|
|
|
const canCreate = hasPermission('ausruestungsanfrage:create_request');
|
|
|
|
const [statusFilter, setStatusFilter] = useState<AusruestungAnfrageStatus[]>(ACTIVE_STATUSES);
|
|
|
|
const { data: requests = [], isLoading } = useQuery({
|
|
queryKey: ['ausruestungsanfrage', 'myRequests'],
|
|
queryFn: () => ausruestungsanfrageApi.getMyRequests(),
|
|
});
|
|
|
|
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 <Typography color="text.secondary">Lade Anfragen...</Typography>;
|
|
|
|
return (
|
|
<Box>
|
|
<Box sx={{ display: 'flex', gap: 2, mb: 2, alignItems: 'center' }}>
|
|
<TextField
|
|
select
|
|
size="small"
|
|
label="Status"
|
|
value={currentFilterValue}
|
|
onChange={e => handleStatusFilterChange(e.target.value)}
|
|
sx={{ minWidth: 200 }}
|
|
>
|
|
<MenuItem value="active">Aktive Anfragen</MenuItem>
|
|
<MenuItem value="all">Alle</MenuItem>
|
|
{(Object.keys(AUSRUESTUNG_STATUS_LABELS) as AusruestungAnfrageStatus[]).map(s => (
|
|
<MenuItem key={s} value={s}>{AUSRUESTUNG_STATUS_LABELS[s]}</MenuItem>
|
|
))}
|
|
</TextField>
|
|
</Box>
|
|
|
|
{filteredRequests.length === 0 ? (
|
|
<Typography color="text.secondary" sx={{ mb: 2 }}>Keine Anfragen vorhanden.</Typography>
|
|
) : (
|
|
<TableContainer component={Paper} variant="outlined">
|
|
<Table size="small">
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>Anfrage ID</TableCell>
|
|
<TableCell>Bezeichnung</TableCell>
|
|
<TableCell>Status</TableCell>
|
|
<TableCell>Positionen</TableCell>
|
|
<TableCell>Geliefert</TableCell>
|
|
<TableCell>Erstellt am</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{filteredRequests.map(r => (
|
|
<TableRow key={r.id} hover sx={{ cursor: 'pointer' }} onClick={() => navigate('/ausruestungsanfrage/' + r.id)}>
|
|
<TableCell>{formatOrderId(r)}</TableCell>
|
|
<TableCell>{r.bezeichnung || '-'}</TableCell>
|
|
<TableCell><Chip label={AUSRUESTUNG_STATUS_LABELS[r.status]} color={AUSRUESTUNG_STATUS_COLORS[r.status]} size="small" /></TableCell>
|
|
<TableCell>{r.positionen_count ?? r.items_count ?? '-'}</TableCell>
|
|
<TableCell>
|
|
{r.positionen_count != null && r.positionen_count > 0
|
|
? `${r.geliefert_count ?? 0}/${r.positionen_count}`
|
|
: '-'}
|
|
</TableCell>
|
|
<TableCell>{new Date(r.erstellt_am).toLocaleDateString('de-AT')}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
)}
|
|
|
|
{canCreate && (
|
|
<ChatAwareFab onClick={() => navigate('/ausruestungsanfrage/neu')} aria-label="Neue Anfrage erstellen">
|
|
<AddIcon />
|
|
</ChatAwareFab>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
// ─── Admin All Requests Tab (merged with overview) ──────────────────────────
|
|
|
|
function AlleAnfragenTab() {
|
|
const navigate = useNavigate();
|
|
const [statusFilter, setStatusFilter] = useState<string>('alle');
|
|
|
|
const { data: requests = [], isLoading: requestsLoading, isError: requestsError } = useQuery({
|
|
queryKey: ['ausruestungsanfrage', 'allRequests', statusFilter],
|
|
queryFn: () => ausruestungsanfrageApi.getRequests(statusFilter !== 'alle' ? { status: statusFilter } : undefined),
|
|
});
|
|
|
|
const { data: overview } = useQuery<AusruestungOverview>({
|
|
queryKey: ['ausruestungsanfrage', 'overview-cards'],
|
|
queryFn: () => ausruestungsanfrageApi.getOverview(),
|
|
});
|
|
|
|
return (
|
|
<Box>
|
|
<Grid container spacing={2} sx={{ mb: 3 }}>
|
|
<Grid item xs={6} sm={3}>
|
|
<Paper variant="outlined" sx={{ p: 2, textAlign: 'center' }}>
|
|
<Typography variant="h4" fontWeight={700}>{overview?.pending_count ?? '-'}</Typography>
|
|
<Typography variant="body2" color="text.secondary">Offene</Typography>
|
|
</Paper>
|
|
</Grid>
|
|
<Grid item xs={6} sm={3}>
|
|
<Paper variant="outlined" sx={{ p: 2, textAlign: 'center' }}>
|
|
<Typography variant="h4" fontWeight={700}>{overview?.approved_count ?? '-'}</Typography>
|
|
<Typography variant="body2" color="text.secondary">Genehmigte</Typography>
|
|
</Paper>
|
|
</Grid>
|
|
<Grid item xs={6} sm={3}>
|
|
<Paper variant="outlined" sx={{ p: 2, textAlign: 'center' }}>
|
|
<Typography variant="h4" fontWeight={700} color="warning.main">{overview?.unhandled_count ?? '-'}</Typography>
|
|
<Typography variant="body2" color="text.secondary">Neue (unbearbeitet)</Typography>
|
|
</Paper>
|
|
</Grid>
|
|
<Grid item xs={6} sm={3}>
|
|
<Paper variant="outlined" sx={{ p: 2, textAlign: 'center' }}>
|
|
<Typography variant="h4" fontWeight={700}>{overview?.total_items ?? '-'}</Typography>
|
|
<Typography variant="body2" color="text.secondary">Gesamt Artikel</Typography>
|
|
</Paper>
|
|
</Grid>
|
|
</Grid>
|
|
|
|
<TextField
|
|
select
|
|
size="small"
|
|
label="Status Filter"
|
|
value={statusFilter}
|
|
onChange={e => setStatusFilter(e.target.value)}
|
|
sx={{ minWidth: 200, mb: 2 }}
|
|
>
|
|
<MenuItem value="alle">Alle</MenuItem>
|
|
{(Object.keys(AUSRUESTUNG_STATUS_LABELS) as AusruestungAnfrageStatus[]).map(s => (
|
|
<MenuItem key={s} value={s}>{AUSRUESTUNG_STATUS_LABELS[s]}</MenuItem>
|
|
))}
|
|
</TextField>
|
|
|
|
{requestsLoading ? (
|
|
<Typography color="text.secondary">Lade Anfragen...</Typography>
|
|
) : requestsError ? (
|
|
<Typography color="error">Fehler beim Laden der Anfragen.</Typography>
|
|
) : requests.length === 0 ? (
|
|
<Typography color="text.secondary">Keine Anfragen vorhanden.</Typography>
|
|
) : (
|
|
<TableContainer component={Paper} variant="outlined">
|
|
<Table size="small">
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>Anfrage ID</TableCell>
|
|
<TableCell>Bezeichnung</TableCell>
|
|
<TableCell>Anfrage für</TableCell>
|
|
<TableCell>Status</TableCell>
|
|
<TableCell>Positionen</TableCell>
|
|
<TableCell>Geliefert</TableCell>
|
|
<TableCell>Erstellt am</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{requests.map(r => (
|
|
<TableRow key={r.id} hover sx={{ cursor: 'pointer' }} onClick={() => navigate('/ausruestungsanfrage/' + r.id)}>
|
|
<TableCell>{formatOrderId(r)}</TableCell>
|
|
<TableCell>{r.bezeichnung || '-'}</TableCell>
|
|
<TableCell>{r.fuer_benutzer_name || r.anfrager_name || r.anfrager_id}</TableCell>
|
|
<TableCell><Chip label={AUSRUESTUNG_STATUS_LABELS[r.status]} color={AUSRUESTUNG_STATUS_COLORS[r.status]} size="small" /></TableCell>
|
|
<TableCell>{r.positionen_count ?? r.items_count ?? '-'}</TableCell>
|
|
<TableCell>
|
|
{r.positionen_count != null && r.positionen_count > 0
|
|
? `${r.geliefert_count ?? 0}/${r.positionen_count}`
|
|
: '-'}
|
|
</TableCell>
|
|
<TableCell>{new Date(r.erstellt_am).toLocaleDateString('de-AT')}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
// ─── 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 = (canCreate ? 1 : 0) + (canApprove ? 1 : 0) + (canView ? 1 : 0);
|
|
|
|
const [activeTab, setActiveTab] = useState(() => {
|
|
const t = Number(searchParams.get('tab'));
|
|
return t >= 0 && t < tabCount ? t : 0;
|
|
});
|
|
|
|
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<string, number> = {};
|
|
let next = 0;
|
|
if (canCreate) { map.meine = next; next++; }
|
|
if (canApprove) { map.alle = next; next++; }
|
|
if (canView) { map.katalog = next; next++; }
|
|
return map;
|
|
}, [canCreate, canApprove, canView]);
|
|
|
|
if (!canView && !canCreate && !canApprove) {
|
|
return (
|
|
<DashboardLayout>
|
|
<Typography>Keine Berechtigung.</Typography>
|
|
</DashboardLayout>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<DashboardLayout>
|
|
<Typography variant="h5" fontWeight={700} sx={{ mb: 2 }}>Interne Bestellungen</Typography>
|
|
|
|
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
|
|
<Tabs value={activeTab} onChange={handleTabChange} variant="scrollable" scrollButtons="auto">
|
|
{canCreate && <Tab label="Meine Anfragen" />}
|
|
{canApprove && <Tab label="Alle Anfragen" />}
|
|
{canView && <Tab label="Katalog" />}
|
|
</Tabs>
|
|
</Box>
|
|
|
|
{canCreate && activeTab === tabIndex.meine && <MeineAnfragenTab />}
|
|
{canApprove && activeTab === tabIndex.alle && <AlleAnfragenTab />}
|
|
{canView && activeTab === tabIndex.katalog && <KatalogTab />}
|
|
</DashboardLayout>
|
|
);
|
|
}
|