2026-07-05 15:55:05 +02:00
|
|
|
import { Link } from 'react-router-dom'
|
|
|
|
|
import {
|
|
|
|
|
PHASE_LABELS,
|
|
|
|
|
PHASE_DESCRIPTIONS,
|
|
|
|
|
SIGNAL_LABELS,
|
|
|
|
|
NEXT_ACTION_KIND_LABELS,
|
|
|
|
|
} from '../constants/operating.js'
|
|
|
|
|
import { MILESTONE_STATUS_LABELS } from '../constants/status.js'
|
|
|
|
|
|
|
|
|
|
function formatDate(iso) {
|
|
|
|
|
if (!iso) return '—'
|
|
|
|
|
try {
|
|
|
|
|
return new Date(iso).toLocaleDateString('de-DE')
|
|
|
|
|
} catch {
|
|
|
|
|
return iso
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-05 15:32:33 +02:00
|
|
|
|
2026-07-05 15:36:15 +02:00
|
|
|
export function SteeringSnapshotPanel({ snapshot, loading, error }) {
|
|
|
|
|
if (loading) {
|
|
|
|
|
return (
|
|
|
|
|
<section className="card steering-snapshot">
|
|
|
|
|
<p className="muted">Steuerungszustand wird geladen …</p>
|
|
|
|
|
</section>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (error || !snapshot) {
|
|
|
|
|
return (
|
|
|
|
|
<section className="card steering-snapshot steering-snapshot--unavailable">
|
|
|
|
|
<div className="section-header">
|
|
|
|
|
<h2>Steuerungszustand</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="muted">
|
|
|
|
|
{error || 'Snapshot nicht verfügbar — bitte Seite neu laden (Strg+F5).'}
|
|
|
|
|
</p>
|
|
|
|
|
</section>
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-07-05 15:32:33 +02:00
|
|
|
|
2026-07-05 15:55:05 +02:00
|
|
|
const {
|
|
|
|
|
operating_phase,
|
|
|
|
|
phase_signals,
|
|
|
|
|
counts,
|
|
|
|
|
upcoming_milestones = [],
|
|
|
|
|
next_actions = [],
|
|
|
|
|
} = snapshot
|
2026-07-05 15:32:33 +02:00
|
|
|
const phaseLabel = PHASE_LABELS[operating_phase] || operating_phase
|
2026-07-05 15:55:05 +02:00
|
|
|
const phaseDesc = PHASE_DESCRIPTIONS[operating_phase] || ''
|
2026-07-05 15:32:33 +02:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<section className="card steering-snapshot">
|
|
|
|
|
<div className="section-header">
|
|
|
|
|
<h2>Steuerungszustand</h2>
|
|
|
|
|
<span className="badge status-badge status-active">{phaseLabel}</span>
|
|
|
|
|
</div>
|
2026-07-05 15:55:05 +02:00
|
|
|
|
|
|
|
|
{phaseDesc && <p className="steering-phase-desc">{phaseDesc}</p>}
|
|
|
|
|
|
2026-07-05 15:32:33 +02:00
|
|
|
{phase_signals?.length > 0 && (
|
|
|
|
|
<p className="muted snapshot-signals">
|
2026-07-05 15:55:05 +02:00
|
|
|
{phase_signals.map((s) => SIGNAL_LABELS[s] || s).join(' · ')}
|
2026-07-05 15:32:33 +02:00
|
|
|
</p>
|
|
|
|
|
)}
|
2026-07-05 15:55:05 +02:00
|
|
|
|
|
|
|
|
{next_actions.length > 0 && (
|
|
|
|
|
<div className="steering-next-actions">
|
|
|
|
|
<h3>Nächste Schritte für dieses Vorhaben</h3>
|
|
|
|
|
<ol className="item-list compact-list">
|
|
|
|
|
{next_actions.map((item, i) => (
|
|
|
|
|
<li key={`${item.kind}-${i}`} className="list-item card-list-item compact">
|
|
|
|
|
<div className="list-item-main">
|
|
|
|
|
<span className="next-action-rank">{i + 1}</span>
|
|
|
|
|
<strong>{item.title}</strong>
|
|
|
|
|
<span className="muted list-item-sub">
|
|
|
|
|
{NEXT_ACTION_KIND_LABELS[item.kind] || item.kind}
|
|
|
|
|
{item.recommended_action ? ` — ${item.recommended_action}` : ''}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ol>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{upcoming_milestones.length > 0 && (
|
|
|
|
|
<div className="steering-milestones">
|
|
|
|
|
<h3>Meilenstein-Horizont</h3>
|
|
|
|
|
<ul className="item-list compact-list">
|
|
|
|
|
{upcoming_milestones.map((ms) => (
|
|
|
|
|
<li key={ms.id} className="list-item card-list-item compact">
|
|
|
|
|
<div className="list-item-main">
|
|
|
|
|
<strong>{ms.title}</strong>
|
|
|
|
|
<span className="muted list-item-sub">
|
|
|
|
|
{MILESTONE_STATUS_LABELS[ms.status] || ms.status}
|
|
|
|
|
{ms.target_date ? ` · Ziel: ${formatDate(ms.target_date)}` : ''}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-07-05 15:32:33 +02:00
|
|
|
<dl className="snapshot-counts">
|
|
|
|
|
<div>
|
|
|
|
|
<dt>Offene Maßnahmen</dt>
|
|
|
|
|
<dd>{counts.actions_open}</dd>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<dt>Blockiert</dt>
|
|
|
|
|
<dd>{counts.actions_blocked}</dd>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<dt>Review nötig</dt>
|
|
|
|
|
<dd>{counts.actions_review_required}</dd>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<dt>Offene Blocker</dt>
|
|
|
|
|
<dd>{counts.blockers_open}</dd>
|
|
|
|
|
</div>
|
|
|
|
|
{(counts.unlinked_blockers > 0 || counts.unlinked_evidence > 0) && (
|
|
|
|
|
<div className="snapshot-warn">
|
|
|
|
|
<dt>Unverknüpft</dt>
|
|
|
|
|
<dd>
|
|
|
|
|
{counts.unlinked_blockers} Blocker, {counts.unlinked_evidence} Evidence
|
|
|
|
|
</dd>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</dl>
|
2026-07-05 15:55:05 +02:00
|
|
|
|
2026-07-05 15:32:33 +02:00
|
|
|
<p className="muted snapshot-hint">
|
2026-07-05 15:55:05 +02:00
|
|
|
Heuristischer Steuerungszustand — später methodengeführt über Steering Core.
|
2026-07-05 15:32:33 +02:00
|
|
|
</p>
|
|
|
|
|
</section>
|
|
|
|
|
)
|
|
|
|
|
}
|