From e6b91e955883a22a31f0e0622c0118ed88ffe6ad Mon Sep 17 00:00:00 2001 From: zhi Date: Tue, 17 Mar 2026 07:07:04 +0000 Subject: [PATCH] P9.2+P9.4: Task action buttons (open/start/finish/close/reopen) with finish-comment modal and close-reason modal --- src/pages/TaskDetailPage.tsx | 160 +++++++++++++++++++++++++++++++---- 1 file changed, 144 insertions(+), 16 deletions(-) diff --git a/src/pages/TaskDetailPage.tsx b/src/pages/TaskDetailPage.tsx index 0c5f91d..2b74a00 100644 --- a/src/pages/TaskDetailPage.tsx +++ b/src/pages/TaskDetailPage.tsx @@ -17,6 +17,12 @@ export default function TaskDetailPage() { 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}`) @@ -43,11 +49,43 @@ export default function TaskDetailPage() { setComments(data) } - const transition = async (newStatus: string) => { - await api.post(`/tasks/${id}/transition?new_status=${newStatus}`) - await refreshTask() + const doAction = async (actionName: string, newStatus: string, extra?: () => Promise) => { + setActionLoading(actionName) + setActionError(null) + try { + if (extra) await extra() + await api.post(`/tasks/${id}/transition?new_status=${newStatus}`) + await refreshTask() + // refresh comments too (finish adds one) + 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', async () => { + await api.post('/comments', { content: finishComment, task_id: task!.id, author_id: user?.id || 1 }) + }) + setShowFinishModal(false) + setFinishComment('') + } + const handleCloseConfirm = async () => { + if (closeReason.trim()) { + await api.post('/comments', { content: `[Close reason] ${closeReason}`, task_id: task!.id, author_id: user?.id || 1 }).catch(() => {}) + } + await doAction('close', 'closed') + setShowCloseModal(false) + setCloseReason('') + } + const handleReopen = () => doAction('reopen', 'open') + const currentMemberRole = useMemo( () => members.find((m) => m.user_id === user?.id)?.role, [members, user?.id] @@ -62,13 +100,8 @@ export default function TaskDetailPage() { if (!task) return
Loading...
- const statusActions: Record = { - open: ['undergoing', 'closed'], - pending: ['open', 'closed'], - undergoing: ['completed', 'closed'], - completed: ['open'], - closed: ['open'], - } + // Determine which action buttons to show based on status (P9.2 / P11.8) + const isTerminal = task.status === 'completed' || task.status === 'closed' return (
@@ -82,7 +115,9 @@ export default function TaskDetailPage() { {task.task_type}{task.task_subtype && {task.task_subtype}} {task.tags && {task.tags}}
- {canEditTask && } + {canEditTask && !isTerminal && task.status !== 'undergoing' && ( + + )}
-

Status changes

-
- {(statusActions[task.status] || []).map((s) => ( - - ))} +

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.

+