Kairo-Jinkendo/frontend/src/components/PlanOutlineNav.jsx
Lars efb45a3ee6
Some checks failed
Test Suite / pytest-backend (push) Failing after 2s
Test Suite / k6 /api/health Baseline (push) Has been skipped
Test Suite / playwright-smoke (push) Has been skipped
Test Suite / lint-backend (push) Successful in 3s
Test Suite / compose-smoke (push) Has been skipped
Deploy Development / deploy (push) Failing after 50s
Erfassen und Transparent: Archetyp, Methode und Auspraegung in der UI sichtbar.
Method-Profiles-API und Auswahl beim Anlegen/Profil; Steuerungs-Meta in Listen und Plan; Projekt-Archetyp-Spiegel, Next-Action-Links und Umlaut-Fix fuer testbare Guidance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 12:36:12 +02:00

136 lines
4.3 KiB
JavaScript

import { NavLink, useLocation } from 'react-router-dom'
import { useEffect, useState } from 'react'
import { getInitiative, listInitiativeActions } from '../api/initiatives.js'
import { listInitiativeBacklog } from '../api/backlog.js'
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
import { useEntityArchetypes } from '../hooks/useEntityArchetypes.js'
import { InitiativeSteeringMeta } from './InitiativeSteeringMeta.jsx'
import {
PLAN_OUTLINE_NODES,
resolvePlanOutlineActiveKey,
} from '../plan/planOutlineNodes.js'
export function PlanOutlineNav() {
const location = useLocation()
const { initiativeId, initiativeTitle, hrefWithScope } = useProgramScope()
const { initiativeArchetypes } = useEntityArchetypes()
const activeKey = resolvePlanOutlineActiveKey(location.pathname)
const [counts, setCounts] = useState({ inbox: null, work: null })
const [initiativeArchetypeKey, setInitiativeArchetypeKey] = useState('')
useEffect(() => {
if (!initiativeId) {
setInitiativeArchetypeKey('')
return undefined
}
let cancelled = false
getInitiative(initiativeId)
.then((data) => {
if (!cancelled) setInitiativeArchetypeKey(data.archetype_key || '')
})
.catch(() => {
if (!cancelled) setInitiativeArchetypeKey('')
})
return () => {
cancelled = true
}
}, [initiativeId])
useEffect(() => {
if (!initiativeId) {
setCounts({ inbox: null, work: null })
return undefined
}
let cancelled = false
Promise.all([
listInitiativeBacklog(initiativeId).catch(() => []),
listInitiativeActions(initiativeId).catch(() => []),
]).then(([backlog, actions]) => {
if (cancelled) return
setCounts({
inbox: backlog.filter((item) => item.status !== 'converted').length,
work: actions.filter(
(action) => action.status !== 'done' && action.status !== 'discarded',
).length,
})
})
return () => {
cancelled = true
}
}, [initiativeId, location.pathname])
function nodeLabel(node) {
if (node.key === 'inbox' && counts.inbox != null) {
return `${node.label} (${counts.inbox})`
}
if (node.key === 'work' && counts.work != null) {
return `${node.label} (${counts.work})`
}
return node.label
}
return (
<nav className="plan-outline-nav" aria-label="Plan-Outline">
<div className="plan-outline-nav__header">
<span className="plan-outline-nav__eyebrow">Planen</span>
{initiativeId ? (
<p className="plan-outline-nav__root" title={initiativeTitle || initiativeId}>
{initiativeTitle || 'Vorhaben'}
</p>
) : (
<p className="plan-outline-nav__root plan-outline-nav__root--muted">
Portfolio Vorhaben wählen
</p>
)}
</div>
{initiativeId && initiativeArchetypeKey && (
<InitiativeSteeringMeta
archetypeKey={initiativeArchetypeKey}
initiativeArchetypes={initiativeArchetypes}
compact
/>
)}
<ul className="plan-outline-nav__list">
{PLAN_OUTLINE_NODES.map((node) => {
const needsInitiative = node.requiresInitiative && !initiativeId
const isDisabled = node.disabled || needsInitiative
if (isDisabled) {
return (
<li key={node.key}>
<span
className={
'plan-outline-nav__link plan-outline-nav__link--disabled' +
(activeKey === node.key ? ' plan-outline-nav__link--active' : '')
}
aria-disabled="true"
title={needsInitiative ? 'Vorhaben im Scope wählen' : node.hint}
>
{nodeLabel(node)}
</span>
</li>
)
}
return (
<li key={node.key}>
<NavLink
to={hrefWithScope(node.to)}
end
className={({ isActive }) =>
'plan-outline-nav__link' +
(isActive ? ' plan-outline-nav__link--active' : '')
}
>
{nodeLabel(node)}
</NavLink>
</li>
)
})}
</ul>
</nav>
)
}