Kairo-Jinkendo/frontend/src/components/CriticalPathPanel.jsx
Lars 43d33d0edb
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
feat(AP2.2b, UX): Linear E2E, Composition-Kernel und Steering-Paritaet
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>
2026-07-27 13:34:13 +02:00

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
}