AP1.10a: Initiative-Standardfelder vision, Zielzustand und Archetyp-Schlüssel.
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
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>
This commit is contained in:
parent
495993cad6
commit
408050bcb8
|
|
@ -21,8 +21,8 @@ def get_active_initiatives(
|
|||
try:
|
||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||
sql = """
|
||||
SELECT id, tenant_id, title, goal, status, priority,
|
||||
owner_actor_id, created_at, updated_at
|
||||
SELECT id, tenant_id, title, goal, vision, target_state_summary, archetype_key,
|
||||
status, priority, owner_actor_id, created_at, updated_at
|
||||
FROM initiatives
|
||||
WHERE tenant_id = %s AND status IN ('active', 'paused')
|
||||
ORDER BY updated_at DESC, title
|
||||
|
|
|
|||
13
backend/entity_archetypes/__init__.py
Normal file
13
backend/entity_archetypes/__init__.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from entity_archetypes.registry import (
|
||||
INITIATIVE_ARCHETYPES,
|
||||
archetype_label,
|
||||
list_archetypes,
|
||||
validate_archetype_key,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"INITIATIVE_ARCHETYPES",
|
||||
"archetype_label",
|
||||
"list_archetypes",
|
||||
"validate_archetype_key",
|
||||
]
|
||||
49
backend/entity_archetypes/registry.py
Normal file
49
backend/entity_archetypes/registry.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"""System-Archetypen (AP1.10a) — Seed-Daten bis EFS-Tabellen (10b)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
INITIATIVE_ARCHETYPES: tuple[dict[str, Any], ...] = (
|
||||
{
|
||||
"key": "initiative.generic",
|
||||
"entity_type": "initiative",
|
||||
"label": "Allgemeines Vorhaben",
|
||||
"description": "Standard-Vorhaben ohne spezielle Vorlage.",
|
||||
"is_system": True,
|
||||
},
|
||||
{
|
||||
"key": "initiative.program",
|
||||
"entity_type": "initiative",
|
||||
"label": "Programm / Mega-Vorhaben",
|
||||
"description": "Mehrere Stränge, Zielzustand und Vision zentral.",
|
||||
"is_system": True,
|
||||
},
|
||||
{
|
||||
"key": "initiative.product",
|
||||
"entity_type": "initiative",
|
||||
"label": "Produkt / Release",
|
||||
"description": "Produkt- oder Release-orientiertes Vorhaben.",
|
||||
"is_system": True,
|
||||
},
|
||||
)
|
||||
|
||||
_ARCHETYPE_INDEX = {
|
||||
(item["entity_type"], item["key"]): item for item in INITIATIVE_ARCHETYPES
|
||||
}
|
||||
|
||||
|
||||
def list_archetypes(*, entity_type: Optional[str] = None) -> list[dict[str, Any]]:
|
||||
if entity_type is None:
|
||||
return [dict(item) for item in INITIATIVE_ARCHETYPES]
|
||||
return [dict(item) for item in INITIATIVE_ARCHETYPES if item["entity_type"] == entity_type]
|
||||
|
||||
|
||||
def validate_archetype_key(*, entity_type: str, archetype_key: str) -> None:
|
||||
if (entity_type, archetype_key) not in _ARCHETYPE_INDEX:
|
||||
raise ValueError(f"Unbekannter Archetyp: {archetype_key}")
|
||||
|
||||
|
||||
def archetype_label(*, entity_type: str, archetype_key: str) -> Optional[str]:
|
||||
item = _ARCHETYPE_INDEX.get((entity_type, archetype_key))
|
||||
return item["label"] if item else None
|
||||
|
|
@ -63,6 +63,7 @@ from routers import ( # noqa: E402
|
|||
blockers,
|
||||
config,
|
||||
decisions,
|
||||
entity_archetypes,
|
||||
evidence,
|
||||
features,
|
||||
initiatives,
|
||||
|
|
@ -85,6 +86,7 @@ app.include_router(features.router)
|
|||
app.include_router(prompts.router)
|
||||
app.include_router(config.router)
|
||||
app.include_router(initiatives.router)
|
||||
app.include_router(entity_archetypes.router)
|
||||
app.include_router(journey.initiative_router)
|
||||
app.include_router(journey.roadmap_router)
|
||||
app.include_router(actions.router)
|
||||
|
|
|
|||
16
backend/migrations/016_initiative_archetypes.sql
Normal file
16
backend/migrations/016_initiative_archetypes.sql
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
-- AP1.10a: Initiative Standardfelder + Archetyp-Schlüssel
|
||||
|
||||
ALTER TABLE initiatives
|
||||
ADD COLUMN IF NOT EXISTS vision TEXT NOT NULL DEFAULT '';
|
||||
|
||||
ALTER TABLE initiatives
|
||||
ADD COLUMN IF NOT EXISTS target_state_summary TEXT NOT NULL DEFAULT '';
|
||||
|
||||
ALTER TABLE initiatives
|
||||
ADD COLUMN IF NOT EXISTS archetype_key VARCHAR(64) NOT NULL DEFAULT 'initiative.generic';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_initiatives_archetype
|
||||
ON initiatives (tenant_id, archetype_key);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_initiatives_status_tenant
|
||||
ON initiatives (tenant_id, status);
|
||||
21
backend/routers/entity_archetypes.py
Normal file
21
backend/routers/entity_archetypes.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"""Entity-Archetypen API — AP1.10a (In-Memory-Registry bis 10b)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from capabilities import require_capability
|
||||
from entity_archetypes import list_archetypes
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from tenant_context import TenantContext
|
||||
|
||||
router = APIRouter(prefix="/api/entity-archetypes", tags=["entity-archetypes"])
|
||||
|
||||
|
||||
@router.get("")
|
||||
def get_entity_archetypes(
|
||||
entity_type: Optional[str] = Query(default=None),
|
||||
ctx: TenantContext = Depends(require_capability("kairo.initiative.read")),
|
||||
):
|
||||
_ = ctx
|
||||
return list_archetypes(entity_type=entity_type)
|
||||
|
|
@ -27,6 +27,9 @@ router = APIRouter(prefix="/api/initiatives", tags=["initiatives"])
|
|||
class InitiativeCreateRequest(BaseModel):
|
||||
title: str = Field(min_length=1, max_length=255)
|
||||
goal: str = ""
|
||||
vision: str = ""
|
||||
target_state_summary: str = ""
|
||||
archetype_key: str = "initiative.generic"
|
||||
status: Literal["active", "paused", "completed", "archived"] = "active"
|
||||
priority: Literal["low", "normal", "high"] = "normal"
|
||||
owner_actor_id: Optional[str] = None
|
||||
|
|
@ -35,6 +38,9 @@ class InitiativeCreateRequest(BaseModel):
|
|||
class InitiativeUpdateRequest(BaseModel):
|
||||
title: Optional[str] = Field(default=None, min_length=1, max_length=255)
|
||||
goal: Optional[str] = None
|
||||
vision: Optional[str] = None
|
||||
target_state_summary: Optional[str] = None
|
||||
archetype_key: Optional[str] = None
|
||||
status: Optional[Literal["active", "paused", "completed", "archived"]] = None
|
||||
priority: Optional[Literal["low", "normal", "high"]] = None
|
||||
owner_actor_id: Optional[str] = None
|
||||
|
|
@ -138,6 +144,9 @@ def create_initiative(
|
|||
tenant_id=ctx.tenant_id,
|
||||
title=body.title,
|
||||
goal=body.goal,
|
||||
vision=body.vision,
|
||||
target_state_summary=body.target_state_summary,
|
||||
archetype_key=body.archetype_key,
|
||||
status=body.status,
|
||||
priority=body.priority,
|
||||
owner_actor_id=owner_actor_id,
|
||||
|
|
@ -198,6 +207,9 @@ def update_initiative(
|
|||
user_id=ctx.user_id,
|
||||
title=body.title,
|
||||
goal=body.goal,
|
||||
vision=body.vision,
|
||||
target_state_summary=body.target_state_summary,
|
||||
archetype_key=body.archetype_key,
|
||||
status=body.status,
|
||||
priority=body.priority,
|
||||
owner_actor_id=body.owner_actor_id,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from typing import Any, Literal, Optional
|
|||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
from db import get_connection
|
||||
from entity_archetypes import validate_archetype_key
|
||||
from services.audit import log_audit
|
||||
|
||||
InitiativeStatus = Literal["active", "paused", "completed", "archived"]
|
||||
|
|
@ -15,6 +16,11 @@ Priority = Literal["low", "normal", "high"]
|
|||
INITIATIVE_STATUSES = frozenset({"active", "paused", "completed", "archived"})
|
||||
PRIORITIES = frozenset({"low", "normal", "high"})
|
||||
|
||||
_INITIATIVE_COLUMNS = """
|
||||
id, tenant_id, title, goal, vision, target_state_summary, archetype_key,
|
||||
status, priority, owner_actor_id, created_at, updated_at
|
||||
"""
|
||||
|
||||
|
||||
def _serialize_row(row: dict[str, Any]) -> dict[str, Any]:
|
||||
result = dict(row)
|
||||
|
|
@ -51,12 +57,19 @@ def _actor_in_tenant(*, tenant_id: str, actor_id: str) -> bool:
|
|||
conn.close()
|
||||
|
||||
|
||||
def _validate_archetype_key(archetype_key: str) -> None:
|
||||
validate_archetype_key(entity_type="initiative", archetype_key=archetype_key)
|
||||
|
||||
|
||||
def create_initiative(
|
||||
*,
|
||||
tenant_id: str,
|
||||
title: str,
|
||||
owner_actor_id: str,
|
||||
goal: str = "",
|
||||
vision: str = "",
|
||||
target_state_summary: str = "",
|
||||
archetype_key: str = "initiative.generic",
|
||||
status: InitiativeStatus = "active",
|
||||
priority: Priority = "normal",
|
||||
user_id: Optional[str] = None,
|
||||
|
|
@ -66,6 +79,7 @@ def create_initiative(
|
|||
raise ValueError("Titel ist erforderlich")
|
||||
_validate_initiative_status(status)
|
||||
_validate_priority(priority)
|
||||
_validate_archetype_key(archetype_key)
|
||||
if not _actor_in_tenant(tenant_id=tenant_id, actor_id=owner_actor_id):
|
||||
raise ValueError("Owner-Actor gehört nicht zum Tenant")
|
||||
|
||||
|
|
@ -73,15 +87,25 @@ def create_initiative(
|
|||
try:
|
||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
f"""
|
||||
INSERT INTO initiatives (
|
||||
tenant_id, title, goal, status, priority, owner_actor_id
|
||||
tenant_id, title, goal, vision, target_state_summary, archetype_key,
|
||||
status, priority, owner_actor_id
|
||||
)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
RETURNING id, tenant_id, title, goal, status, priority,
|
||||
owner_actor_id, created_at, updated_at
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
RETURNING {_INITIATIVE_COLUMNS}
|
||||
""",
|
||||
(tenant_id, title, goal, status, priority, owner_actor_id),
|
||||
(
|
||||
tenant_id,
|
||||
title,
|
||||
goal,
|
||||
vision,
|
||||
target_state_summary,
|
||||
archetype_key,
|
||||
status,
|
||||
priority,
|
||||
owner_actor_id,
|
||||
),
|
||||
)
|
||||
row = _serialize_row(dict(cur.fetchone()))
|
||||
conn.commit()
|
||||
|
|
@ -109,9 +133,8 @@ def list_initiatives(*, tenant_id: str) -> list[dict[str, Any]]:
|
|||
try:
|
||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, tenant_id, title, goal, status, priority,
|
||||
owner_actor_id, created_at, updated_at
|
||||
f"""
|
||||
SELECT {_INITIATIVE_COLUMNS}
|
||||
FROM initiatives
|
||||
WHERE tenant_id = %s
|
||||
ORDER BY updated_at DESC, title
|
||||
|
|
@ -128,9 +151,8 @@ def get_initiative(*, tenant_id: str, initiative_id: str) -> Optional[dict[str,
|
|||
try:
|
||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, tenant_id, title, goal, status, priority,
|
||||
owner_actor_id, created_at, updated_at
|
||||
f"""
|
||||
SELECT {_INITIATIVE_COLUMNS}
|
||||
FROM initiatives
|
||||
WHERE id = %s AND tenant_id = %s
|
||||
""",
|
||||
|
|
@ -149,6 +171,9 @@ def update_initiative(
|
|||
user_id: Optional[str] = None,
|
||||
title: Optional[str] = None,
|
||||
goal: Optional[str] = None,
|
||||
vision: Optional[str] = None,
|
||||
target_state_summary: Optional[str] = None,
|
||||
archetype_key: Optional[str] = None,
|
||||
status: Optional[InitiativeStatus] = None,
|
||||
priority: Optional[Priority] = None,
|
||||
owner_actor_id: Optional[str] = None,
|
||||
|
|
@ -170,6 +195,16 @@ def update_initiative(
|
|||
if goal is not None:
|
||||
updates.append("goal = %s")
|
||||
params.append(goal)
|
||||
if vision is not None:
|
||||
updates.append("vision = %s")
|
||||
params.append(vision)
|
||||
if target_state_summary is not None:
|
||||
updates.append("target_state_summary = %s")
|
||||
params.append(target_state_summary)
|
||||
if archetype_key is not None:
|
||||
_validate_archetype_key(archetype_key)
|
||||
updates.append("archetype_key = %s")
|
||||
params.append(archetype_key)
|
||||
if status is not None:
|
||||
_validate_initiative_status(status)
|
||||
updates.append("status = %s")
|
||||
|
|
@ -198,8 +233,7 @@ def update_initiative(
|
|||
UPDATE initiatives
|
||||
SET {", ".join(updates)}
|
||||
WHERE id = %s AND tenant_id = %s
|
||||
RETURNING id, tenant_id, title, goal, status, priority,
|
||||
owner_actor_id, created_at, updated_at
|
||||
RETURNING {_INITIATIVE_COLUMNS}
|
||||
""",
|
||||
params,
|
||||
)
|
||||
|
|
|
|||
65
backend/tests/test_ap110_initiative_archetypes.py
Normal file
65
backend/tests/test_ap110_initiative_archetypes.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
"""Initiative-Archetypen & Standardfelder (AP1.10a)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from auth import AUTH_HEADER
|
||||
from tests.factories import provision_user_in_tenant
|
||||
from tests.test_initiatives_actions import _auth, _create_initiative, _login
|
||||
|
||||
|
||||
def test_initiative_archetype_fields(client):
|
||||
user = provision_user_in_tenant(tenant_role="member")
|
||||
token = _login(client, user)
|
||||
|
||||
created = _create_initiative(
|
||||
client,
|
||||
token,
|
||||
title="Produkt Alpha",
|
||||
goal="Kurz-Ziel",
|
||||
vision="Ausführliches Zielbild für Release 1",
|
||||
target_state_summary="MVP live auf Dev",
|
||||
archetype_key="initiative.product",
|
||||
)
|
||||
assert created.status_code == 201
|
||||
body = created.json()
|
||||
assert body["archetype_key"] == "initiative.product"
|
||||
assert body["vision"] == "Ausführliches Zielbild für Release 1"
|
||||
assert body["target_state_summary"] == "MVP live auf Dev"
|
||||
|
||||
updated = client.patch(
|
||||
f"/api/initiatives/{body['id']}",
|
||||
json={
|
||||
"archetype_key": "initiative.program",
|
||||
"vision": "Programm-Vision aktualisiert",
|
||||
},
|
||||
headers=_auth(token),
|
||||
)
|
||||
assert updated.status_code == 200
|
||||
assert updated.json()["archetype_key"] == "initiative.program"
|
||||
assert updated.json()["vision"] == "Programm-Vision aktualisiert"
|
||||
|
||||
|
||||
def test_list_entity_archetypes(client):
|
||||
user = provision_user_in_tenant(tenant_role="member")
|
||||
token = _login(client, user)
|
||||
|
||||
res = client.get(
|
||||
"/api/entity-archetypes?entity_type=initiative",
|
||||
headers=_auth(token),
|
||||
)
|
||||
assert res.status_code == 200
|
||||
keys = {item["key"] for item in res.json()}
|
||||
assert keys == {"initiative.generic", "initiative.program", "initiative.product"}
|
||||
|
||||
|
||||
def test_invalid_archetype_rejected(client):
|
||||
user = provision_user_in_tenant(tenant_role="member")
|
||||
token = _login(client, user)
|
||||
|
||||
res = _create_initiative(
|
||||
client,
|
||||
token,
|
||||
title="Bad Archetype",
|
||||
archetype_key="initiative.unknown",
|
||||
)
|
||||
assert res.status_code == 400
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
APP_VERSION = "0.16.9-ap1.12d-fix"
|
||||
DB_SCHEMA_VERSION = "015"
|
||||
APP_VERSION = "0.17.0-ap1.10a"
|
||||
DB_SCHEMA_VERSION = "016"
|
||||
APP_NAME = "jinkendo-kairo"
|
||||
|
|
|
|||
6
frontend/src/api/entityArchetypes.js
Normal file
6
frontend/src/api/entityArchetypes.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { apiFetch } from './client.js'
|
||||
|
||||
export function listEntityArchetypes(entityType) {
|
||||
const qs = entityType ? `?entity_type=${encodeURIComponent(entityType)}` : ''
|
||||
return apiFetch(`/api/entity-archetypes${qs}`)
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { INITIATIVE_STATUSES, PRIORITIES } from '../constants/status.js'
|
||||
import { INITIATIVE_STATUS_LABELS, PRIORITY_LABELS } from '../constants/status.js'
|
||||
import { INITIATIVE_ARCHETYPE_LABELS } from '../constants/initiativeArchetypes.js'
|
||||
import { listEntityArchetypes } from '../api/entityArchetypes.js'
|
||||
|
||||
export function InitiativeForm({
|
||||
initial = {},
|
||||
|
|
@ -8,12 +11,38 @@ export function InitiativeForm({
|
|||
busy = false,
|
||||
submitLabel = 'Speichern',
|
||||
}) {
|
||||
const [archetypes, setArchetypes] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
listEntityArchetypes('initiative')
|
||||
.then((items) => {
|
||||
if (!cancelled) setArchetypes(items)
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
setArchetypes(
|
||||
Object.entries(INITIATIVE_ARCHETYPE_LABELS).map(([key, label]) => ({
|
||||
key,
|
||||
label,
|
||||
})),
|
||||
)
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
const form = e.target
|
||||
await onSubmit({
|
||||
title: form.title.value.trim(),
|
||||
archetype_key: form.archetype_key.value,
|
||||
goal: form.goal.value,
|
||||
vision: form.vision.value,
|
||||
target_state_summary: form.target_state_summary.value,
|
||||
status: form.status.value,
|
||||
priority: form.priority.value,
|
||||
})
|
||||
|
|
@ -26,9 +55,36 @@ export function InitiativeForm({
|
|||
<input name="title" defaultValue={initial.title || ''} required maxLength={255} />
|
||||
</label>
|
||||
<label>
|
||||
Ziel
|
||||
<textarea name="goal" rows={3} defaultValue={initial.goal || ''} />
|
||||
Archetyp
|
||||
<select
|
||||
name="archetype_key"
|
||||
defaultValue={initial.archetype_key || 'initiative.generic'}
|
||||
disabled={!archetypes}
|
||||
>
|
||||
{(archetypes || []).map((item) => (
|
||||
<option key={item.key} value={item.key}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Kurzbeschreibung (Ziel)
|
||||
<textarea name="goal" rows={2} defaultValue={initial.goal || ''} />
|
||||
</label>
|
||||
<label>
|
||||
Vision
|
||||
<textarea name="vision" rows={4} defaultValue={initial.vision || ''} />
|
||||
</label>
|
||||
<label>
|
||||
Zielzustand (Kurzfassung)
|
||||
<textarea
|
||||
name="target_state_summary"
|
||||
rows={2}
|
||||
defaultValue={initial.target_state_summary || ''}
|
||||
/>
|
||||
</label>
|
||||
<div className="form-row form-row--2">
|
||||
<label>
|
||||
Status
|
||||
<select name="status" defaultValue={initial.status || 'active'}>
|
||||
|
|
@ -49,8 +105,9 @@ export function InitiativeForm({
|
|||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-actions">
|
||||
<button type="submit" className="btn btn-primary" disabled={busy}>
|
||||
<button type="submit" className="btn btn-primary" disabled={busy || !archetypes}>
|
||||
{busy ? 'Speichern …' : submitLabel}
|
||||
</button>
|
||||
{onCancel && (
|
||||
|
|
|
|||
10
frontend/src/constants/initiativeArchetypes.js
Normal file
10
frontend/src/constants/initiativeArchetypes.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/** Fallback-Labels bis API geladen (AP1.10a). */
|
||||
export const INITIATIVE_ARCHETYPE_LABELS = {
|
||||
'initiative.generic': 'Allgemeines Vorhaben',
|
||||
'initiative.program': 'Programm / Mega-Vorhaben',
|
||||
'initiative.product': 'Produkt / Release',
|
||||
}
|
||||
|
||||
export function initiativeArchetypeLabel(key) {
|
||||
return INITIATIVE_ARCHETYPE_LABELS[key] || key || 'Allgemeines Vorhaben'
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ 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()
|
||||
|
|
@ -52,20 +53,36 @@ function PlanProfileInner() {
|
|||
</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">Ziel</h3>
|
||||
<h3 className="plan-profile__label">Kurzbeschreibung (Ziel)</h3>
|
||||
<p>{initiative.goal}</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="muted">Noch kein Ziel hinterlegt.</p>
|
||||
<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">
|
||||
Archetyp-Felder und dynamische Attribute folgen mit AP1.10 (Entity Field System).
|
||||
Dynamische Zusatzfelder (EFS) folgen in AP1.10c.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<Modal open={editing} title="Vorhaben bearbeiten" onClose={() => setEditing(false)}>
|
||||
<Modal open={editing} title="Vorhaben bearbeiten" onClose={() => setEditing(false)} size="lg">
|
||||
<InitiativeForm
|
||||
initial={initiative}
|
||||
onSubmit={handleSave}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user