feat: unify project milestone and task editing with modals

This commit is contained in:
zhi
2026-03-16 18:13:54 +00:00
parent ef42231697
commit 7587554fd8
9 changed files with 571 additions and 239 deletions

View File

@@ -3,22 +3,13 @@ import { useNavigate } from 'react-router-dom'
import api from '@/services/api'
import type { Milestone, Project } from '@/types'
import dayjs from 'dayjs'
import MilestoneFormModal from '@/components/MilestoneFormModal'
export default function MilestonesPage() {
const [milestones, setMilestones] = useState<Milestone[]>([])
const [projects, setProjects] = useState<Project[]>([])
const [projectFilter, setProjectFilter] = useState('')
const [showCreate, setShowCreate] = useState(false)
const [form, setForm] = useState({
title: '',
description: '',
project_id: 0,
due_date: '',
planned_release_date: '',
status: 'open',
depend_on_milestones: [] as string[],
depend_on_tasks: [] as number[]
})
const navigate = useNavigate()
const fetchMilestones = () => {
@@ -27,37 +18,17 @@ export default function MilestonesPage() {
}
useEffect(() => {
api.get<Project[]>('/projects').then(({ data }) => {
setProjects(data)
if (data.length) setForm((f) => ({ ...f, project_id: data[0].id }))
})
api.get<Project[]>('/projects').then(({ data }) => setProjects(data))
}, [])
useEffect(() => { fetchMilestones() }, [projectFilter])
const createMilestone = async (e: React.FormEvent) => {
e.preventDefault()
const payload: Record<string, unknown> = {
title: form.title,
description: form.description,
project_id: form.project_id,
status: form.status,
due_date: form.due_date || null,
planned_release_date: form.planned_release_date || null,
depend_on_milestones: form.depend_on_milestones,
depend_on_tasks: form.depend_on_tasks
}
await api.post('/milestones', payload)
setShowCreate(false)
fetchMilestones()
}
return (
<div className="milestones-page">
<div className="page-header">
<h2>🏁 Milestones ({milestones.length})</h2>
<button className="btn-primary" disabled={!projectFilter} onClick={() => setShowCreate(!showCreate)}>
{showCreate ? 'Cancel' : '+ NewMilestones'}
<button className="btn-primary" disabled={!projectFilter} onClick={() => setShowCreate(true)}>
+ New Milestone
</button>
</div>
@@ -68,29 +39,13 @@ export default function MilestonesPage() {
</select>
</div>
{showCreate && (
<form className="inline-form" onSubmit={createMilestone}>
<input required placeholder="MilestonesTitle" value={form.title}
onChange={(e) => setForm({ ...form, title: e.target.value })} />
<input placeholder="Description (optional)" value={form.description}
onChange={(e) => setForm({ ...form, description: e.target.value })} />
<select value={form.project_id} onChange={(e) => setForm({ ...form, project_id: Number(e.target.value) })}>
{projects.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
</select>
<select value={form.status} onChange={(e) => setForm({ ...form, status: e.target.value })}>
<option value="open">Open</option>
<option value="pending">Pending</option>
<option value="deferred">Deferred</option>
<option value="progressing">Progressing</option>
<option value="closed">Closed</option>
</select>
<input type="date" value={form.due_date}
onChange={(e) => setForm({ ...form, due_date: e.target.value })} />
<input type="date" placeholder="Planned Release" value={form.planned_release_date}
onChange={(e) => setForm({ ...form, planned_release_date: e.target.value })} />
<button type="submit" className="btn-primary">Create</button>
</form>
)}
<MilestoneFormModal
isOpen={showCreate}
onClose={() => setShowCreate(false)}
initialProjectId={projectFilter ? Number(projectFilter) : undefined}
lockProject={Boolean(projectFilter)}
onSaved={() => fetchMilestones()}
/>
<div className="milestone-grid">
{milestones.map((ms) => (