import { Board, ProjectRequirement, RequirementDraftTask } from "@vtit-agent-coding/shared"; import { useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Badge } from "./ui/badge"; import { Button } from "./ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog"; import { Input } from "./ui/input"; import { Label } from "./ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"; import { Textarea } from "./ui/textarea"; export function RequirementDetailView({ projectId, requirementId, onBack }: { projectId: string; requirementId: string; onBack: () => void }) { const navigate = useNavigate(); const [req, setReq] = useState(null); const [logs, _setLogs] = useState([]); const [draftTasks, setDraftTasks] = useState([]); const [boards, setBoards] = useState([]); const [editingTask, setEditingTask] = useState(null); const [editDialogOpen, setEditDialogOpen] = useState(false); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [importDialogOpen, setImportDialogOpen] = useState(false); const [selectedBoardId, setSelectedBoardId] = useState(""); const [selectedTaskIds, setSelectedTaskIds] = useState>(new Set()); const logEndRef = useRef(null); useEffect(() => { loadReq(); loadDraftTasks(); loadBoards(); }, [projectId, requirementId]); useEffect(() => { const es = new EventSource(`/api/projects/${projectId}/requirements/${requirementId}/events`); const handleStatusUpdate = (e: MessageEvent) => { try { const data = JSON.parse(e.data); if (data.status) { setReq((prev: ProjectRequirement | null) => (prev ? { ...prev, status: data.status } : null)); if (data.status === "completed") { loadDraftTasks(); } } } catch (err) { console.error("Failed to parse status SSE event:", err); } }; es.addEventListener("status", handleStatusUpdate); return () => { es.removeEventListener("status", handleStatusUpdate); es.close(); }; }, [projectId, requirementId]); useEffect(() => { if (logEndRef.current) { logEndRef.current.scrollIntoView({ behavior: "smooth" }); } }, [logs]); async function loadReq() { const res = await fetch(`/api/projects/${projectId}/requirements/${requirementId}`); if (res.ok) setReq(await res.json()); } async function loadDraftTasks() { const res = await fetch(`/api/projects/${projectId}/requirements/${requirementId}/draft-tasks`); if (res.ok) { const data = await res.json(); setDraftTasks(Array.isArray(data) ? data : []); } } async function loadBoards() { const res = await fetch(`/api/boards`); if (res.ok) { const data = await res.json(); setBoards(Array.isArray(data) ? data : []); } } async function handleUpdateTask(e: React.FormEvent) { e.preventDefault(); if (!editingTask) return; const res = await fetch(`/api/projects/${projectId}/requirements/${requirementId}/draft-tasks/${editingTask.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(editingTask), }); if (res.ok) { setEditDialogOpen(false); loadDraftTasks(); } } async function handleDeleteTask(taskId: string) { if (!confirm("Xác nhận xóa?")) return; const res = await fetch(`/api/projects/${projectId}/requirements/${requirementId}/draft-tasks/${taskId}`, { method: "DELETE", }); if (res.ok) loadDraftTasks(); } async function handleImport() { if (!selectedBoardId) return alert("Vui lòng chọn Board!"); if (selectedTaskIds.size === 0) return alert("Vui lòng chọn ít nhất 1 task!"); const res = await fetch(`/api/projects/${projectId}/requirements/${requirementId}/import`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ boardId: selectedBoardId, taskIds: Array.from(selectedTaskIds) }), }); if (res.ok) { alert("Import thành công!"); navigate(`/projects/${projectId}?tab=kanban`); // Or somewhere appropriate } else { alert("Import thất bại!"); } } if (!req) return
Đang tải...
; const steps = [ { id: "pending", label: "Chờ xử lý" }, { id: "processing_pm", label: "PM Phân Tích" }, { id: "processing_sa", label: "SA Thiết Kế" }, { id: "processing_ba", label: "BA Nghiệp Vụ" }, { id: "completed", label: "Hoàn Thành" }, ]; const currentStepIndex = steps.findIndex((s) => s.id === req.status) !== -1 ? steps.findIndex((s) => s.id === req.status) : req.status === "failed" ? steps.length : 0; return (

{req.title}

{(req.status === "pending" || req.status === "failed") && ( )}
{steps.map((step, idx) => (
{idx + 1}
{step.label}
))}
{logs.length === 0 && Chưa có logs...} {logs.map((l, i) => (
{l}
))}
{(req.status === "completed" || draftTasks.length > 0) && ( Danh sách Task Nháp
Đưa lên bảng Kanban } /> Import vào Kanban Board
{draftTasks.map((t) => (
{ const newSet = new Set(selectedTaskIds); if (e.target.checked) newSet.add(t.id); else newSet.delete(t.id); setSelectedTaskIds(newSet); }} /> {t.title}
))}
{draftTasks.map((task) => (
{task.title}
Mô tả:

{task.description}

{/* SA Section */}
SA (Technical)
Tech Notes:

{task.technical_notes}

{task.affected_files && task.affected_files.length > 0 && (
Affected Files:{" "}
{JSON.stringify(task.affected_files, null, 2)}
)} {task.steps && task.steps.length > 0 && (
Steps:
{JSON.stringify(task.steps, null, 2)}
)}
{/* BA Section */}
BA (Business)
Business Context:

{task.business_context}

{task.qc_checks && task.qc_checks.length > 0 && (
QC Checks:
{JSON.stringify(task.qc_checks, null, 2)}
)} {task.acceptance_tests && task.acceptance_tests.length > 0 && (
Acceptance Tests:{" "}
{JSON.stringify(task.acceptance_tests, null, 2)}
)}
))}
)} {/* Edit Dialog */} Sửa Task Nháp
setEditingTask((prev: any) => (prev ? { ...prev, title: e.target.value } : null))} />