Fix: milestone routes use milestone_code, config check via backend

- Milestone navigation now uses milestone_code instead of numeric id
- MilestoneDetailPage uses milestone.id (numeric) for project-scoped API calls
- App.tsx checks /config/status on backend first, falls back to wizard
- Added milestone_code to Milestone type
- Fixed MilestoneDetailPage to use fetched milestone.id for sub-queries
This commit is contained in:
zhi
2026-03-22 10:06:33 +00:00
parent ce07ee9021
commit 5fccc7dc7d
3 changed files with 34 additions and 14 deletions

View File

@@ -57,14 +57,14 @@ export default function MilestoneDetailPage() {
setProjectCode(proj.project_code || '')
})
api.get<ProjectMember[]>(`/projects/${data.project_id}/members`).then(({ data }) => setMembers(data)).catch(() => {})
fetchPreflight(data.project_id)
fetchPreflight(data.project_id, data.id)
}
api.get<MilestoneProgress>(`/milestones/${data.id}/progress`).then(({ data: prog }) => setProgress(prog)).catch(() => {})
})
api.get<MilestoneProgress>(`/milestones/${id}/progress`).then(({ data }) => setProgress(data)).catch(() => {})
}
const fetchPreflight = (projectId: number) => {
api.get(`/projects/${projectId}/milestones/${id}/actions/preflight`)
const fetchPreflight = (projectId: number, milestoneId: number) => {
api.get(`/projects/${projectId}/milestones/${milestoneId}/actions/preflight`)
.then(({ data }) => setPreflight(data))
.catch(() => setPreflight(null))
}
@@ -74,23 +74,23 @@ export default function MilestoneDetailPage() {
}, [id])
const refreshMilestoneItems = () => {
if (!projectCode || !id) return
api.get<MilestoneTask[]>(`/tasks/${projectCode}/${id}`).then(({ data }) => setTasks(data)).catch(() => {})
api.get<any[]>(`/supports/${projectCode}/${id}`).then(({ data }) => setSupports(data)).catch(() => {})
api.get<any[]>(`/meetings/${projectCode}/${id}`).then(({ data }) => setMeetings(data)).catch(() => {})
if (!projectCode || !milestone) return
api.get<MilestoneTask[]>(`/tasks/${projectCode}/${milestone.id}`).then(({ data }) => setTasks(data)).catch(() => {})
api.get<any[]>(`/supports/${projectCode}/${milestone.id}`).then(({ data }) => setSupports(data)).catch(() => {})
api.get<any[]>(`/meetings/${projectCode}/${milestone.id}`).then(({ data }) => setMeetings(data)).catch(() => {})
}
useEffect(() => {
refreshMilestoneItems()
}, [projectCode, id])
}, [projectCode, milestone?.id])
const createItem = async (type: 'supports' | 'meetings') => {
if (!newTitle.trim() || !projectCode) return
if (!newTitle.trim() || !projectCode || !milestone) return
const payload = {
title: newTitle,
description: newDesc || null,
}
await api.post(`/${type}/${projectCode}/${id}`, payload)
await api.post(`/${type}/${projectCode}/${milestone.id}`, payload)
setNewTitle('')
setNewDesc('')
setShowCreateSupport(false)
@@ -122,7 +122,7 @@ export default function MilestoneDetailPage() {
await api.post(`/projects/${project.id}/milestones/${milestone.id}/actions/${action}`, body ?? {})
fetchMilestone()
refreshMilestoneItems()
fetchPreflight(project.id)
fetchPreflight(project.id, milestone.id)
} catch (err: any) {
const detail = err?.response?.data?.detail
setActionError(typeof detail === 'string' ? detail : `${action} failed`)

View File

@@ -114,7 +114,7 @@ export default function ProjectDetailPage() {
{canEditProject && <button className="btn-sm" onClick={() => setShowMilestoneModal(true)}>+ New</button>}
</h3>
{milestones.map((ms) => (
<div key={ms.id} className="milestone-item" onClick={() => navigate(`/milestones/${ms.id}`)}>
<div key={ms.id} className="milestone-item" onClick={() => navigate(`/milestones/${ms.milestone_code || ms.id}`)}>
<span className={`badge status-${ms.status === 'open' ? 'open' : ms.status === 'closed' ? 'closed' : 'in_progress'}`}>{ms.status}</span>
<span className="milestone-title">{ms.title}</span>
{ms.due_date && <span className="text-dim"> · Due {dayjs(ms.due_date).format('YYYY-MM-DD')}</span>}