'use client'; import { useState, useMemo, useEffect } from 'react'; import { Plus, Code2, Filter, Trash2 } from 'lucide-react'; import { useDevTaskStore } from '@/stores/useDevTaskStore'; import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore'; import { useTaskWorklogStore } from '@/stores/useTaskWorklogStore'; import { useRequirementStore } from '@/stores/useRequirementStore'; import { useMemberStore } from '@/stores/useMemberStore'; import { useAuthStore } from '@/stores/useAuthStore'; import { DevTaskRow } from './DevTaskRow'; import { DevTaskCreateModal } from './DevTaskCreateModal'; import { DevTaskDetailDrawer } from './DevTaskDetailDrawer'; import { calcGroupProgress, DEV_TASK_STATUS_LABEL } from '@/lib/dev-task'; import { Pagination, usePagination } from '@/components/Pagination'; import type { DevTask, DevTaskStatus } from '@/lib/dev-task'; interface Props { versionId: string; requirementIds: string[]; versionDeadline?: string; } export function DevTaskTab({ versionId, requirementIds, versionDeadline }: Props) { const { tasks, fetchTasks, deleteTask } = useDevTaskStore(); const { categories, fetchCategories } = useTaskCategoryStore(); const { fetchWorklogs } = useTaskWorklogStore(); const { requirements } = useRequirementStore(); const { members } = useMemberStore(); const user = useAuthStore((s) => s.user); useEffect(() => { fetchTasks(); }, [fetchTasks]); useEffect(() => { fetchCategories(); }, [fetchCategories]); useEffect(() => { fetchWorklogs(); }, [fetchWorklogs]); const versionTasks = useMemo( () => tasks.filter((t) => requirementIds.includes(t.requirementId)), [tasks, requirementIds], ); // 筛选状态 const [filterAssignee, setFilterAssignee] = useState(''); const [filterStatus, setFilterStatus] = useState(''); const [filterBlocked, setFilterBlocked] = useState(''); const [filterCategory, setFilterCategory] = useState(''); const filteredTasks = useMemo(() => { let result = versionTasks; if (filterAssignee) result = result.filter((t) => t.assigneeId === filterAssignee); if (filterStatus) result = result.filter((t) => t.status === filterStatus); if (filterBlocked === 'yes') result = result.filter((t) => t.isBlocked); if (filterBlocked === 'no') result = result.filter((t) => !t.isBlocked); if (filterCategory) result = result.filter((t) => t.categoryId === filterCategory); return result; }, [versionTasks, filterAssignee, filterStatus, filterBlocked, filterCategory]); const { paged, page, setPage, total, pageSize, setPageSize } = usePagination(filteredTasks, 20); // 统计 const progress = calcGroupProgress(versionTasks); const totalEstimate = versionTasks.reduce((s, t) => s + t.estimateHours, 0); const totalActual = versionTasks.reduce((s, t) => s + t.actualHours, 0); const blockedCount = versionTasks.filter((t) => t.isBlocked).length; const [showCreate, setShowCreate] = useState(false); const [selectedTaskId, setSelectedTaskId] = useState(null); // 批量选择 const [selectedIds, setSelectedIds] = useState>(new Set()); const isAllSelected = paged.length > 0 && paged.every((t) => selectedIds.has(t.id)); const toggleSelect = (id: string) => { const next = new Set(selectedIds); if (next.has(id)) next.delete(id); else next.add(id); setSelectedIds(next); }; const toggleAll = () => { if (isAllSelected) { setSelectedIds(new Set()); } else { setSelectedIds(new Set(paged.map((t) => t.id))); } }; const handleBatchDelete = () => { if (selectedIds.size === 0) return; if (!confirm(`确定删除选中的 ${selectedIds.size} 个任务?`)) return; selectedIds.forEach((id) => deleteTask(id)); setSelectedIds(new Set()); }; // 按需求分组 const groupedByReq = useMemo(() => { const map = new Map(); for (const t of paged) { const list = map.get(t.requirementId) || []; list.push(t); map.set(t.requirementId, list); } return map; }, [paged]); const assignees = useMemo(() => { const names = new Set(versionTasks.map((t) => t.assigneeId)); return Array.from(names); }, [versionTasks]); const hasFilter = !!(filterAssignee || filterStatus || filterBlocked || filterCategory); return (
{/* 统计栏 */}
开发进度 {progress}%
{versionTasks.length} 任务 · 投入 {totalActual}h / 预计 {totalEstimate}h {blockedCount > 0 && ( {blockedCount} 阻塞 )}
{/* 筛选栏 + 批量操作 */}
{hasFilter && ( )} {selectedIds.size > 0 && ( )} {filteredTasks.length} 条结果
{/* 任务列表 */} {filteredTasks.length === 0 ? (

{hasFilter ? '没有匹配的任务' : '暂无开发任务'}

{!hasFilter && }
) : ( <> {/* 全选 */}
全选当页
{Array.from(groupedByReq.entries()).map(([reqId, reqTasks]) => { const req = requirements.find((r) => r.id === reqId); const reqProgress = calcGroupProgress(reqTasks); return (
{req?.code} {req?.title} {reqProgress}%
{reqTasks.map((t) => (
toggleSelect(t.id)} className="h-3.5 w-3.5 rounded border-[var(--line)]" onClick={(e) => e.stopPropagation()} />
c.id === t.categoryId)} onClick={() => setSelectedTaskId(t.id)} />
))}
); })} )} {/* 分页 */} {total > 20 && ( )} {/* 弹窗 & 抽屉 */} {showCreate && setShowCreate(false)} />} {selectedTaskId && t.id)} onClose={() => setSelectedTaskId(null)} />}
); }