AP2.3d: Route Gating via OperatingRouteGate und Slice-basierte Seitenlogik.
Some checks failed
Test Suite / pytest-backend (push) Waiting to run
Test Suite / lint-backend (push) Waiting to run
Test Suite / compose-smoke (push) Waiting to run
Test Suite / k6 /api/health Baseline (push) Blocked by required conditions
Test Suite / playwright-smoke (push) Blocked by required conditions
Deploy Development / deploy (push) Has been cancelled
Some checks failed
Test Suite / pytest-backend (push) Waiting to run
Test Suite / lint-backend (push) Waiting to run
Test Suite / compose-smoke (push) Waiting to run
Test Suite / k6 /api/health Baseline (push) Blocked by required conditions
Test Suite / playwright-smoke (push) Blocked by required conditions
Deploy Development / deploy (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
1b55d8558c
commit
11e36e674e
|
|
@ -7,6 +7,7 @@ export function InitiativeInboxPage() {
|
|||
initiative,
|
||||
initiativeId,
|
||||
backlogItems,
|
||||
dataSlices,
|
||||
roadmapItems,
|
||||
featureParentActions,
|
||||
workCycles,
|
||||
|
|
@ -31,8 +32,6 @@ export function InitiativeInboxPage() {
|
|||
return <LoadingState message="Lade Eingang …" />
|
||||
}
|
||||
|
||||
const isProductArchetype = initiative?.archetype_key === 'initiative.product'
|
||||
|
||||
return (
|
||||
<>
|
||||
{error && <p className="error">{error}</p>}
|
||||
|
|
@ -43,7 +42,7 @@ export function InitiativeInboxPage() {
|
|||
featureActions={featureParentActions}
|
||||
workCycles={workCycles}
|
||||
activeWorkCycle={activeWorkCycle}
|
||||
sprintPlanningEnabled={isProductArchetype}
|
||||
sprintPlanningEnabled={dataSlices.includes('work_cycles')}
|
||||
canManage={capabilities.has('kairo.backlog.manage')}
|
||||
onCreate={handleCreateBacklog}
|
||||
onUpdate={handleUpdateBacklog}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useMemo } from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { RequireInitiativeScope } from '../../components/RequireInitiativeScope.jsx'
|
||||
import { ScopedInitiativeProvider } from '../../layout/ScopedInitiativeProvider.jsx'
|
||||
|
|
@ -41,11 +41,23 @@ function WorkSprintInner() {
|
|||
handleMoveActionToSprint,
|
||||
handleUnplanAction,
|
||||
initiative,
|
||||
operatingContext,
|
||||
dataSlices,
|
||||
steeringSnapshot,
|
||||
steeringSnapshotLoading,
|
||||
visibleActions,
|
||||
reloadSlices,
|
||||
} = ops
|
||||
|
||||
const supportsWorkCycles = dataSlices.includes('work_cycles')
|
||||
const isContinuousProduct = operatingContext?.method_key === 'continuous_product'
|
||||
|
||||
useEffect(() => {
|
||||
if (initiativeId && !steeringSnapshot && !steeringSnapshotLoading) {
|
||||
reloadSlices(['steering_snapshot'])
|
||||
}
|
||||
}, [initiativeId, steeringSnapshot, steeringSnapshotLoading, reloadSlices])
|
||||
|
||||
const defaultWorkCycleId = activeWorkCycle?.id || ''
|
||||
|
||||
const createAction = useMemo(
|
||||
|
|
@ -62,7 +74,7 @@ function WorkSprintInner() {
|
|||
return <LoadingState message="Lade Sprint-Backlog …" />
|
||||
}
|
||||
|
||||
const isProductArchetype = initiative?.archetype_key === 'initiative.product'
|
||||
const isProductArchetype = isContinuousProduct && supportsWorkCycles
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -187,8 +199,8 @@ function WorkSprintInner() {
|
|||
</>
|
||||
)}
|
||||
|
||||
{!activeWorkCycle && !isProductArchetype && (
|
||||
<EmptyState message="Sprint-Ausführung ist für Product-Vorhaben — wähle ein passendes Vorhaben im Scope." />
|
||||
{!activeWorkCycle && !supportsWorkCycles && (
|
||||
<EmptyState message="Sprint-Ausführung ist für diesen Vorhabentyp nicht verfügbar." />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
30
frontend/src/registry/OperatingRouteGate.jsx
Normal file
30
frontend/src/registry/OperatingRouteGate.jsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { Navigate, useLocation } from 'react-router-dom'
|
||||
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
|
||||
import { useMethodUiContext } from '../hooks/useMethodUiContext.js'
|
||||
import {
|
||||
isRouteAllowedForProfile,
|
||||
resolveRedirectForDisallowedRoute,
|
||||
} from './resolveOperatingProfile.js'
|
||||
|
||||
/**
|
||||
* AP2.3d — Redirect wenn Route für Archetyp/Slice unpassend.
|
||||
*/
|
||||
export function OperatingRouteGate({ children }) {
|
||||
const location = useLocation()
|
||||
const { hrefWithScope, initiativeId } = useProgramScope()
|
||||
const { operatingContext, loading } = useMethodUiContext()
|
||||
|
||||
if (!initiativeId || loading) {
|
||||
return children
|
||||
}
|
||||
|
||||
const input = { operatingContext }
|
||||
const path = location.pathname
|
||||
|
||||
if (isRouteAllowedForProfile(path, input)) {
|
||||
return children
|
||||
}
|
||||
|
||||
const redirect = resolveRedirectForDisallowedRoute(path, input)
|
||||
return <Navigate to={hrefWithScope(redirect)} replace />
|
||||
}
|
||||
|
|
@ -144,6 +144,38 @@ export const MODE_ROUTE_COMPONENTS = {
|
|||
controlJourney: ControlJourneyPage,
|
||||
}
|
||||
|
||||
/** Modus-Routen mit Slice-Gating (AP2.3d). */
|
||||
export const MODE_ROUTES = [
|
||||
{ path: 'today', componentKey: 'workToday', requiredSlices: ['actions'] },
|
||||
{ path: 'mine', componentKey: 'workMine', requiredSlices: ['actions'] },
|
||||
{
|
||||
path: 'sprint',
|
||||
componentKey: 'workSprint',
|
||||
requiredSlices: ['work_cycles'],
|
||||
routePath: '/work/sprint',
|
||||
},
|
||||
{ path: 'portfolio', componentKey: 'planPortfolio', requiredSlices: [] },
|
||||
{ path: 'profile', componentKey: 'planProfile', requiredSlices: [] },
|
||||
{ path: 'structure', componentKey: 'planStructure', requiredSlices: ['projects'] },
|
||||
{ path: 'gates', componentKey: 'planGates', requiredSlices: ['roadmap'] },
|
||||
{ path: 'inbox', componentKey: 'planInbox', requiredSlices: ['backlog'] },
|
||||
{
|
||||
path: 'sprint',
|
||||
componentKey: 'planSprint',
|
||||
requiredSlices: ['work_cycles'],
|
||||
routePath: '/plan/sprint',
|
||||
},
|
||||
{ path: 'work', componentKey: 'planWork', requiredSlices: ['actions'] },
|
||||
{ path: 'status', componentKey: 'controlStatus', requiredSlices: [] },
|
||||
{ path: 'plan-ist', componentKey: 'controlPlanIst', requiredSlices: ['roadmap'] },
|
||||
{
|
||||
path: 'journey',
|
||||
componentKey: 'controlJourney',
|
||||
requiredSlices: ['roadmap'],
|
||||
routePath: '/control/journey',
|
||||
},
|
||||
]
|
||||
|
||||
export {
|
||||
WorkLayout,
|
||||
WorkIndexRedirect,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user