All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 1m51s
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
Migration 016, Archetyp-Registry-API, erweitertes Profil-Modal und Plan-Profil-Ansicht. Co-authored-by: Cursor <cursoragent@cursor.com>
106 lines
3.6 KiB
JavaScript
106 lines
3.6 KiB
JavaScript
import { useState } from 'react'
|
|
import { RequireInitiativeScope } from '../../components/RequireInitiativeScope.jsx'
|
|
import { ScopedInitiativeProvider } from '../../layout/ScopedInitiativeProvider.jsx'
|
|
import { StatusBadge } from '../../components/StatusBadge.jsx'
|
|
import { LoadingState } from '../../components/LoadingState.jsx'
|
|
import { Modal } from '../../components/Modal.jsx'
|
|
import { InitiativeForm } from '../../components/InitiativeForm.jsx'
|
|
import { useInitiativeOperations } from '../../context/InitiativeOperationsContext.jsx'
|
|
import { useProgramScope } from '../../context/ProgramScopeContext.jsx'
|
|
import { initiativeArchetypeLabel } from '../../constants/initiativeArchetypes.js'
|
|
|
|
function PlanProfileInner() {
|
|
const ops = useInitiativeOperations()
|
|
const { refreshInitiativeMeta } = useProgramScope()
|
|
const { initiative, loading, error, capabilities, formBusy } = ops
|
|
const [editing, setEditing] = useState(false)
|
|
|
|
const canManage = capabilities.has('kairo.initiative.manage')
|
|
|
|
if (loading) {
|
|
return <LoadingState message="Lade Profil …" />
|
|
}
|
|
|
|
if (!initiative) {
|
|
return <p className="muted">Vorhaben nicht gefunden.</p>
|
|
}
|
|
|
|
async function handleSave(payload) {
|
|
const ok = await ops.handleUpdateInitiative(payload)
|
|
if (ok) {
|
|
refreshInitiativeMeta()
|
|
setEditing(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<section className="card plan-profile">
|
|
<header className="plan-profile__header">
|
|
<h2 className="card-title">{initiative.title}</h2>
|
|
<div className="plan-profile__header-actions">
|
|
<StatusBadge kind="initiative" status={initiative.status} />
|
|
{canManage && (
|
|
<button
|
|
type="button"
|
|
className="btn btn-secondary btn-sm"
|
|
onClick={() => setEditing(true)}
|
|
disabled={formBusy}
|
|
>
|
|
Bearbeiten
|
|
</button>
|
|
)}
|
|
</div>
|
|
</header>
|
|
{error && <p className="error">{error}</p>}
|
|
<div className="plan-profile__field">
|
|
<h3 className="plan-profile__label">Archetyp</h3>
|
|
<p>{initiativeArchetypeLabel(initiative.archetype_key)}</p>
|
|
</div>
|
|
{initiative.goal ? (
|
|
<div className="plan-profile__field">
|
|
<h3 className="plan-profile__label">Kurzbeschreibung (Ziel)</h3>
|
|
<p>{initiative.goal}</p>
|
|
</div>
|
|
) : (
|
|
<p className="muted">Noch keine Kurzbeschreibung hinterlegt.</p>
|
|
)}
|
|
{initiative.vision && (
|
|
<div className="plan-profile__field">
|
|
<h3 className="plan-profile__label">Vision</h3>
|
|
<p>{initiative.vision}</p>
|
|
</div>
|
|
)}
|
|
{initiative.target_state_summary && (
|
|
<div className="plan-profile__field">
|
|
<h3 className="plan-profile__label">Zielzustand</h3>
|
|
<p>{initiative.target_state_summary}</p>
|
|
</div>
|
|
)}
|
|
<p className="muted plan-profile__note">
|
|
Dynamische Zusatzfelder (EFS) folgen in AP1.10c.
|
|
</p>
|
|
</section>
|
|
|
|
<Modal open={editing} title="Vorhaben bearbeiten" onClose={() => setEditing(false)} size="lg">
|
|
<InitiativeForm
|
|
initial={initiative}
|
|
onSubmit={handleSave}
|
|
onCancel={() => setEditing(false)}
|
|
busy={formBusy}
|
|
/>
|
|
</Modal>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export function PlanProfilePage() {
|
|
return (
|
|
<RequireInitiativeScope lead="Das Vorhabens-Profil gehört zu einem gewählten Scope.">
|
|
<ScopedInitiativeProvider>
|
|
<PlanProfileInner />
|
|
</ScopedInitiativeProvider>
|
|
</RequireInitiativeScope>
|
|
)
|
|
}
|