From dc6f25b673d17d2aa1c6a81bfaddee11243b41b8 Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 26 Jul 2026 18:25:12 +0200 Subject: [PATCH] feat(product): Plan-Arbeit fuer Direct-AP-Pfad (ADP P1) Product-Outline enthaelt work; planOutlineKeys-Reihenfolge steuert Sidebar; Product-spezifischer Lead auf Plan-Arbeit. --- backend/entity_archetypes/ui_profiles.py | 2 +- frontend/src/config/methodUiDefaults.js | 8 +++++--- frontend/src/config/methodUiDefaults.test.js | 8 +++++++- frontend/src/pages/modes/PlanWorkPage.jsx | 8 +++++++- frontend/src/registry/resolveOperatingProfile.js | 6 ++++-- frontend/src/registry/resolveOperatingProfile.test.js | 2 +- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/backend/entity_archetypes/ui_profiles.py b/backend/entity_archetypes/ui_profiles.py index b807145..9d5d606 100644 --- a/backend/entity_archetypes/ui_profiles.py +++ b/backend/entity_archetypes/ui_profiles.py @@ -83,7 +83,7 @@ INITIATIVE_UI_PROFILES: dict[str, dict[str, Any]] = { "workDefaultRoute": "/work/sprint", "workDefaultRouteWithoutActiveWorkCycle": "/work/today", "controlDefaultRoute": "/control/status", - "planOutlineKeys": ["profile", "inbox", "sprint", "gates"], + "planOutlineKeys": ["profile", "inbox", "work", "sprint", "gates"], "workNavKeys": ["sprint", "today", "mine"], "dataSlices": [ "backlog", diff --git a/frontend/src/config/methodUiDefaults.js b/frontend/src/config/methodUiDefaults.js index 88928ab..2b3ab78 100644 --- a/frontend/src/config/methodUiDefaults.js +++ b/frontend/src/config/methodUiDefaults.js @@ -54,7 +54,7 @@ const ARCHETYPE_UI = { planDefaultRoute: '/plan/inbox', workDefaultRoute: '/work/sprint', controlDefaultRoute: '/control/status', - planOutlineKeys: ['profile', 'inbox', 'sprint', 'gates'], + planOutlineKeys: ['profile', 'inbox', 'work', 'sprint', 'gates'], workNavKeys: ['sprint', 'today', 'mine'], }, 'initiative.linear_project': { @@ -130,8 +130,10 @@ export function resolveProcessSteps(input = {}) { export function resolvePlanOutlineNodes(input = {}) { const { planOutlineKeys } = resolveMethodUiDefaults(input) if (!planOutlineKeys) return PLAN_OUTLINE_NODES - const allowed = new Set(planOutlineKeys) - return PLAN_OUTLINE_NODES.filter((node) => allowed.has(node.key)) + const order = new Map(planOutlineKeys.map((key, index) => [key, index])) + return PLAN_OUTLINE_NODES.filter((node) => order.has(node.key)).sort( + (a, b) => (order.get(a.key) ?? 0) - (order.get(b.key) ?? 0), + ) } /** diff --git a/frontend/src/config/methodUiDefaults.test.js b/frontend/src/config/methodUiDefaults.test.js index cf26d43..1ddfe0d 100644 --- a/frontend/src/config/methodUiDefaults.test.js +++ b/frontend/src/config/methodUiDefaults.test.js @@ -15,7 +15,7 @@ describe('methodUiDefaults', () => { }) expect(ui.planDefaultRoute).toBe('/plan/inbox') expect(ui.workDefaultRoute).toBe('/work/sprint') - expect(ui.planOutlineKeys).toEqual(['profile', 'inbox', 'sprint', 'gates']) + expect(ui.planOutlineKeys).toEqual(['profile', 'inbox', 'work', 'sprint', 'gates']) expect(resolveWorkNavItems({ archetypeKey: 'initiative.product' }).map((i) => i.key)).toEqual([ 'sprint', 'today', @@ -41,6 +41,12 @@ describe('methodUiDefaults', () => { ]) }) + it('product plan outline includes work for direct AP path', () => { + expect( + resolvePlanOutlineNodes({ archetypeKey: 'initiative.product' }).map((n) => n.key), + ).toEqual(['profile', 'inbox', 'work', 'sprint', 'gates']) + }) + it('linear archetype defaults to gates and filters plan outline', () => { const ui = resolveMethodUiDefaults({ archetypeKey: 'initiative.linear_project' }) expect(ui.planDefaultRoute).toBe('/plan/gates') diff --git a/frontend/src/pages/modes/PlanWorkPage.jsx b/frontend/src/pages/modes/PlanWorkPage.jsx index 67e5cbd..a2ac3c9 100644 --- a/frontend/src/pages/modes/PlanWorkPage.jsx +++ b/frontend/src/pages/modes/PlanWorkPage.jsx @@ -13,6 +13,7 @@ function PlanWorkInner() { const { initiativeId, projectId: scopeProjectId, hrefWithScope } = useProgramScope() const [executionGraph, setExecutionGraph] = useState(null) const { + initiative, actions, projects, roadmapItems, @@ -52,6 +53,11 @@ function PlanWorkInner() { return } + const isProduct = initiative?.archetype_key === 'initiative.product' + const sectionLead = isProduct + ? 'Arbeitspakete direkt anlegen (Pfad B) oder committetes Ist — ohne Eingang-Umweg. Sprint-fokussiert unter Ausführen → Sprint.' + : 'Gesamt-Ist am Vorhaben — Sprint-fokussierte Arbeit unter Ausführen → Sprint.' + return ( <> {error &&

{error}

} @@ -76,7 +82,7 @@ function PlanWorkInner() { busy={formBusy} executionGraph={executionGraph} sectionTitle="Alle Arbeitspakete" - sectionLead="Gesamt-Ist am Vorhaben — Sprint-fokussierte Arbeit unter Ausführen → Sprint." + sectionLead={sectionLead} /> ) diff --git a/frontend/src/registry/resolveOperatingProfile.js b/frontend/src/registry/resolveOperatingProfile.js index 671d40b..a2f5ffe 100644 --- a/frontend/src/registry/resolveOperatingProfile.js +++ b/frontend/src/registry/resolveOperatingProfile.js @@ -145,8 +145,10 @@ export function resolveOperatingProfileFromInput(input = {}) { export function resolvePlanOutlineNodesFromProfile(input = {}) { const { planOutlineKeys } = resolveOperatingProfileFromInput(input) if (!planOutlineKeys) return PLAN_OUTLINE_NODES - const allowed = new Set(planOutlineKeys) - return PLAN_OUTLINE_NODES.filter((node) => allowed.has(node.key)) + const order = new Map(planOutlineKeys.map((key, index) => [key, index])) + return PLAN_OUTLINE_NODES.filter((node) => order.has(node.key)).sort( + (a, b) => (order.get(a.key) ?? 0) - (order.get(b.key) ?? 0), + ) } /** diff --git a/frontend/src/registry/resolveOperatingProfile.test.js b/frontend/src/registry/resolveOperatingProfile.test.js index 805f07f..58fb77e 100644 --- a/frontend/src/registry/resolveOperatingProfile.test.js +++ b/frontend/src/registry/resolveOperatingProfile.test.js @@ -21,7 +21,7 @@ const PRODUCT_CONTEXT = { workDefaultRoute: '/work/sprint', workDefaultRouteWithoutActiveWorkCycle: '/work/today', controlDefaultRoute: '/control/status', - planOutlineKeys: ['profile', 'inbox', 'sprint', 'gates'], + planOutlineKeys: ['profile', 'inbox', 'work', 'sprint', 'gates'], workNavKeys: ['sprint', 'today', 'mine'], }, data_slices: ['backlog', 'actions', 'roadmap', 'work_cycles', 'projects'],