From b80f52cb502d7250822aa82eacb7b484dedbbe74 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 29 Jul 2026 08:17:39 +0200 Subject: [PATCH] =?UTF-8?q?feat(plan):=20Join-Gate=20im=20Gate-Designer=20?= =?UTF-8?q?f=C3=BCr=20alle=20Archetypen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Typ Join/Schaltknoten anlegbar, Detail ohne Verify, visuelle Kennzeichnung im Graph. Co-authored-by: Cursor --- .../src/components/GateDesignerNodePanel.jsx | 13 +++++- frontend/src/components/GateGraphDesigner.jsx | 1 + frontend/src/components/GateMapView.jsx | 1 + frontend/src/components/RoadmapItemForm.jsx | 43 ++++++++++++++----- frontend/src/constants/status.js | 12 ++++++ .../initiative/RoadmapItemDetailPage.jsx | 41 +++++++++++++----- frontend/src/styles/components.css | 7 +++ 7 files changed, 95 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/GateDesignerNodePanel.jsx b/frontend/src/components/GateDesignerNodePanel.jsx index be454e7..ecddf72 100644 --- a/frontend/src/components/GateDesignerNodePanel.jsx +++ b/frontend/src/components/GateDesignerNodePanel.jsx @@ -3,6 +3,7 @@ import { gatePath } from '../utils/routes.js' import { MILESTONE_STATUS_LABELS, ROADMAP_ITEM_TYPE_LABELS, + isJoinGateItem, } from '../constants/status.js' import { StatusBadge } from './StatusBadge.jsx' @@ -25,6 +26,7 @@ export function GateDesignerNodePanel({ }) { if (!item) return null + const joinGate = isJoinGateItem(item) const graphItem = graphState?.items?.[item.id] const progress = criteriaProgress?.[item.id] @@ -72,8 +74,15 @@ export function GateDesignerNodePanel({ )} - {item.goal_description && ( -

{item.goal_description}

+ {joinGate && ( +

+ Schaltknoten — schaltet, wenn alle Eingänge (requires) erreicht sind. Keine Checkliste, + kein manuelles Verify. +

+ )} + + {item.goal_description && ( +

{item.goal_description}

)} {graphItem?.parallel_group_keys?.length > 0 && ( diff --git a/frontend/src/components/GateGraphDesigner.jsx b/frontend/src/components/GateGraphDesigner.jsx index bb2602b..09e23a2 100644 --- a/frontend/src/components/GateGraphDesigner.jsx +++ b/frontend/src/components/GateGraphDesigner.jsx @@ -610,6 +610,7 @@ export function GateGraphDesigner({ const nodeClass = 'gate-map-view__node gate-map-view__node--' + (item.status || 'planned') + + (item.item_type === 'join_gate' ? ' gate-map-view__node--join_gate' : '') + (isSelected ? ' gate-graph-designer__node--selected' : '') + (draggingNodeId === node.id ? ' gate-graph-designer__node--dragging' : '') return ( diff --git a/frontend/src/components/GateMapView.jsx b/frontend/src/components/GateMapView.jsx index db164e7..a8cd291 100644 --- a/frontend/src/components/GateMapView.jsx +++ b/frontend/src/components/GateMapView.jsx @@ -171,6 +171,7 @@ export function GateMapView({ initiativeId, items, graphState: graphStateProp, e const nodeClass = 'gate-map-view__node gate-map-view__node--' + (item.status || 'planned') + + (item.item_type === 'join_gate' ? ' gate-map-view__node--join_gate' : '') + (enforceBlocking && nodeGraph?.blocked ? ' gate-map-view__node--blocked' : '') + (nodeGraph?.ready ? ' gate-map-view__node--ready' : '') return ( diff --git a/frontend/src/components/RoadmapItemForm.jsx b/frontend/src/components/RoadmapItemForm.jsx index 51cbf5c..27e860e 100644 --- a/frontend/src/components/RoadmapItemForm.jsx +++ b/frontend/src/components/RoadmapItemForm.jsx @@ -1,10 +1,11 @@ +import { useState } from 'react' import { MILESTONE_STATUSES, MILESTONE_STATUS_LABELS, + ROADMAP_ITEM_TYPES_FOR_DESIGNER, ROADMAP_ITEM_TYPE_LABELS, } from '../constants/status.js' -const ITEM_TYPES = ['milestone', 'review_gate', 'maturity_stage'] const EDITABLE_STATUSES = MILESTONE_STATUSES.filter( (s) => !['reached', 'moved', 'discarded'].includes(s), ) @@ -17,6 +18,9 @@ export function RoadmapItemForm({ submitLabel = 'Anlegen', mode = 'create', }) { + const [itemType, setItemType] = useState(initial.item_type || 'milestone') + const isJoinGate = itemType === 'join_gate' + async function handleSubmit(e) { e.preventDefault() const form = e.target @@ -27,7 +31,7 @@ export function RoadmapItemForm({ item_type: form.item_type.value, sequencing_mode: initial.sequencing_mode || 'sequential', } - if (mode === 'create') { + if (mode === 'create' && !isJoinGate) { const initialCriterion = form.initial_criterion_title?.value?.trim() if (initialCriterion) { payload.initial_criterion_title = initialCriterion @@ -46,13 +50,23 @@ export function RoadmapItemForm({ {mode === 'create' && ( )} @@ -72,14 +88,20 @@ export function RoadmapItemForm({ name="goal_description" rows={3} defaultValue={initial.goal_description || ''} - placeholder="Kurzer Kontext — prüfbare Akzeptanz legst du als Kriterien an." + placeholder={ + isJoinGate + ? 'Konsolidierter Meilenstein — welche Pfade laufen hier zusammen?' + : 'Kurzer Kontext — prüfbare Akzeptanz legst du als Kriterien an.' + } disabled={statusLocked} /> - - Die Checkliste auf der Gate-Detailseite ist führend für Verify. - + {!isJoinGate && ( + + Die Checkliste auf der Gate-Detailseite ist führend für Verify. + + )} - {mode === 'create' && ( + {mode === 'create' && !isJoinGate && (