From ba3245b6d7e6c53315d1e60d8259e9748303cc3c Mon Sep 17 00:00:00 2001 From: zhi Date: Sun, 22 Mar 2026 10:06:33 +0000 Subject: [PATCH] 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 --- src/App.tsx | 22 +++++++++++++++++++++- src/pages/MilestoneDetailPage.tsx | 24 ++++++++++++------------ src/pages/MilestonesPage.tsx | 2 +- src/pages/ProjectDetailPage.tsx | 2 +- src/types/index.ts | 1 + 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index ae7218e..d8fa97a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -22,6 +22,10 @@ import axios from 'axios' const WIZARD_PORT = Number(import.meta.env.VITE_WIZARD_PORT) || 18080 const WIZARD_BASE = `http://127.0.0.1:${WIZARD_PORT}` +const getApiBase = () => { + return localStorage.getItem('HF_BACKEND_BASE_URL') || import.meta.env.VITE_API_BASE || 'http://127.0.0.1:8000' +} + type AppState = 'checking' | 'setup' | 'ready' export default function App() { @@ -33,6 +37,22 @@ export default function App() { }, []) const checkInitialized = async () => { + // First try the backend /config/status endpoint (reads from config volume directly) + try { + const res = await axios.get(`${getApiBase()}/config/status`, { timeout: 5000 }) + const cfg = res.data || {} + if (cfg.backend_url) { + localStorage.setItem('HF_BACKEND_BASE_URL', cfg.backend_url) + } + if (cfg.initialized === true) { + setAppState('ready') + return + } + } catch { + // Backend unreachable โ€” fall through to wizard check + } + + // Fallback: try the wizard directly (needed during initial setup before backend starts) try { const res = await axios.get(`${WIZARD_BASE}/api/v1/config/harborforge.json`, { timeout: 5000, @@ -47,7 +67,7 @@ export default function App() { setAppState('setup') } } catch { - // Wizard unreachable or config doesn't exist โ†’ setup needed + // Neither backend nor wizard reachable โ†’ setup needed setAppState('setup') } } diff --git a/src/pages/MilestoneDetailPage.tsx b/src/pages/MilestoneDetailPage.tsx index f5e27c5..a583c59 100644 --- a/src/pages/MilestoneDetailPage.tsx +++ b/src/pages/MilestoneDetailPage.tsx @@ -56,14 +56,14 @@ export default function MilestoneDetailPage() { setProjectCode(proj.project_code || '') }) api.get(`/projects/${data.project_id}/members`).then(({ data }) => setMembers(data)).catch(() => {}) - fetchPreflight(data.project_id) + fetchPreflight(data.project_id, data.id) } + api.get(`/milestones/${data.id}/progress`).then(({ data: prog }) => setProgress(prog)).catch(() => {}) }) - api.get(`/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)) } @@ -73,23 +73,23 @@ export default function MilestoneDetailPage() { }, [id]) const refreshMilestoneItems = () => { - if (!projectCode || !id) return - api.get(`/tasks/${projectCode}/${id}`).then(({ data }) => setTasks(data)).catch(() => {}) - api.get(`/supports/${projectCode}/${id}`).then(({ data }) => setSupports(data)).catch(() => {}) - api.get(`/meetings/${projectCode}/${id}`).then(({ data }) => setMeetings(data)).catch(() => {}) + if (!projectCode || !milestone) return + api.get(`/tasks/${projectCode}/${milestone.id}`).then(({ data }) => setTasks(data)).catch(() => {}) + api.get(`/supports/${projectCode}/${milestone.id}`).then(({ data }) => setSupports(data)).catch(() => {}) + api.get(`/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) @@ -121,7 +121,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`) diff --git a/src/pages/MilestonesPage.tsx b/src/pages/MilestonesPage.tsx index f0ffab9..17c6c3f 100644 --- a/src/pages/MilestonesPage.tsx +++ b/src/pages/MilestonesPage.tsx @@ -49,7 +49,7 @@ export default function MilestonesPage() {
{milestones.map((ms) => ( -
navigate(`/milestones/${ms.id}`)}> +
navigate(`/milestones/${ms.milestone_code || ms.id}`)}>
{ms.status}

{ms.title}

diff --git a/src/pages/ProjectDetailPage.tsx b/src/pages/ProjectDetailPage.tsx index 9db0a4b..be6bc67 100644 --- a/src/pages/ProjectDetailPage.tsx +++ b/src/pages/ProjectDetailPage.tsx @@ -113,7 +113,7 @@ export default function ProjectDetailPage() { {canEditProject && } {milestones.map((ms) => ( -
navigate(`/milestones/${ms.id}`)}> +
navigate(`/milestones/${ms.milestone_code || ms.id}`)}> {ms.status} {ms.title} {ms.due_date && ยท Due {dayjs(ms.due_date).format('YYYY-MM-DD')}} diff --git a/src/types/index.ts b/src/types/index.ts index d7f3e7a..8fc13b0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -64,6 +64,7 @@ export interface Comment { export interface Milestone { id: number + milestone_code: string | null title: string description: string | null status: 'open' | 'freeze' | 'undergoing' | 'completed' | 'closed'