feat: add issue kanban/attachments/deadlines, dashboard widget DnD, and checklisten system
This commit is contained in:
@@ -4,13 +4,14 @@ import {
|
||||
TableHead, TableRow, Paper, Chip, IconButton, Button, Dialog, DialogTitle,
|
||||
DialogContent, DialogActions, TextField, MenuItem, Select, FormControl,
|
||||
InputLabel, CircularProgress, FormControlLabel, Switch,
|
||||
Autocomplete,
|
||||
Autocomplete, ToggleButtonGroup, ToggleButton,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Add as AddIcon, Delete as DeleteIcon,
|
||||
BugReport, FiberNew, HelpOutline,
|
||||
Circle as CircleIcon, Edit as EditIcon,
|
||||
DragIndicator, Check as CheckIcon, Close as CloseIcon,
|
||||
ViewList as ViewListIcon, ViewKanban as ViewKanbanIcon,
|
||||
} from '@mui/icons-material';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useSearchParams, useNavigate } from 'react-router-dom';
|
||||
@@ -21,6 +22,7 @@ import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { issuesApi } from '../services/issues';
|
||||
import type { Issue, IssueTyp, IssueFilters, AssignableMember, IssueStatusDef, IssuePriorityDef } from '../types/issue.types';
|
||||
import KanbanBoard from '../components/issues/KanbanBoard';
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
@@ -555,8 +557,10 @@ function IssueSettings() {
|
||||
export default function Issues() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { hasPermission } = usePermissionContext();
|
||||
const { user } = useAuth();
|
||||
const { showSuccess, showError } = useNotification();
|
||||
|
||||
const canViewAll = hasPermission('issues:view_all');
|
||||
const hasEdit = hasPermission('issues:edit');
|
||||
@@ -581,6 +585,27 @@ export default function Issues() {
|
||||
const [showDoneMine, setShowDoneMine] = useState(false);
|
||||
const [showDoneAssigned, setShowDoneAssigned] = useState(false);
|
||||
const [filters, setFilters] = useState<IssueFilters>({});
|
||||
const [viewMode, setViewMode] = useState<'list' | 'kanban'>(() => {
|
||||
try { return (localStorage.getItem('issues-view-mode') as 'list' | 'kanban') || 'list'; }
|
||||
catch { return 'list'; }
|
||||
});
|
||||
|
||||
const handleViewModeChange = (_: unknown, val: 'list' | 'kanban' | null) => {
|
||||
if (!val) return;
|
||||
setViewMode(val);
|
||||
localStorage.setItem('issues-view-mode', val);
|
||||
};
|
||||
|
||||
// Mutation for kanban drag-and-drop status change
|
||||
const updateStatusMut = useMutation({
|
||||
mutationFn: ({ id, status }: { id: number; status: string }) =>
|
||||
issuesApi.updateIssue(id, { status }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['issues'] });
|
||||
showSuccess('Status aktualisiert');
|
||||
},
|
||||
onError: () => showError('Fehler beim Aktualisieren'),
|
||||
});
|
||||
|
||||
// Fetch all issues for mine/assigned tabs
|
||||
const { data: issues = [], isLoading } = useQuery({
|
||||
@@ -634,7 +659,22 @@ export default function Issues() {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Typography variant="h5" gutterBottom>Issues</Typography>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
||||
<Typography variant="h5">Issues</Typography>
|
||||
<ToggleButtonGroup
|
||||
value={viewMode}
|
||||
exclusive
|
||||
onChange={handleViewModeChange}
|
||||
size="small"
|
||||
>
|
||||
<ToggleButton value="list" aria-label="Listenansicht">
|
||||
<ViewListIcon fontSize="small" />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="kanban" aria-label="Kanban-Ansicht">
|
||||
<ViewKanbanIcon fontSize="small" />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
|
||||
<Tabs value={tab} onChange={handleTabChange} sx={{ mb: 0 }}>
|
||||
{tabs.map((t, i) => <Tab key={i} label={t.label} />)}
|
||||
@@ -642,13 +682,24 @@ export default function Issues() {
|
||||
|
||||
{/* Tab 0: Meine Issues */}
|
||||
<TabPanel value={tab} index={0}>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={showDoneMine} onChange={(e) => setShowDoneMine(e.target.checked)} size="small" />}
|
||||
label="Erledigte anzeigen"
|
||||
sx={{ mb: 1 }}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 1 }}>
|
||||
{viewMode === 'list' && (
|
||||
<FormControlLabel
|
||||
control={<Switch checked={showDoneMine} onChange={(e) => setShowDoneMine(e.target.checked)} size="small" />}
|
||||
label="Erledigte anzeigen"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{isLoading ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}><CircularProgress /></Box>
|
||||
) : viewMode === 'kanban' ? (
|
||||
<KanbanBoard
|
||||
issues={myIssuesFiltered}
|
||||
statuses={issueStatuses}
|
||||
priorities={issuePriorities}
|
||||
onNavigate={(id) => navigate(`/issues/${id}`)}
|
||||
onStatusChange={(id, status) => updateStatusMut.mutate({ id, status })}
|
||||
/>
|
||||
) : (
|
||||
<IssueTable issues={myIssuesFiltered} statuses={issueStatuses} priorities={issuePriorities} />
|
||||
)}
|
||||
@@ -656,13 +707,24 @@ export default function Issues() {
|
||||
|
||||
{/* Tab 1: Zugewiesene Issues */}
|
||||
<TabPanel value={tab} index={1}>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={showDoneAssigned} onChange={(e) => setShowDoneAssigned(e.target.checked)} size="small" />}
|
||||
label="Erledigte anzeigen"
|
||||
sx={{ mb: 1 }}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 1 }}>
|
||||
{viewMode === 'list' && (
|
||||
<FormControlLabel
|
||||
control={<Switch checked={showDoneAssigned} onChange={(e) => setShowDoneAssigned(e.target.checked)} size="small" />}
|
||||
label="Erledigte anzeigen"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{isLoading ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}><CircularProgress /></Box>
|
||||
) : viewMode === 'kanban' ? (
|
||||
<KanbanBoard
|
||||
issues={assignedFiltered}
|
||||
statuses={issueStatuses}
|
||||
priorities={issuePriorities}
|
||||
onNavigate={(id) => navigate(`/issues/${id}`)}
|
||||
onStatusChange={(id, status) => updateStatusMut.mutate({ id, status })}
|
||||
/>
|
||||
) : (
|
||||
<IssueTable issues={assignedFiltered} statuses={issueStatuses} priorities={issuePriorities} />
|
||||
)}
|
||||
@@ -674,6 +736,14 @@ export default function Issues() {
|
||||
<FilterBar filters={filters} onChange={setFilters} types={types} members={members} statuses={issueStatuses} priorities={issuePriorities} />
|
||||
{isFilteredLoading ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}><CircularProgress /></Box>
|
||||
) : viewMode === 'kanban' ? (
|
||||
<KanbanBoard
|
||||
issues={filteredIssues}
|
||||
statuses={issueStatuses}
|
||||
priorities={issuePriorities}
|
||||
onNavigate={(id) => navigate(`/issues/${id}`)}
|
||||
onStatusChange={(id, status) => updateStatusMut.mutate({ id, status })}
|
||||
/>
|
||||
) : (
|
||||
<IssueTable issues={filteredIssues} statuses={issueStatuses} priorities={issuePriorities} />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user