'use client';
import { useState } from 'react';
import { X, AlertTriangle, Link2, ChevronRight, Clock, User, Tag, Calendar, Play, Trash2, Pencil, ArrowRightLeft } from 'lucide-react';
import { StatusBadge } from './StatusBadge';
import { CategoryChip } from './CategoryChip';
import { useDevTaskStore } from '@/stores/useDevTaskStore';
import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore';
import { useRequirementStore } from '@/stores/useRequirementStore';
import { useMemberStore } from '@/stores/useMemberStore';
import { ALLOWED_TRANSITIONS, DEV_TASK_STATUS_LABEL, DEV_TASK_STATUS_COLOR, formatHours, calcActualHoursByDates } from '@/lib/dev-task';
import { formatDateTime } from '@/lib/format';
import type { DevTaskStatus } from '@/lib/dev-task';
interface Props {
taskId: string;
allTaskIds: string[];
onClose: () => void;
contextLabel?: string;
}
export function DevTaskDetailDrawer({ taskId, allTaskIds, onClose, contextLabel }: Props) {
const { tasks, changeStatus, setBlocked, deleteTask, updateTask } = useDevTaskStore();
const { categories } = useTaskCategoryStore();
const { requirements } = useRequirementStore();
const { members } = useMemberStore();
const [showTransfer, setShowTransfer] = useState(false);
const [transferTo, setTransferTo] = useState('');
const task = tasks.find((t) => t.id === taskId);
if (!task) return null;
const category = categories.find((c) => c.id === task.categoryId);
const requirement = requirements.find((r) => r.id === task.requirementId);
const allTasks = tasks.filter((t) => allTaskIds.includes(t.id));
const predecessors = (task.predecessorIds || []).map((id) => allTasks.find((t) => t.id === id)).filter(Boolean);
const nextStatuses = ALLOWED_TRANSITIONS[task.status] || [];
const [blockReason, setBlockReason] = useState(task.blockReason || '');
const [showBlockInput, setShowBlockInput] = useState(false);
const handleTransition = (to: DevTaskStatus) => {
const result = changeStatus(task.id, to);
if (!result.ok) alert(result.message);
};
const handleBlock = () => {
if (!blockReason.trim()) return;
setBlocked(task.id, true, blockReason.trim());
setShowBlockInput(false);
};
const handleUnblock = () => {
setBlocked(task.id, false);
setBlockReason('');
};
return (
e.stopPropagation()}>
{/* 上下文 */}
{contextLabel && (
{contextLabel}
)}
{/* 顶栏 */}
{task.taskNo}
{task.title}
{task.status !== 'submitted' && (
)}
{/* 转交 */}
{showTransfer && (
转交给:
)}
{/* 滚动区域 */}
{/* 卡片1:关联需求 */}
{requirement && (
关联需求
{requirement.code}
{requirement.title}
)}
{/* 卡片2:状态 & 操作 */}
状态 & 操作
{DEV_TASK_STATUS_LABEL[task.status]}
{task.isBlocked && (
阻塞中
)}
{/* 流转按钮 */}
{nextStatuses.length > 0 && (
{nextStatuses.map((s) => (
))}
)}
{/* 阻塞管理 */}
{task.isBlocked ? (
) : (
showBlockInput ? (
setBlockReason(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleBlock(); }} placeholder="阻塞原因" className="flex-1 h-8 rounded-lg border border-[var(--line)] px-3 text-[12px] focus:border-red-400 focus:outline-none" autoFocus />
) : (
)
)}
{/* 卡片3:基本信息 */}
基本信息
类型
负责人
{task.assigneeId}
优先级
{task.priority}
预计
{formatHours(task.estimateHours)}
实际
{task.startDate ? formatHours(calcActualHoursByDates(task.startDate, task.completedAt)) : '-'}
{task.startDate && (
开始开发
{formatDateTime(task.startDate)}
)}
{task.dueDate && (
截止
{task.dueDate}
)}
{task.completedAt && (
提测
{formatDateTime(task.completedAt)}
)}
{task.description && (
)}
{/* 卡片4:前置任务 */}
{predecessors.length > 0 && (
前置任务
{predecessors.map((p: any) => (
{p.taskNo}
{p.title}
))}
)}
);
}