feat(propose): P10.7 edit modal for open proposes — title+description editable, hidden for accepted/rejected
This commit is contained in:
@@ -17,6 +17,12 @@ export default function ProposeDetailPage() {
|
|||||||
const [actionLoading, setActionLoading] = useState(false)
|
const [actionLoading, setActionLoading] = useState(false)
|
||||||
const [error, setError] = useState('')
|
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 = () => {
|
const fetchPropose = () => {
|
||||||
if (!projectId) return
|
if (!projectId) return
|
||||||
api.get<Propose>(`/projects/${projectId}/proposes/${id}`).then(({ data }) => setPropose(data))
|
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 () => {
|
const handleReopen = async () => {
|
||||||
if (!projectId) return
|
if (!projectId) return
|
||||||
setActionLoading(true)
|
setActionLoading(true)
|
||||||
@@ -122,6 +154,12 @@ export default function ProposeDetailPage() {
|
|||||||
<div className="section" style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
<div className="section" style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||||
{propose.status === 'open' && (
|
{propose.status === 'open' && (
|
||||||
<>
|
<>
|
||||||
|
<button
|
||||||
|
className="btn-transition"
|
||||||
|
onClick={openEditModal}
|
||||||
|
>
|
||||||
|
✏️ Edit
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn-primary"
|
className="btn-primary"
|
||||||
disabled={actionLoading}
|
disabled={actionLoading}
|
||||||
@@ -154,6 +192,43 @@ export default function ProposeDetailPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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 */}
|
{/* Accept modal with milestone selector */}
|
||||||
{showAccept && (
|
{showAccept && (
|
||||||
<div className="modal-overlay" onClick={() => setShowAccept(false)}>
|
<div className="modal-overlay" onClick={() => setShowAccept(false)}>
|
||||||
|
|||||||
Reference in New Issue
Block a user