2026-07-05 15:55:05 +02:00
|
|
|
import { Link } from 'react-router-dom'
|
|
|
|
|
import {
|
2026-07-05 17:09:20 +02:00
|
|
|
LIFECYCLE_LABELS,
|
2026-07-05 15:55:05 +02:00
|
|
|
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 17:24:17 +02:00
|
|
|
export function SteeringSnapshotPanel({
|
|
|
|
|
snapshot,
|
|
|
|
|
loading,
|
|
|
|
|
error,
|
|
|
|
|
methods = [],
|
|
|
|
|
canManageMethod = false,
|
|
|
|
|
onMethodChange,
|
|
|
|
|
methodBusy = false,
|
|
|
|
|
}) {
|
2026-07-05 15:36:15 +02:00
|
|
|
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 {
|
2026-07-05 17:09:20 +02:00
|
|
|
lifecycle_state,
|
|
|
|
|
lifecycle_label,
|
2026-07-05 17:24:17 +02:00
|
|
|
method_key,
|
|
|
|
|
method_label,
|
2026-07-05 15:55:05 +02:00
|
|
|
operating_phase,
|
2026-07-05 17:09:20 +02:00
|
|
|
operating_phase_deprecated,
|
2026-07-05 15:55:05 +02:00
|
|
|
phase_signals,
|
|
|
|
|
counts,
|
|
|
|
|
upcoming_milestones = [],
|
|
|
|
|
next_actions = [],
|
|
|
|
|
} = snapshot
|
2026-07-05 17:09:20 +02:00
|
|
|
const displayLabel =
|
|
|
|
|
lifecycle_label ||
|
|
|
|
|
LIFECYCLE_LABELS[lifecycle_state] ||
|
|
|
|
|
PHASE_LABELS[operating_phase] ||
|
|
|
|
|
lifecycle_state ||
|
|
|
|
|
operating_phase
|
|
|
|
|
const phaseDesc = lifecycle_state
|
|
|
|
|
? null
|
|
|
|
|
: 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>
|
2026-07-05 17:09:20 +02:00
|
|
|
<span className="badge status-badge status-active">{displayLabel}</span>
|
2026-07-05 15:32:33 +02:00
|
|
|
</div>
|
2026-07-05 15:55:05 +02:00
|
|
|
|
2026-07-05 17:24:17 +02:00
|
|
|
{(method_label || canManageMethod) && (
|
|
|
|
|
<p className="steering-method-row">
|
|
|
|
|
<span className="muted">Methode: </span>
|
|
|
|
|
{canManageMethod && methods.length > 0 ? (
|
|
|
|
|
<select
|
|
|
|
|
className="inline-select steering-method-select"
|
|
|
|
|
value={method_key || 'generic_operating'}
|
|
|
|
|
disabled={methodBusy}
|
|
|
|
|
onChange={(e) => onMethodChange?.(e.target.value)}
|
|
|
|
|
aria-label="Steuerungsmethode"
|
|
|
|
|
>
|
|
|
|
|
{methods.map((m) => (
|
|
|
|
|
<option key={m.key} value={m.key}>
|
|
|
|
|
{m.label}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
) : (
|
|
|
|
|
<strong>{method_label || method_key}</strong>
|
|
|
|
|
)}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
|
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 17:09:20 +02:00
|
|
|
Steuerungszustand über Standard Lifecycle
|
|
|
|
|
{lifecycle_state ? ` (${lifecycle_state})` : ''}.
|
2026-07-05 15:32:33 +02:00
|
|
|
</p>
|
|
|
|
|
</section>
|
|
|
|
|
)
|
|
|
|
|
}
|