All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 4m24s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 12s
AP2.2b: Integrationstests Neue-Kueche-Happy-Path, Truth Table und Execution Plan abgeschlossen. UX Composition Kernel (Control/Plan/Cockpit-Slots), CriticalPathPanel aus Kernel-Read-Model mit Gate-Horizont, Next Action elementgesteuert ohne Snapshot-Doppelung, Product-Continuous-Copy und Plan/Arbeit ueber Kernel planning_debt. Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
4.3 KiB
JavaScript
127 lines
4.3 KiB
JavaScript
import { useEffect, useState } from 'react'
|
|
import { Link } from 'react-router-dom'
|
|
import { getInitiativeExecutionGraphState } from '../api/executionPlan.js'
|
|
import { LoadingState } from './LoadingState.jsx'
|
|
import { ErrorState } from './ErrorState.jsx'
|
|
import { EmptyState } from './EmptyState.jsx'
|
|
import { StatusBadge } from './StatusBadge.jsx'
|
|
import { ExecutionFlowBadge } from './ExecutionFlowBadge.jsx'
|
|
import {
|
|
buildCriticalPathSteps,
|
|
summarizeCriticalPath,
|
|
} from '../utils/executionGraph.js'
|
|
import { actionPath, scopedPath } from '../utils/routes.js'
|
|
|
|
export function CriticalPathPanel({
|
|
initiativeId,
|
|
actions = [],
|
|
graphState: kernelGraphState = null,
|
|
graphLoading = false,
|
|
scopeRoadmapItemId = null,
|
|
embedded = true,
|
|
}) {
|
|
const useKernelGraph = kernelGraphState != null || graphLoading
|
|
const [fallbackGraphState, setFallbackGraphState] = useState(null)
|
|
const [fallbackLoading, setFallbackLoading] = useState(!useKernelGraph)
|
|
const [error, setError] = useState(null)
|
|
|
|
useEffect(() => {
|
|
if (!initiativeId || useKernelGraph) {
|
|
setFallbackGraphState(null)
|
|
setFallbackLoading(false)
|
|
return undefined
|
|
}
|
|
let cancelled = false
|
|
setFallbackLoading(true)
|
|
setError(null)
|
|
getInitiativeExecutionGraphState(initiativeId, {
|
|
scopeRoadmapItemId: scopeRoadmapItemId || undefined,
|
|
})
|
|
.then((data) => {
|
|
if (!cancelled) setFallbackGraphState(data)
|
|
})
|
|
.catch((err) => {
|
|
if (!cancelled) setError(err.message)
|
|
})
|
|
.finally(() => {
|
|
if (!cancelled) setFallbackLoading(false)
|
|
})
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [initiativeId, scopeRoadmapItemId, useKernelGraph])
|
|
|
|
const graphState = useKernelGraph ? kernelGraphState : fallbackGraphState
|
|
const loading = useKernelGraph ? graphLoading : fallbackLoading
|
|
|
|
const steps = buildCriticalPathSteps(graphState, actions)
|
|
const summary = summarizeCriticalPath(graphState, actions)
|
|
|
|
const body = (
|
|
<>
|
|
{loading && <LoadingState message="Lade kritischer Pfad …" />}
|
|
{!loading && error && (
|
|
<ErrorState message={error} onRetry={() => window.location.reload()} />
|
|
)}
|
|
{!loading && !error && steps.length === 0 && (
|
|
<EmptyState message="Noch kein kritischer Pfad im aktiven Gate-Horizont — Arbeitspakete mit Abhängigkeiten unter Plan → Zielzustände / Durchführungsplan anlegen." />
|
|
)}
|
|
{!loading && !error && steps.length > 0 && (
|
|
<>
|
|
<p className="critical-path-summary muted">{summary.message}</p>
|
|
<ol className="critical-path-steps item-list">
|
|
{steps.map((step) => (
|
|
<li
|
|
key={step.actionId}
|
|
className={
|
|
'list-item card-list-item critical-path-step' +
|
|
(step.isNextReady ? ' critical-path-step--next' : '') +
|
|
(step.status === 'done' ? ' critical-path-step--done' : '')
|
|
}
|
|
>
|
|
<span className="critical-path-step__index muted">{step.index}</span>
|
|
<div className="list-item-main">
|
|
<Link to={actionPath(step.actionId)} className="link-inline">
|
|
<strong>{step.title}</strong>
|
|
</Link>
|
|
<p className="list-item-desc muted">{step.reason}</p>
|
|
</div>
|
|
<div className="list-item-meta">
|
|
<StatusBadge status={step.status} />
|
|
<ExecutionFlowBadge meta={step.meta} actionStatus={step.status} />
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ol>
|
|
<p className="muted critical-path-hint">
|
|
Graph unter{' '}
|
|
<Link to={scopedPath('/plan/gates', { initiativeId })} className="link-inline">
|
|
Plan → Zielzustände
|
|
</Link>
|
|
{' '}pflegen.
|
|
</p>
|
|
</>
|
|
)}
|
|
</>
|
|
)
|
|
|
|
if (embedded) {
|
|
return (
|
|
<section className="card critical-path-panel">
|
|
<div className="section-header">
|
|
<div>
|
|
<h2>Kritischer Pfad</h2>
|
|
<p className="section-lead muted">
|
|
Abhängigkeitspfad im aktiven Gate-Horizont — Steuerungsantwort für lineare
|
|
Vorhaben (A2).
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{body}
|
|
</section>
|
|
)
|
|
}
|
|
|
|
return body
|
|
}
|