feat: milestone/task status UI + propose pages + action buttons #8
@@ -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)}>
|
||||
|
||||
Reference in New Issue
Block a user