fix permissions
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
||||
Add as AddIcon, Delete as DeleteIcon, ExpandMore, ExpandLess,
|
||||
BugReport, FiberNew, HelpOutline, Send as SendIcon,
|
||||
Circle as CircleIcon, Edit as EditIcon, Refresh as RefreshIcon,
|
||||
DragIndicator,
|
||||
DragIndicator, Check as CheckIcon, Close as CloseIcon,
|
||||
} from '@mui/icons-material';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
@@ -20,7 +20,7 @@ import { useNotification } from '../contexts/NotificationContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { issuesApi } from '../services/issues';
|
||||
import type { Issue, IssueComment, IssueTyp, CreateIssuePayload, UpdateIssuePayload, IssueFilters, AssignableMember } from '../types/issue.types';
|
||||
import type { Issue, IssueComment, IssueTyp, CreateIssuePayload, UpdateIssuePayload, IssueFilters, AssignableMember, IssueStatusmeldung } from '../types/issue.types';
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
@@ -732,6 +732,156 @@ function IssueTypeAdmin() {
|
||||
);
|
||||
}
|
||||
|
||||
// ── Issue Settings (Statusmeldungen + Kategorien) ──
|
||||
|
||||
function IssueSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
const { showSuccess, showError } = useNotification();
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [createData, setCreateData] = useState<{ titel: string; inhalt: string; schwere: 'info' | 'warnung' | 'fehler' }>({ titel: '', inhalt: '', schwere: 'info' });
|
||||
const [editId, setEditId] = useState<number | null>(null);
|
||||
const [editData, setEditData] = useState<Partial<IssueStatusmeldung>>({});
|
||||
|
||||
const { data: statusmeldungen = [], isLoading: smLoading } = useQuery({
|
||||
queryKey: ['issue-statusmeldungen'],
|
||||
queryFn: issuesApi.getStatusmeldungen,
|
||||
});
|
||||
|
||||
const createSmMut = useMutation({
|
||||
mutationFn: (data: { titel: string; inhalt?: string; schwere?: string }) => issuesApi.createStatusmeldung(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['issue-statusmeldungen'] });
|
||||
showSuccess('Statusmeldung erstellt');
|
||||
setCreateOpen(false);
|
||||
setCreateData({ titel: '', inhalt: '', schwere: 'info' });
|
||||
},
|
||||
onError: () => showError('Fehler beim Erstellen'),
|
||||
});
|
||||
|
||||
const updateSmMut = useMutation({
|
||||
mutationFn: ({ id, data }: { id: number; data: Partial<IssueStatusmeldung> }) => issuesApi.updateStatusmeldung(id, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['issue-statusmeldungen'] });
|
||||
showSuccess('Statusmeldung aktualisiert');
|
||||
setEditId(null);
|
||||
},
|
||||
onError: () => showError('Fehler beim Aktualisieren'),
|
||||
});
|
||||
|
||||
const deleteSmMut = useMutation({
|
||||
mutationFn: (id: number) => issuesApi.deleteStatusmeldung(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['issue-statusmeldungen'] });
|
||||
showSuccess('Statusmeldung gelöscht');
|
||||
},
|
||||
onError: () => showError('Fehler beim Löschen'),
|
||||
});
|
||||
|
||||
const schwereColors: Record<string, 'info' | 'warning' | 'error'> = { info: 'info', warnung: 'warning', fehler: 'error' };
|
||||
const schwereLabels: Record<string, string> = { info: 'Info', warnung: 'Warnung', fehler: 'Fehler' };
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{/* Section 1: Statusmeldungen */}
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||
<Typography variant="h6">Statusmeldungen</Typography>
|
||||
<Button startIcon={<AddIcon />} variant="contained" size="small" onClick={() => setCreateOpen(true)}>
|
||||
Neue Meldung
|
||||
</Button>
|
||||
</Box>
|
||||
{smLoading ? <CircularProgress /> : (
|
||||
<TableContainer component={Paper} variant="outlined">
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Titel</TableCell>
|
||||
<TableCell>Schwere</TableCell>
|
||||
<TableCell>Aktiv</TableCell>
|
||||
<TableCell>Aktionen</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{statusmeldungen.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} sx={{ textAlign: 'center', color: 'text.secondary' }}>
|
||||
Keine Statusmeldungen
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : statusmeldungen.map((sm) => (
|
||||
<TableRow key={sm.id}>
|
||||
{editId === sm.id ? (
|
||||
<>
|
||||
<TableCell>
|
||||
<TextField size="small" value={editData.titel ?? sm.titel} onChange={(e) => setEditData({ ...editData, titel: e.target.value })} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Select size="small" value={editData.schwere ?? sm.schwere} onChange={(e) => setEditData({ ...editData, schwere: e.target.value as 'info' | 'warnung' | 'fehler' })}>
|
||||
<MenuItem value="info">Info</MenuItem>
|
||||
<MenuItem value="warnung">Warnung</MenuItem>
|
||||
<MenuItem value="fehler">Fehler</MenuItem>
|
||||
</Select>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Switch checked={editData.aktiv ?? sm.aktiv} onChange={(e) => setEditData({ ...editData, aktiv: e.target.checked })} size="small" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton size="small" onClick={() => updateSmMut.mutate({ id: sm.id, data: editData })}><CheckIcon /></IconButton>
|
||||
<IconButton size="small" onClick={() => setEditId(null)}><CloseIcon /></IconButton>
|
||||
</TableCell>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TableCell>{sm.titel}</TableCell>
|
||||
<TableCell><Chip label={schwereLabels[sm.schwere]} color={schwereColors[sm.schwere]} size="small" /></TableCell>
|
||||
<TableCell>
|
||||
<Switch checked={sm.aktiv} onChange={(e) => updateSmMut.mutate({ id: sm.id, data: { aktiv: e.target.checked } })} size="small" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton size="small" onClick={() => { setEditId(sm.id); setEditData({ titel: sm.titel, schwere: sm.schwere, aktiv: sm.aktiv, inhalt: sm.inhalt ?? '' }); }}><EditIcon /></IconButton>
|
||||
<IconButton size="small" onClick={() => deleteSmMut.mutate(sm.id)}><DeleteIcon /></IconButton>
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Section 2: Kategorien */}
|
||||
<Box>
|
||||
<IssueTypeAdmin />
|
||||
</Box>
|
||||
|
||||
{/* Create Statusmeldung Dialog */}
|
||||
<Dialog open={createOpen} onClose={() => setCreateOpen(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>Neue Statusmeldung</DialogTitle>
|
||||
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: '20px !important' }}>
|
||||
<TextField label="Titel" required fullWidth value={createData.titel} onChange={(e) => setCreateData({ ...createData, titel: e.target.value })} autoFocus />
|
||||
<TextField label="Inhalt" fullWidth multiline rows={3} value={createData.inhalt} onChange={(e) => setCreateData({ ...createData, inhalt: e.target.value })} />
|
||||
<FormControl fullWidth size="small">
|
||||
<InputLabel>Schwere</InputLabel>
|
||||
<Select label="Schwere" value={createData.schwere} onChange={(e) => setCreateData({ ...createData, schwere: e.target.value as 'info' | 'warnung' | 'fehler' })}>
|
||||
<MenuItem value="info">Info</MenuItem>
|
||||
<MenuItem value="warnung">Warnung</MenuItem>
|
||||
<MenuItem value="fehler">Fehler</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setCreateOpen(false)}>Abbrechen</Button>
|
||||
<Button variant="contained" onClick={() => createSmMut.mutate(createData)} disabled={!createData.titel.trim() || createSmMut.isPending}>
|
||||
Erstellen
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Main Page ──
|
||||
|
||||
export default function Issues() {
|
||||
@@ -756,7 +906,7 @@ export default function Issues() {
|
||||
{ label: 'Zugewiesene Issues', key: 'assigned' },
|
||||
];
|
||||
if (canViewAll) t.push({ label: 'Alle Issues', key: 'all' });
|
||||
if (hasEditSettings) t.push({ label: 'Kategorien', key: 'types' });
|
||||
if (hasEditSettings) t.push({ label: 'Einstellungen', key: 'settings' });
|
||||
return t;
|
||||
}, [canViewAll, hasEditSettings]);
|
||||
|
||||
@@ -868,10 +1018,10 @@ export default function Issues() {
|
||||
</TabPanel>
|
||||
)}
|
||||
|
||||
{/* Tab 3: Kategorien (conditional) */}
|
||||
{/* Tab: Einstellungen (conditional) */}
|
||||
{hasEditSettings && (
|
||||
<TabPanel value={tab} index={tabs.findIndex(t => t.key === 'types')}>
|
||||
<IssueTypeAdmin />
|
||||
<TabPanel value={tab} index={tabs.findIndex(t => t.key === 'settings')}>
|
||||
<IssueSettings />
|
||||
</TabPanel>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user