All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 2m57s
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 20s
CriticalPathPanel aus Execution Graph, Next-Action-Begruendung und Integrationstest fuer sequential_dependency. Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
4.5 KiB
JavaScript
135 lines
4.5 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,
|
|
findNextReadyOnCriticalPath,
|
|
summarizeCriticalPath,
|
|
} from '../utils/executionGraph.js'
|
|
import { actionPath, scopedPath } from '../utils/routes.js'
|
|
|
|
export function CriticalPathPanel({
|
|
initiativeId,
|
|
actions = [],
|
|
embedded = true,
|
|
}) {
|
|
const [graphState, setGraphState] = useState(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState(null)
|
|
|
|
useEffect(() => {
|
|
if (!initiativeId) {
|
|
setGraphState(null)
|
|
setLoading(false)
|
|
return undefined
|
|
}
|
|
let cancelled = false
|
|
setLoading(true)
|
|
setError(null)
|
|
getInitiativeExecutionGraphState(initiativeId)
|
|
.then((data) => {
|
|
if (!cancelled) setGraphState(data)
|
|
})
|
|
.catch((err) => {
|
|
if (!cancelled) setError(err.message)
|
|
})
|
|
.finally(() => {
|
|
if (!cancelled) setLoading(false)
|
|
})
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [initiativeId])
|
|
|
|
const steps = buildCriticalPathSteps(graphState, actions)
|
|
const summary = summarizeCriticalPath(graphState, actions)
|
|
const nextReady = findNextReadyOnCriticalPath(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 — 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>
|
|
{nextReady && (
|
|
<div className="critical-path-next card-list-item">
|
|
<span className="badge status-badge status-active">Nächster Schritt</span>
|
|
<strong>{nextReady.action.title}</strong>
|
|
<p className="muted">
|
|
Kritischer Pfad — ausführungsbereit, keine blockierenden Vorgänger.
|
|
</p>
|
|
<Link
|
|
to={actionPath(nextReady.actionId)}
|
|
className="btn btn-primary btn-sm"
|
|
>
|
|
Arbeitspaket öffnen
|
|
</Link>
|
|
</div>
|
|
)}
|
|
<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">
|
|
Längster Abhängigkeitspfad aus dem Execution Graph — Steuerungsantwort für
|
|
lineare Vorhaben (A2).
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{body}
|
|
</section>
|
|
)
|
|
}
|
|
|
|
return body
|
|
}
|