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
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>
136 lines
4.3 KiB
JavaScript
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>
|
|
)
|
|
}
|