feat: milestone/task status UI + propose pages + action buttons #8

Merged
hzhang merged 11 commits from feat/milestone-propose-state-machine into main 2026-03-19 11:12:03 +00:00
Showing only changes of commit 208538f930 - Show all commits

View File

@@ -17,6 +17,12 @@ export default function ProposeDetailPage() {
const [actionLoading, setActionLoading] = useState(false)
const [error, setError] = useState('')
// Edit state (P10.7)
const [showEdit, setShowEdit] = useState(false)
const [editTitle, setEditTitle] = useState('')
const [editDescription, setEditDescription] = useState('')
const [editLoading, setEditLoading] = useState(false)
const fetchPropose = () => {
if (!projectId) return
api.get<Propose>(`/projects/${projectId}/proposes/${id}`).then(({ data }) => setPropose(data))
@@ -62,6 +68,32 @@ export default function ProposeDetailPage() {
}
}
const openEditModal = () => {
if (!propose) return
setEditTitle(propose.title)
setEditDescription(propose.description || '')
setError('')
setShowEdit(true)
}
const handleEdit = async () => {
if (!projectId) return
setEditLoading(true)
setError('')
try {
await api.patch(`/projects/${projectId}/proposes/${id}`, {
title: editTitle,
description: editDescription,
})
setShowEdit(false)
fetchPropose()
} catch (err: any) {
setError(err.response?.data?.detail || 'Update failed')
} finally {
setEditLoading(false)
}
}
const handleReopen = async () => {
if (!projectId) return
setActionLoading(true)
@@ -122,6 +154,12 @@ export default function ProposeDetailPage() {
<div className="section" style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
{propose.status === 'open' && (
<>
<button
className="btn-transition"
onClick={openEditModal}
>
Edit
</button>
<button
className="btn-primary"
disabled={actionLoading}
@@ -154,6 +192,43 @@ export default function ProposeDetailPage() {
)}
</div>
{/* Edit modal (P10.7 — only reachable when open) */}
{showEdit && (
<div className="modal-overlay" onClick={() => setShowEdit(false)}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<h3>Edit Propose</h3>
<label style={{ display: 'block', marginBottom: 8 }}>
<strong>Title</strong>
<input
type="text"
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
style={{ width: '100%', marginTop: 4 }}
/>
</label>
<label style={{ display: 'block', marginBottom: 8 }}>
<strong>Description</strong>
<textarea
value={editDescription}
onChange={(e) => setEditDescription(e.target.value)}
rows={6}
style={{ width: '100%', marginTop: 4 }}
/>
</label>
<div style={{ marginTop: 12, display: 'flex', gap: 8 }}>
<button
className="btn-primary"
onClick={handleEdit}
disabled={!editTitle.trim() || editLoading}
>
{editLoading ? 'Saving...' : 'Save'}
</button>
<button className="btn-back" onClick={() => setShowEdit(false)}>Cancel</button>
</div>
</div>
</div>
)}
{/* Accept modal with milestone selector */}
{showAccept && (
<div className="modal-overlay" onClick={() => setShowAccept(false)}>