Kairo-Jinkendo/frontend/src/components/SteeringSnapshotPanel.jsx

183 lines
5.3 KiB
React
Raw Normal View History

import { Link } from 'react-router-dom'
import {
LIFECYCLE_LABELS,
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
}
}
export function SteeringSnapshotPanel({
snapshot,
loading,
error,
methods = [],
canManageMethod = false,
onMethodChange,
methodBusy = false,
}) {
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>
)
}
const {
lifecycle_state,
lifecycle_label,
method_key,
method_label,
operating_phase,
operating_phase_deprecated,
phase_signals,
counts,
upcoming_milestones = [],
next_actions = [],
} = snapshot
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] || ''
return (
<section className="card steering-snapshot">
<div className="section-header">
<h2>Steuerungszustand</h2>
<span className="badge status-badge status-active">{displayLabel}</span>
</div>
{(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>
)}
{phaseDesc && <p className="steering-phase-desc">{phaseDesc}</p>}
{phase_signals?.length > 0 && (
<p className="muted snapshot-signals">
{phase_signals.map((s) => SIGNAL_LABELS[s] || s).join(' · ')}
</p>
)}
{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>
)}
<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>
<p className="muted snapshot-hint">
Steuerungszustand über Standard Lifecycle
{lifecycle_state ? ` (${lifecycle_state})` : ''}.
</p>
</section>
)
}