All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 4m54s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 13s
PO No-Go A1 adressieren: Tages-Tracking mit Messnotiz, Work-UI für Übung, Next-Action-Link, Gate-Fortschritt in Kontrolle, Eingang aus A1-Nav entfernt. Co-authored-by: Cursor <cursoragent@cursor.com>
183 lines
6.1 KiB
JavaScript
183 lines
6.1 KiB
JavaScript
/**
|
|
* AP1.9d — Methoden-Default-UI (DEPRECATED AP2.3b — Fallback bis Resolver live).
|
|
* Bevorzugt: GET /api/initiatives/:id/operating-context + resolveOperatingProfile.js
|
|
*
|
|
* @typedef {{ key: string, to: string, label: string, mode: 'plan'|'work'|'control' }} ProcessStep
|
|
* @typedef {{
|
|
* archetypeKey: string | null,
|
|
* methodKey: string | null,
|
|
* hasActiveSprint: boolean,
|
|
* }} MethodUiInput
|
|
* @typedef {{
|
|
* processSteps: ProcessStep[],
|
|
* planDefaultRoute: string,
|
|
* workDefaultRoute: string,
|
|
* controlDefaultRoute: string,
|
|
* planOutlineKeys: string[] | null,
|
|
* workNavKeys: string[] | null,
|
|
* }} MethodUiDefaults
|
|
*/
|
|
|
|
import { PLAN_OUTLINE_NODES } from '../plan/planOutlineNodes.js'
|
|
import { WORK_NAV_ITEMS } from './workNav.js'
|
|
|
|
const PRODUCT_PROCESS = [
|
|
{ key: 'inbox', to: '/plan/inbox', label: 'Eingang', mode: 'plan' },
|
|
{ key: 'sprint-plan', to: '/plan/sprint', label: 'Sprint planen', mode: 'plan' },
|
|
{ key: 'sprint-work', to: '/work/sprint', label: 'Ausführen', mode: 'work' },
|
|
{ key: 'control', to: '/control/status', label: 'Kontrolle', mode: 'control' },
|
|
]
|
|
|
|
const LINEAR_PROCESS = [
|
|
{ key: 'gates', to: '/plan/gates', label: 'Zielzustände', mode: 'plan' },
|
|
{ key: 'work', to: '/work/today', label: 'Ausführen', mode: 'work' },
|
|
{ key: 'control', to: '/control/status', label: 'Kontrolle', mode: 'control' },
|
|
]
|
|
|
|
const MATURITY_PROCESS = [
|
|
{ key: 'gates', to: '/plan/gates', label: 'Stufen', mode: 'plan' },
|
|
{ key: 'journey', to: '/control/journey', label: 'Rhythmen', mode: 'control' },
|
|
{ key: 'work', to: '/work/today', label: 'Ausführen', mode: 'work' },
|
|
{ key: 'control', to: '/control/status', label: 'Kontrolle', mode: 'control' },
|
|
]
|
|
|
|
const PROGRAM_PROCESS = [
|
|
{ key: 'gates', to: '/plan/gates', label: 'Phasen', mode: 'plan' },
|
|
{ key: 'inbox', to: '/plan/inbox', label: 'Eingang', mode: 'plan' },
|
|
{ key: 'work', to: '/work/today', label: 'Ausführen', mode: 'work' },
|
|
{ key: 'control', to: '/control/status', label: 'Kontrolle', mode: 'control' },
|
|
]
|
|
|
|
const ARCHETYPE_UI = {
|
|
'initiative.product': {
|
|
processSteps: PRODUCT_PROCESS,
|
|
planDefaultRoute: '/plan/inbox',
|
|
workDefaultRoute: '/work/sprint',
|
|
controlDefaultRoute: '/control/status',
|
|
planOutlineKeys: ['profile', 'inbox', 'work', 'sprint', 'gates'],
|
|
workNavKeys: ['sprint', 'today', 'mine'],
|
|
},
|
|
'initiative.linear_project': {
|
|
processSteps: LINEAR_PROCESS,
|
|
planDefaultRoute: '/plan/gates',
|
|
workDefaultRoute: '/work/today',
|
|
controlDefaultRoute: '/control/status',
|
|
planOutlineKeys: ['profile', 'structure', 'gates', 'inbox', 'work'],
|
|
workNavKeys: ['today', 'mine', 'sprint'],
|
|
},
|
|
'initiative.maturity_journey': {
|
|
processSteps: MATURITY_PROCESS,
|
|
planDefaultRoute: '/plan/gates',
|
|
workDefaultRoute: '/work/today',
|
|
controlDefaultRoute: '/control/status',
|
|
planOutlineKeys: ['profile', 'gates', 'work'],
|
|
workNavKeys: ['today', 'mine', 'sprint'],
|
|
},
|
|
'initiative.program': {
|
|
processSteps: PROGRAM_PROCESS,
|
|
planDefaultRoute: '/plan/gates',
|
|
workDefaultRoute: '/work/today',
|
|
controlDefaultRoute: '/control/status',
|
|
planOutlineKeys: ['profile', 'structure', 'gates', 'inbox', 'sprint', 'work'],
|
|
workNavKeys: ['today', 'mine', 'sprint'],
|
|
},
|
|
}
|
|
|
|
const FALLBACK_DEFAULTS = {
|
|
processSteps: [],
|
|
planDefaultRoute: '/plan/profile',
|
|
workDefaultRoute: '/work/today',
|
|
controlDefaultRoute: '/control/status',
|
|
planOutlineKeys: null,
|
|
workNavKeys: null,
|
|
}
|
|
|
|
/**
|
|
* @param {MethodUiInput} input
|
|
* @returns {MethodUiDefaults}
|
|
*/
|
|
export function resolveMethodUiDefaults(input = {}) {
|
|
const archetypeKey = input.archetypeKey || null
|
|
const base = ARCHETYPE_UI[archetypeKey] || FALLBACK_DEFAULTS
|
|
|
|
let workDefaultRoute = base.workDefaultRoute
|
|
if (archetypeKey === 'initiative.product' && !input.hasActiveSprint) {
|
|
workDefaultRoute = '/work/today'
|
|
}
|
|
|
|
return {
|
|
processSteps: base.processSteps,
|
|
planDefaultRoute: base.planDefaultRoute,
|
|
workDefaultRoute,
|
|
controlDefaultRoute: base.controlDefaultRoute,
|
|
planOutlineKeys: base.planOutlineKeys,
|
|
workNavKeys: base.workNavKeys,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {MethodUiInput} input
|
|
* @returns {ProcessStep[]}
|
|
*/
|
|
export function resolveProcessSteps(input = {}) {
|
|
return resolveMethodUiDefaults(input).processSteps
|
|
}
|
|
|
|
/**
|
|
* @param {MethodUiInput} input
|
|
* @returns {typeof PLAN_OUTLINE_NODES}
|
|
*/
|
|
export function resolvePlanOutlineNodes(input = {}) {
|
|
const { planOutlineKeys } = resolveMethodUiDefaults(input)
|
|
if (!planOutlineKeys) return PLAN_OUTLINE_NODES
|
|
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),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* @param {MethodUiInput} input
|
|
* @returns {typeof WORK_NAV_ITEMS}
|
|
*/
|
|
export function resolveWorkNavItems(input = {}) {
|
|
const { workNavKeys } = resolveMethodUiDefaults(input)
|
|
if (!workNavKeys) return WORK_NAV_ITEMS
|
|
const order = new Map(workNavKeys.map((key, index) => [key, index]))
|
|
return WORK_NAV_ITEMS.filter((item) => order.has(item.key)).sort(
|
|
(a, b) => (order.get(a.key) ?? 0) - (order.get(b.key) ?? 0),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Aktiven Prozessschritt aus Modus + Pfad ableiten.
|
|
* @param {string} pathname
|
|
* @param {ProcessStep[]} steps
|
|
*/
|
|
export function resolveActiveProcessStepKey(pathname, steps) {
|
|
const path = (pathname || '').replace(/\/$/, '') || pathname
|
|
if (!steps.length) return null
|
|
|
|
if (path.startsWith('/control/')) {
|
|
const journey = steps.find((s) => s.key === 'journey')
|
|
if (journey && path.startsWith('/control/journey')) return 'journey'
|
|
return steps.find((s) => s.mode === 'control')?.key ?? null
|
|
}
|
|
if (path.startsWith('/work/')) {
|
|
if (path.startsWith('/work/sprint')) {
|
|
return steps.find((s) => s.key === 'sprint-work')?.key ?? null
|
|
}
|
|
return steps.find((s) => s.key === 'work')?.key ?? null
|
|
}
|
|
if (path.startsWith('/plan/')) {
|
|
if (path.startsWith('/plan/inbox')) return 'inbox'
|
|
if (path.startsWith('/plan/sprint')) return 'sprint-plan'
|
|
if (path.startsWith('/plan/gates')) return 'gates'
|
|
}
|
|
if (path.startsWith('/actions/')) {
|
|
return steps.find((s) => s.mode === 'work')?.key ?? null
|
|
}
|
|
|
|
return null
|
|
}
|