import { useCallback, useEffect, useState } from 'react' import { Link, useParams } from 'react-router-dom' import { getInitiative, getInitiativeSteeringSnapshot, listInitiativeActions, createInitiativeAction, } from '../api/initiatives.js' import { updateAction, setActionAssignments } from '../api/actions.js' import { listInitiativeBlockers, createInitiativeBlocker, updateBlocker, deleteBlocker, } from '../api/blockers.js' import { listInitiativeBacklog, createInitiativeBacklogItem, updateBacklogItem, deleteBacklogItem, convertBacklogToAction, } from '../api/backlog.js' import { listInitiativeMilestones, createInitiativeMilestone, updateMilestone, deleteMilestone, } from '../api/milestones.js' import { listInitiativeEvidence, createInitiativeEvidence, updateEvidence, deleteEvidence, } from '../api/evidence.js' import { listInitiativeDecisions, createInitiativeDecision, updateDecision, deleteDecision, } from '../api/decisions.js' import { listInitiativeReviews, createInitiativeReview, updateReview, deleteReview, } from '../api/reviews.js' import { listInitiativeRecurring, createInitiativeRecurring, updateRecurring, deleteRecurring, } from '../api/recurring.js' import { StatusBadge } from '../components/StatusBadge.jsx' import { PriorityBadge } from '../components/PriorityBadge.jsx' import { BlockersSection } from '../components/BlockersSection.jsx' import { BacklogSection } from '../components/BacklogSection.jsx' import { MilestonesSection } from '../components/MilestonesSection.jsx' import { EvidenceSection } from '../components/EvidenceSection.jsx' import { DecisionsSection } from '../components/DecisionsSection.jsx' import { ReviewsSection } from '../components/ReviewsSection.jsx' import { RecurringSection } from '../components/RecurringSection.jsx' import { SteeringSnapshotPanel } from '../components/SteeringSnapshotPanel.jsx' import { InitiativeActionsHub } from '../components/InitiativeActionsHub.jsx' import { CollapsibleSection } from '../components/CollapsibleSection.jsx' import { listSteeringMethods, updateInitiativeSteeringMethod } from '../api/steering.js' import { ErrorState } from '../components/ErrorState.jsx' import { LoadingState } from '../components/LoadingState.jsx' import { useCapabilities } from '../hooks/useCapabilities.js' import { useActors } from '../hooks/useActors.js' import { useSession } from '../context/SessionContext.jsx' export function InitiativeDetailPage() { const { id } = useParams() const { context, loading: sessionLoading } = useSession() const { capabilities } = useCapabilities() const { actors, loading: actorsLoading, error: actorsError, usedFallback: actorsUsedFallback, reload: reloadActors, } = useActors() const [initiative, setInitiative] = useState(null) const [actions, setActions] = useState([]) const [blockers, setBlockers] = useState([]) const [backlogItems, setBacklogItems] = useState([]) const [milestones, setMilestones] = useState([]) const [evidenceItems, setEvidenceItems] = useState([]) const [decisions, setDecisions] = useState([]) const [reviews, setReviews] = useState([]) const [recurringItems, setRecurringItems] = useState([]) const [steeringSnapshot, setSteeringSnapshot] = useState(null) const [steeringSnapshotLoading, setSteeringSnapshotLoading] = useState(false) const [steeringSnapshotError, setSteeringSnapshotError] = useState(null) const [steeringMethods, setSteeringMethods] = useState([]) const [methodBusy, setMethodBusy] = useState(false) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [showActionForm, setShowActionForm] = useState(false) const [editingAction, setEditingAction] = useState(null) const [formBusy, setFormBusy] = useState(false) const [hideDone, setHideDone] = useState(true) const load = useCallback(async () => { if (sessionLoading) return setLoading(true) setError(null) try { const [initData, actionData] = await Promise.all([ getInitiative(id), listInitiativeActions(id), ]) setInitiative(initData) setActions(actionData) const loads = [] const canReadOm = capabilities.has('kairo.initiative.read') || capabilities.has('kairo.blocker.read') if (canReadOm) { loads.push(listInitiativeBlockers(id).then(setBlockers).catch(() => setBlockers([]))) loads.push(listInitiativeBacklog(id).then(setBacklogItems).catch(() => setBacklogItems([]))) loads.push(listInitiativeMilestones(id).then(setMilestones).catch(() => setMilestones([]))) loads.push(listInitiativeEvidence(id).then(setEvidenceItems).catch(() => setEvidenceItems([]))) loads.push(listInitiativeDecisions(id).then(setDecisions).catch(() => setDecisions([]))) loads.push(listInitiativeReviews(id).then(setReviews).catch(() => setReviews([]))) loads.push(listInitiativeRecurring(id).then(setRecurringItems).catch(() => setRecurringItems([]))) loads.push( listSteeringMethods().then(setSteeringMethods).catch(() => setSteeringMethods([])) ) loads.push( (async () => { setSteeringSnapshotLoading(true) setSteeringSnapshotError(null) try { setSteeringSnapshot(await getInitiativeSteeringSnapshot(id)) } catch (err) { setSteeringSnapshot(null) setSteeringSnapshotError(err.message || 'Snapshot fehlgeschlagen') } finally { setSteeringSnapshotLoading(false) } })() ) } await Promise.all(loads) } catch (err) { setError(err.message) } finally { setLoading(false) } }, [id, capabilities, sessionLoading]) useEffect(() => { load() }, [load]) const visibleActions = hideDone ? actions.filter((a) => a.status !== 'done' && a.status !== 'discarded') : actions const actionContextById = Object.fromEntries( (steeringSnapshot?.actions || []).map((a) => [a.id, a]) ) const unlinkedBlockers = blockers.filter((b) => !b.action_id) const advancedSummary = (() => { const c = steeringSnapshot?.counts const parts = [] if (backlogItems.length) parts.push(`${backlogItems.length} Backlog`) if (decisions.length) parts.push(`${decisions.length} Entscheidungen`) if (recurringItems.length) parts.push(`${recurringItems.length} Wiederkehrend`) if (unlinkedBlockers.length) parts.push(`${unlinkedBlockers.length} lose Blocker`) if (c?.unlinked_evidence) parts.push(`${c.unlinked_evidence} lose Nachweise`) if (reviews.length) parts.push(`${reviews.length} Reviews`) return parts.length ? parts.join(' · ') : 'Backlog, Entscheidungen, Nachweise …' })() async function handleMethodChange(methodKey) { setMethodBusy(true) try { await updateInitiativeSteeringMethod(id, methodKey) setSteeringSnapshot(await getInitiativeSteeringSnapshot(id)) } catch (err) { setSteeringSnapshotError(err.message) } finally { setMethodBusy(false) } } async function handleCreateAction(payload) { setFormBusy(true) try { const assigned = payload.assigned_actor_ids?.length > 0 ? payload.assigned_actor_ids : context?.actor?.id ? [context.actor.id] : [] await createInitiativeAction(id, { ...payload, assigned_actor_ids: assigned }) setShowActionForm(false) await load() } catch (err) { setError(err.message) } finally { setFormBusy(false) } } async function handleUpdateAction(actionId, payload) { setFormBusy(true) try { await updateAction(actionId, { title: payload.title, description: payload.description, status: payload.status, priority: payload.priority, due_at: payload.due_at, clear_due_at: !payload.due_at, }) if (capabilities.has('kairo.action.manage')) { await setActionAssignments(actionId, payload.assigned_actor_ids || []) } setEditingAction(null) await load() } catch (err) { setError(err.message) } finally { setFormBusy(false) } } async function handleQuickStatus(action, status) { if (!capabilities.has('kairo.action.manage')) return try { await updateAction(action.id, { status }) await load() } catch (err) { setError(err.message) } } async function handleCreateBlocker(body) { setFormBusy(true) try { await createInitiativeBlocker(id, body) await load() } catch (err) { setError(err.message) } finally { setFormBusy(false) } } async function handleCreateBlockerForAction(actionId) { if (!capabilities.has('kairo.blocker.manage')) return setFormBusy(true) try { await createInitiativeBlocker(id, { title: 'Blocker', action_id: actionId, set_action_blocked: true, }) await load() } catch (err) { setError(err.message) } finally { setFormBusy(false) } } async function handleBlockerStatus(blockerId, status) { try { await updateBlocker(blockerId, { status }) await load() } catch (err) { setError(err.message) } } async function handleDeleteBlocker(blockerId) { try { await deleteBlocker(blockerId) await load() } catch (err) { setError(err.message) } } async function handleCreateBacklog(body) { setFormBusy(true) try { await createInitiativeBacklogItem(id, body) await load() } catch (err) { setError(err.message) } finally { setFormBusy(false) } } async function handleBacklogStatus(itemId, status) { try { await updateBacklogItem(itemId, { status }) await load() } catch (err) { setError(err.message) } } async function handleConvertBacklog(itemId) { setFormBusy(true) try { await convertBacklogToAction(itemId) await load() } catch (err) { setError(err.message) } finally { setFormBusy(false) } } async function handleDeleteBacklog(itemId) { try { await deleteBacklogItem(itemId) await load() } catch (err) { setError(err.message) } } async function handleCreateMilestone(body) { setFormBusy(true) try { await createInitiativeMilestone(id, body) await load() } catch (err) { setError(err.message) } finally { setFormBusy(false) } } async function handleMilestoneStatus(milestoneId, status) { try { await updateMilestone(milestoneId, { status }) await load() } catch (err) { setError(err.message) } } async function handleDeleteMilestone(milestoneId) { try { await deleteMilestone(milestoneId) await load() } catch (err) { setError(err.message) } } async function handleCreateEvidence(body) { setFormBusy(true) try { await createInitiativeEvidence(id, body) await load() } catch (err) { setError(err.message) } finally { setFormBusy(false) } } async function handleEvidenceStatus(evidenceId, status) { try { await updateEvidence(evidenceId, { status }) await load() } catch (err) { setError(err.message) } } async function handleDeleteEvidence(evidenceId) { try { await deleteEvidence(evidenceId) await load() } catch (err) { setError(err.message) } } async function handleCreateDecision(body) { setFormBusy(true) try { await createInitiativeDecision(id, body) await load() } catch (err) { setError(err.message) } finally { setFormBusy(false) } } async function handleDecisionStatus(decisionId, status) { try { await updateDecision(decisionId, { status }) await load() } catch (err) { setError(err.message) } } async function handleDeleteDecision(decisionId) { try { await deleteDecision(decisionId) await load() } catch (err) { setError(err.message) } } async function handleCreateReview(body) { setFormBusy(true) try { await createInitiativeReview(id, body) await load() } catch (err) { setError(err.message) } finally { setFormBusy(false) } } async function handleReviewStatus(reviewId, status) { try { await updateReview(reviewId, { status }) await load() } catch (err) { setError(err.message) } } async function handleDeleteReview(reviewId) { try { await deleteReview(reviewId) await load() } catch (err) { setError(err.message) } } async function handleCreateRecurring(body) { setFormBusy(true) try { await createInitiativeRecurring(id, body) await load() } catch (err) { setError(err.message) } finally { setFormBusy(false) } } async function handleRecurringStatus(recurringId, status) { try { await updateRecurring(recurringId, { status }) await load() } catch (err) { setError(err.message) } } async function handleDeleteRecurring(recurringId) { try { await deleteRecurring(recurringId) await load() } catch (err) { setError(err.message) } } if (loading) { return (
) } if (error && !initiative) { return (
) } if (!initiative) { return (
) } return (

{initiative.title}

{initiative.goal &&

{initiative.goal}

}
{error &&

{error}

} {capabilities.has('kairo.initiative.read') && ( )} { setShowActionForm((v) => !v) setEditingAction(null) }} editingActionId={editingAction} onEditAction={setEditingAction} onCancelEdit={() => setEditingAction(null)} onCreateAction={handleCreateAction} onUpdateAction={handleUpdateAction} onQuickStatus={handleQuickStatus} onCreateBlockerForAction={handleCreateBlockerForAction} canManage={capabilities.has('kairo.action.manage')} canManageBlocker={capabilities.has('kairo.blocker.manage')} formBusy={formBusy} actors={actors} actorsLoading={actorsLoading} actorsError={actorsError} actorsUsedFallback={actorsUsedFallback} onReloadActors={reloadActors} /> {capabilities.has('kairo.initiative.read') && ( )} {capabilities.has('kairo.initiative.read') && (
)}
) }