feat(P8.3): freeze/start buttons disabled with hints when pre-conditions unmet
This commit is contained in:
@@ -45,6 +45,7 @@ export default function MilestoneDetailPage() {
|
|||||||
const [actionError, setActionError] = useState<string | null>(null)
|
const [actionError, setActionError] = useState<string | null>(null)
|
||||||
const [showCloseConfirm, setShowCloseConfirm] = useState(false)
|
const [showCloseConfirm, setShowCloseConfirm] = useState(false)
|
||||||
const [closeReason, setCloseReason] = useState('')
|
const [closeReason, setCloseReason] = useState('')
|
||||||
|
const [preflight, setPreflight] = useState<{ freeze?: { allowed: boolean; reason: string | null }; start?: { allowed: boolean; reason: string | null } } | null>(null)
|
||||||
|
|
||||||
const fetchMilestone = () => {
|
const fetchMilestone = () => {
|
||||||
api.get<Milestone>(`/milestones/${id}`).then(({ data }) => {
|
api.get<Milestone>(`/milestones/${id}`).then(({ data }) => {
|
||||||
@@ -55,11 +56,18 @@ export default function MilestoneDetailPage() {
|
|||||||
setProjectCode(proj.project_code || '')
|
setProjectCode(proj.project_code || '')
|
||||||
})
|
})
|
||||||
api.get<ProjectMember[]>(`/projects/${data.project_id}/members`).then(({ data }) => setMembers(data)).catch(() => {})
|
api.get<ProjectMember[]>(`/projects/${data.project_id}/members`).then(({ data }) => setMembers(data)).catch(() => {})
|
||||||
|
fetchPreflight(data.project_id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
api.get<MilestoneProgress>(`/milestones/${id}/progress`).then(({ data }) => setProgress(data)).catch(() => {})
|
api.get<MilestoneProgress>(`/milestones/${id}/progress`).then(({ data }) => setProgress(data)).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchPreflight = (projectId: number) => {
|
||||||
|
api.get(`/projects/${projectId}/milestones/${id}/actions/preflight`)
|
||||||
|
.then(({ data }) => setPreflight(data))
|
||||||
|
.catch(() => setPreflight(null))
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchMilestone()
|
fetchMilestone()
|
||||||
}, [id])
|
}, [id])
|
||||||
@@ -113,6 +121,7 @@ export default function MilestoneDetailPage() {
|
|||||||
await api.post(`/projects/${project.id}/milestones/${milestone.id}/actions/${action}`, body ?? {})
|
await api.post(`/projects/${project.id}/milestones/${milestone.id}/actions/${action}`, body ?? {})
|
||||||
fetchMilestone()
|
fetchMilestone()
|
||||||
refreshMilestoneItems()
|
refreshMilestoneItems()
|
||||||
|
fetchPreflight(project.id)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const detail = err?.response?.data?.detail
|
const detail = err?.response?.data?.detail
|
||||||
setActionError(typeof detail === 'string' ? detail : `${action} failed`)
|
setActionError(typeof detail === 'string' ? detail : `${action} failed`)
|
||||||
@@ -164,22 +173,38 @@ export default function MilestoneDetailPage() {
|
|||||||
{!isTerminal && (
|
{!isTerminal && (
|
||||||
<div style={{ display: 'flex', gap: 8, marginTop: 8, flexWrap: 'wrap', alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: 8, marginTop: 8, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||||
{msStatus === 'open' && (
|
{msStatus === 'open' && (
|
||||||
<button
|
<span title={preflight?.freeze?.allowed === false ? preflight.freeze.reason ?? '' : ''}>
|
||||||
className="btn-primary"
|
<button
|
||||||
disabled={actionLoading === 'freeze'}
|
className="btn-primary"
|
||||||
onClick={handleFreeze}
|
disabled={actionLoading === 'freeze' || preflight?.freeze?.allowed === false}
|
||||||
>
|
onClick={handleFreeze}
|
||||||
{actionLoading === 'freeze' ? '⏳ Freezing...' : '🧊 Freeze'}
|
style={preflight?.freeze?.allowed === false ? { opacity: 0.5, cursor: 'not-allowed' } : {}}
|
||||||
</button>
|
>
|
||||||
|
{actionLoading === 'freeze' ? '⏳ Freezing...' : '🧊 Freeze'}
|
||||||
|
</button>
|
||||||
|
{preflight?.freeze?.allowed === false && (
|
||||||
|
<span className="text-dim" style={{ marginLeft: 8, fontSize: '0.85em' }}>
|
||||||
|
⚠ {preflight.freeze.reason}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
{msStatus === 'freeze' && (
|
{msStatus === 'freeze' && (
|
||||||
<button
|
<span title={preflight?.start?.allowed === false ? preflight.start.reason ?? '' : ''}>
|
||||||
className="btn-primary"
|
<button
|
||||||
disabled={actionLoading === 'start'}
|
className="btn-primary"
|
||||||
onClick={handleStart}
|
disabled={actionLoading === 'start' || preflight?.start?.allowed === false}
|
||||||
>
|
onClick={handleStart}
|
||||||
{actionLoading === 'start' ? '⏳ Starting...' : '▶️ Start'}
|
style={preflight?.start?.allowed === false ? { opacity: 0.5, cursor: 'not-allowed' } : {}}
|
||||||
</button>
|
>
|
||||||
|
{actionLoading === 'start' ? '⏳ Starting...' : '▶️ Start'}
|
||||||
|
</button>
|
||||||
|
{preflight?.start?.allowed === false && (
|
||||||
|
<span className="text-dim" style={{ marginLeft: 8, fontSize: '0.85em' }}>
|
||||||
|
⚠ {preflight.start.reason}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
{(msStatus === 'open' || msStatus === 'freeze' || msStatus === 'undergoing') && (
|
{(msStatus === 'open' || msStatus === 'freeze' || msStatus === 'undergoing') && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in New Issue
Block a user