import { useState, useEffect, useMemo } from 'react' import { useParams, useNavigate } from 'react-router-dom' import api from '@/services/api' import type { Task, Comment, Project, ProjectMember, Milestone } from '@/types' import dayjs from 'dayjs' import { useAuth } from '@/hooks/useAuth' import CreateTaskModal from '@/components/CreateTaskModal' export default function TaskDetailPage() { const { id } = useParams() const navigate = useNavigate() const { user } = useAuth() const [task, setTask] = useState(null) const [milestone, setMilestone] = useState(null) const [project, setProject] = useState(null) const [members, setMembers] = useState([]) const [comments, setComments] = useState([]) const [newComment, setNewComment] = useState('') const [showEditTask, setShowEditTask] = useState(false) const [actionLoading, setActionLoading] = useState(null) const [actionError, setActionError] = useState(null) const [showFinishModal, setShowFinishModal] = useState(false) const [finishComment, setFinishComment] = useState('') const [showCloseModal, setShowCloseModal] = useState(false) const [closeReason, setCloseReason] = useState('') const refreshTask = async () => { const { data } = await api.get(`/tasks/${id}`) setTask(data) if (data.project_id) { api.get(`/projects/${data.project_id}`).then(({ data }) => setProject(data)).catch(() => {}) api.get(`/projects/${data.project_id}/members`).then(({ data }) => setMembers(data)).catch(() => {}) } if (data.milestone_id) { api.get(`/milestones/${data.milestone_id}`).then(({ data }) => setMilestone(data)).catch(() => {}) } } useEffect(() => { refreshTask().catch(console.error) api.get(`/tasks/${id}/comments`).then(({ data }) => setComments(data)) }, [id]) const addComment = async () => { if (!newComment.trim() || !task) return await api.post('/comments', { content: newComment, task_id: task.id, author_id: 1 }) setNewComment('') const { data } = await api.get(`/tasks/${id}/comments`) setComments(data) } const doAction = async (actionName: string, newStatus: string, body?: Record) => { setActionLoading(actionName) setActionError(null) try { await api.post(`/tasks/${id}/transition?new_status=${newStatus}`, body || {}) await refreshTask() // refresh comments too (finish adds one via backend) const { data } = await api.get(`/tasks/${id}/comments`) setComments(data) } catch (err: any) { setActionError(err?.response?.data?.detail || err?.message || 'Action failed') } finally { setActionLoading(null) } } const handleOpen = () => doAction('open', 'open') const handleStart = () => doAction('start', 'undergoing') const handleFinishConfirm = async () => { if (!finishComment.trim()) return await doAction('finish', 'completed', { comment: finishComment }) setShowFinishModal(false) setFinishComment('') } const handleCloseConfirm = async () => { const body: Record = {} if (closeReason.trim()) { body.comment = `[Close reason] ${closeReason}` } await doAction('close', 'closed', body) setShowCloseModal(false) setCloseReason('') } const handleReopen = () => doAction('reopen', 'open') const currentMemberRole = useMemo( () => members.find((m) => m.user_id === user?.id)?.role, [members, user?.id] ) const canEditTask = Boolean(task && project && user && ( user.is_admin || user.id === project.owner_id || user.id === task.created_by_id || user.id === milestone?.created_by_id || currentMemberRole === 'admin' )) if (!task) return
Loading...
// Determine which action buttons to show based on status (P9.2 / P11.8) const isTerminal = task.status === 'completed' || task.status === 'closed' return (

#{task.id} {task.title}

{task.status} {task.priority} {task.task_type}{task.task_subtype && {task.task_subtype}} {task.tags && {task.tags}}
{canEditTask && !isTerminal && task.status !== 'undergoing' && ( )}
setShowEditTask(false)} task={task} onSaved={(data) => setTask(data)} />

Description

{task.description || 'No description'}

Details

Created
{dayjs(task.created_at).format('YYYY-MM-DD HH:mm')}
{task.updated_at && <>
Updated
{dayjs(task.updated_at).format('YYYY-MM-DD HH:mm')}
} {project && <>
Project
{project.name}
} {milestone && <>
Milestone
{milestone.title}
}

Actions

{actionError &&
{actionError}
}
{/* pending: Open + Close */} {task.status === 'pending' && ( <> )} {/* open: Start + Close */} {task.status === 'open' && ( <> )} {/* undergoing: Finish + Close */} {task.status === 'undergoing' && ( <> )} {/* completed / closed: Reopen */} {(task.status === 'completed' || task.status === 'closed') && ( )}
{/* Finish modal — requires comment (P9.4) */} {showFinishModal && (

Finish Task

Please leave a completion comment before finishing.