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

197 lines
6.5 KiB
React
Raw Normal View History

import {
LIFECYCLE_LABELS,
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,
archetype_key,
archetype_label,
method_profile_key,
steering_guidance,
attention_items = [],
signals = [],
counts,
upcoming_milestones = [],
next_actions = [],
} = snapshot
const displayLabel =
lifecycle_label ||
LIFECYCLE_LABELS[lifecycle_state] ||
lifecycle_state
const statPills = [
{ label: 'Offen', value: counts.actions_open, warn: false },
{ label: 'Blockiert', value: counts.actions_blocked, warn: counts.actions_blocked > 0 },
{ label: 'Review', value: counts.actions_review_required, warn: counts.actions_review_required > 0 },
{ label: 'Blocker', value: counts.blockers_open, warn: counts.blockers_open > 0 },
]
return (
<section className="card steering-snapshot steering-snapshot--hero">
<header className="steering-hero-header">
<div>
<p className="steering-hero-kicker">Leitfrage</p>
<h2 className="steering-hero-title">
Wo stehe ich — und was ist als Nächstes dran?
</h2>
{steering_guidance && (
<p className="steering-guidance">{steering_guidance}</p>
)}
</div>
<span className="badge status-badge status-active steering-lifecycle-badge">
{displayLabel}
</span>
</header>
<div className="steering-hero-toolbar">
{(archetype_label || archetype_key) && (
<p className="steering-archetype-row muted">
Archetyp: <strong>{archetype_label || archetype_key}</strong>
{method_profile_key ? ` · Ausprägung: ${method_profile_key}` : ''}
</p>
)}
{(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>
)}
<ul className="steering-stat-pills" aria-label="Kurzüberblick">
{statPills.map((pill) => (
<li
key={pill.label}
className={`steering-stat-pill${pill.warn ? ' steering-stat-pill--warn' : ''}`}
>
<span className="steering-stat-pill-value">{pill.value}</span>
<span className="steering-stat-pill-label">{pill.label}</span>
</li>
))}
</ul>
</div>
{(attention_items?.length > 0 || signals?.length > 0) && (
<p className="muted snapshot-signals snapshot-attention">
Attention:{' '}
{(attention_items.length > 0
? attention_items.map((a) => a.label)
: signals.map((s) => SIGNAL_LABELS[s] || s)
).join(' · ')}
</p>
)}
<div className="steering-hero-columns">
{next_actions.length > 0 && (
<div className="steering-next-actions">
<h3>Nächste Schritte</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.reason_code ? ` · ${item.reason_code}` : ''}
{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>
)}
</div>
{(counts.unlinked_blockers > 0 || counts.unlinked_evidence > 0) && (
<p className="steering-unlinked-hint muted">
{counts.unlinked_blockers > 0 && `${counts.unlinked_blockers} Blocker ohne Maßnahme`}
{counts.unlinked_blockers > 0 && counts.unlinked_evidence > 0 && ' · '}
{counts.unlinked_evidence > 0 && `${counts.unlinked_evidence} Nachweise ohne Maßnahme`}
{' — unter „Erweitert“ verknüpfen oder aufräumen.'}
</p>
)}
</section>
)
}