diff --git a/src/index.css b/src/index.css index f474997..e580b58 100644 --- a/src/index.css +++ b/src/index.css @@ -72,6 +72,9 @@ tr.clickable:hover { background: var(--bg-hover); } .status-resolved { background: #10b981; } .status-closed { background: #6b7280; } .status-blocked { background: #ef4444; } +.status-freeze { background: #8b5cf6; } +.status-undergoing { background: #f59e0b; } +.status-completed { background: #10b981; } .priority-low { background: #6b7280; } .priority-medium { background: #3b82f6; } .priority-high { background: #f59e0b; } diff --git a/src/pages/MilestoneDetailPage.tsx b/src/pages/MilestoneDetailPage.tsx index 37db6cf..4e92c31 100644 --- a/src/pages/MilestoneDetailPage.tsx +++ b/src/pages/MilestoneDetailPage.tsx @@ -41,6 +41,10 @@ export default function MilestoneDetailPage() { const [newTitle, setNewTitle] = useState('') const [newDesc, setNewDesc] = useState('') const [projectCode, setProjectCode] = useState('') + const [actionLoading, setActionLoading] = useState(null) + const [actionError, setActionError] = useState(null) + const [showCloseConfirm, setShowCloseConfirm] = useState(false) + const [closeReason, setCloseReason] = useState('') const fetchMilestone = () => { api.get(`/milestones/${id}`).then(({ data }) => { @@ -96,7 +100,34 @@ export default function MilestoneDetailPage() { currentMemberRole === 'admin' )) - const isUndergoing = milestone?.status === 'undergoing' + const msStatus = milestone?.status + const isUndergoing = msStatus === 'undergoing' + const isTerminal = msStatus === 'completed' || msStatus === 'closed' + + // --- Milestone action handlers (P8.2) --- + const performAction = async (action: string, body?: Record) => { + if (!milestone || !project) return + setActionLoading(action) + setActionError(null) + try { + await api.post(`/projects/${project.id}/milestones/${milestone.id}/actions/${action}`, body ?? {}) + fetchMilestone() + refreshMilestoneItems() + } catch (err: any) { + const detail = err?.response?.data?.detail + setActionError(typeof detail === 'string' ? detail : `${action} failed`) + } finally { + setActionLoading(null) + } + } + + const handleFreeze = () => performAction('freeze') + const handleStart = () => performAction('start') + const handleClose = () => { + performAction('close', closeReason ? { reason: closeReason } : {}) + setShowCloseConfirm(false) + setCloseReason('') + } if (!milestone) return
Loading...
@@ -120,8 +151,65 @@ export default function MilestoneDetailPage() { {milestone.status} {milestone.due_date && Due {dayjs(milestone.due_date).format('YYYY-MM-DD')}} {milestone.planned_release_date && Planned Release: {dayjs(milestone.planned_release_date).format('YYYY-MM-DD')}} + {milestone.started_at && Started: {dayjs(milestone.started_at).format('YYYY-MM-DD HH:mm')}} - {canEditMilestone && } + {canEditMilestone && !isTerminal && } + + {/* Milestone status action buttons (P8.2) */} + {!isTerminal && ( +
+ {msStatus === 'open' && ( + + )} + {msStatus === 'freeze' && ( + + )} + {(msStatus === 'open' || msStatus === 'freeze' || msStatus === 'undergoing') && ( + <> + {!showCloseConfirm ? ( + + ) : ( +
+ setCloseReason(e.target.value)} + style={{ minWidth: 180 }} + /> + + +
+ )} + + )} +
+ )} + {actionError &&

⚠️ {actionError}

} {milestone.description && ( @@ -154,14 +242,14 @@ export default function MilestoneDetailPage() {
- {!isUndergoing && canEditMilestone && ( + {!isTerminal && !isUndergoing && canEditMilestone && ( <> )} - {isUndergoing && Milestone is undergoing - cannot add new items} + {(isUndergoing || isTerminal) && {isTerminal ? `Milestone is ${msStatus}` : 'Milestone is undergoing'} — cannot add new items}