diff --git a/frontend/src/components/PlanOutlineNav.jsx b/frontend/src/components/PlanOutlineNav.jsx
index c61bd84..2d3c21d 100644
--- a/frontend/src/components/PlanOutlineNav.jsx
+++ b/frontend/src/components/PlanOutlineNav.jsx
@@ -5,11 +5,12 @@ import { listInitiativeActions } from '../api/initiatives.js'
import { listInitiativeBacklog } from '../api/backlog.js'
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
import { buildProjectsByParent } from '../utils/projectTree.js'
-import { projectPath } from '../utils/routes.js'
+import { projectPath, actionPath } from '../utils/routes.js'
import {
PLAN_OUTLINE_NODES,
resolvePlanOutlineActiveKey,
resolvePlanOutlineProjectId,
+ resolvePlanOutlineActionId,
} from '../plan/planOutlineNodes.js'
function PlanOutlineProjectTree({ projects, parentId, activeProjectId, depth = 0 }) {
@@ -52,16 +53,25 @@ export function PlanOutlineNav() {
const { initiativeId, initiativeTitle, hrefWithScope } = useProgramScope()
const activeKey = resolvePlanOutlineActiveKey(location.pathname)
const activeProjectId = resolvePlanOutlineProjectId(location.pathname)
+ const activeActionId = resolvePlanOutlineActionId(location.pathname)
const [counts, setCounts] = useState({ inbox: null, work: null })
const [projects, setProjects] = useState([])
+ const [openActions, setOpenActions] = useState([])
const [structureOpen, setStructureOpen] = useState(
activeKey === 'structure' || Boolean(activeProjectId),
)
+ const [workOpen, setWorkOpen] = useState(
+ activeKey === 'work' || Boolean(activeActionId),
+ )
useEffect(() => {
setStructureOpen(activeKey === 'structure' || Boolean(activeProjectId))
}, [activeKey, activeProjectId])
+ useEffect(() => {
+ setWorkOpen(activeKey === 'work' || Boolean(activeActionId))
+ }, [activeKey, activeActionId])
+
useEffect(() => {
if (!initiativeId) {
setProjects([])
@@ -91,11 +101,13 @@ export function PlanOutlineNav() {
listInitiativeActions(initiativeId).catch(() => []),
]).then(([backlog, actions]) => {
if (cancelled) return
+ const open = actions.filter(
+ (action) => action.status !== 'done' && action.status !== 'discarded',
+ )
+ setOpenActions(open)
setCounts({
inbox: backlog.filter((item) => item.status !== 'converted').length,
- work: actions.filter(
- (action) => action.status !== 'done' && action.status !== 'discarded',
- ).length,
+ work: open.length,
})
})
return () => {
@@ -136,7 +148,9 @@ export function PlanOutlineNav() {
const needsInitiative = node.requiresInitiative && !initiativeId
const isDisabled = node.disabled || needsInitiative
const isStructure = node.key === 'structure'
+ const isWork = node.key === 'work'
const hasProjectTree = isStructure && projects.length > 0 && initiativeId
+ const hasActionList = isWork && openActions.length > 0 && initiativeId
if (isDisabled) {
return (
@@ -169,12 +183,25 @@ export function PlanOutlineNav() {
{structureOpen ? '▾' : '▸'}
)}
+ {hasActionList && (
+
+ )}
- Überprüfbare Zielpunkte — optional. Operative Planung läuft unter Ausführung.
- Erreicht nur über Verify (Kriterien, Evidence, Review).
+ Überprüfbare Zielpunkte — optional. Bearbeitung und Verify auf der Gate-Detailseite,
+ nicht inline in der Liste.
- Legt akzeptiertes Evidence am Plan-Element an und setzt Status auf erreicht.
- Bereits vorhandener Nachweis auf Journey: Plan-Element zuweisen und auf
- Akzeptiert setzen, dann erneut Verify.
-
+ {openActions.map((action) => (
+
+ )}
)
})}
diff --git a/frontend/src/components/RoadmapItemForm.jsx b/frontend/src/components/RoadmapItemForm.jsx
new file mode 100644
index 0000000..3304f3a
--- /dev/null
+++ b/frontend/src/components/RoadmapItemForm.jsx
@@ -0,0 +1,124 @@
+import {
+ MILESTONE_STATUSES,
+ MILESTONE_STATUS_LABELS,
+ ROADMAP_ITEM_TYPE_LABELS,
+ SEQUENCING_MODE_LABELS,
+} from '../constants/status.js'
+
+const ITEM_TYPES = ['milestone', 'review_gate', 'maturity_stage']
+const SEQUENCING_MODES = ['sequential', 'parallel', 'optional']
+const EDITABLE_STATUSES = MILESTONE_STATUSES.filter(
+ (s) => !['reached', 'moved', 'discarded'].includes(s),
+)
+
+export function RoadmapItemForm({
+ initial = {},
+ onSubmit,
+ onCancel,
+ busy = false,
+ submitLabel = 'Anlegen',
+ mode = 'create',
+}) {
+ async function handleSubmit(e) {
+ e.preventDefault()
+ const form = e.target
+ const payload = {
+ title: form.title.value.trim(),
+ goal_description: form.goal_description.value.trim(),
+ target_date: form.target_date.value || undefined,
+ item_type: form.item_type.value,
+ sequencing_mode: form.sequencing_mode.value,
+ }
+ if (mode === 'edit') {
+ payload.status = form.status.value
+ }
+ await onSubmit(payload)
+ }
+
+ const statusLocked = ['reached', 'moved', 'discarded'].includes(initial.status)
+
+ return (
+
+ )
+}
diff --git a/frontend/src/components/RoadmapPlanSection.jsx b/frontend/src/components/RoadmapPlanSection.jsx
index b3c33b5..4d2a1eb 100644
--- a/frontend/src/components/RoadmapPlanSection.jsx
+++ b/frontend/src/components/RoadmapPlanSection.jsx
@@ -2,25 +2,19 @@ import { useState } from 'react'
import { Link } from 'react-router-dom'
import { gatePath } from '../utils/routes.js'
import {
- MILESTONE_STATUSES,
- MILESTONE_STATUS_LABELS,
ROADMAP_ITEM_TYPE_LABELS,
SEQUENCING_MODE_LABELS,
} from '../constants/status.js'
import { StatusBadge } from './StatusBadge.jsx'
import { EmptyState } from './EmptyState.jsx'
-
-const ITEM_TYPES = ['milestone', 'review_gate', 'maturity_stage']
-const SEQUENCING_MODES = ['sequential', 'parallel', 'optional']
-const EDITABLE_STATUSES = MILESTONE_STATUSES.filter(
- (s) => !['reached', 'moved', 'discarded'].includes(s)
-)
+import { Modal } from './Modal.jsx'
+import { RoadmapItemForm } from './RoadmapItemForm.jsx'
function formatDate(value) {
if (!value) return null
try {
return new Date(value.includes('T') ? value : `${value}T12:00:00`).toLocaleDateString(
- 'de-DE'
+ 'de-DE',
)
} catch {
return value
@@ -32,117 +26,37 @@ export function RoadmapPlanSection({
items,
canManage,
onCreate,
- onUpdateStatus,
- onUpdateItem,
- onVerifyWithEvidence,
onDelete,
busy,
}) {
- const [title, setTitle] = useState('')
- const [goalDescription, setGoalDescription] = useState('')
- const [targetDate, setTargetDate] = useState('')
- const [itemType, setItemType] = useState('milestone')
- const [sequencingMode, setSequencingMode] = useState('sequential')
- const [showForm, setShowForm] = useState(false)
- const [verifyTitles, setVerifyTitles] = useState({})
+ const [showCreate, setShowCreate] = useState(false)
- async function handleSubmit(e) {
- e.preventDefault()
- if (!title.trim()) return
- await onCreate({
- title: title.trim(),
- goal_description: goalDescription.trim(),
- target_date: targetDate || undefined,
- item_type: itemType,
- sequencing_mode: sequencingMode,
- })
- setTitle('')
- setGoalDescription('')
- setTargetDate('')
- setItemType('milestone')
- setSequencingMode('sequential')
- setShowForm(false)
+ async function handleCreateSubmit(payload) {
+ await onCreate(payload)
+ setShowCreate(false)
}
return (
- Zielzustände (Gates)
{item.goal_description}
} -
@@ -365,6 +384,26 @@ export function RoadmapItemDetailPage({ overrideItemId }) {
)}
+
+