Some checks failed
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Failing after 3m9s
Test Suite / k6 /api/health Baseline (push) Has been skipped
Test Suite / playwright-smoke (push) Has been skipped
Test Suite / lint-backend (push) Successful in 3s
Test Suite / compose-smoke (push) Has been skipped
Archetyp-Profile und Seitenlogik auf Operating Context migriert; methodUiDefaults nur noch Fallback. Co-authored-by: Cursor <cursoragent@cursor.com>
223 lines
7.5 KiB
JavaScript
223 lines
7.5 KiB
JavaScript
import { useEffect, useMemo } from 'react'
|
|
import { Link } from 'react-router-dom'
|
|
import { RequireInitiativeScope } from '../../components/RequireInitiativeScope.jsx'
|
|
import { InitiativeActionsHub } from '../../components/InitiativeActionsHub.jsx'
|
|
import { NextActionWidget } from '../../widgets/NextActionWidget.jsx'
|
|
import { LoadingState } from '../../components/LoadingState.jsx'
|
|
import { EmptyState } from '../../components/EmptyState.jsx'
|
|
import { useInitiativeOperations } from '../../context/InitiativeOperationsContext.jsx'
|
|
import { useProgramScope } from '../../context/ProgramScopeContext.jsx'
|
|
import { scopedPath } from '../../utils/routes.js'
|
|
|
|
function WorkSprintInner() {
|
|
const { initiativeId, hrefWithScope } = useProgramScope()
|
|
const ops = useInitiativeOperations()
|
|
const {
|
|
activeWorkCycle,
|
|
sprintActions,
|
|
projects,
|
|
roadmapItems,
|
|
capabilities,
|
|
hideDone,
|
|
setHideDone,
|
|
loading,
|
|
error,
|
|
formBusy,
|
|
showActionForm,
|
|
setShowActionForm,
|
|
editingActionId,
|
|
setEditingActionId,
|
|
actionContextById,
|
|
actors,
|
|
actorsLoading,
|
|
actorsError,
|
|
actorsUsedFallback,
|
|
reloadActors,
|
|
handleCreateAction,
|
|
handleUpdateAction,
|
|
handleQuickStatus,
|
|
handleCreateBlockerForAction,
|
|
handleMoveActionToSprint,
|
|
handleUnplanAction,
|
|
initiative,
|
|
operatingContext,
|
|
dataSlices,
|
|
uiFeatures,
|
|
steeringSnapshot,
|
|
steeringSnapshotLoading,
|
|
visibleActions,
|
|
reloadSlices,
|
|
} = ops
|
|
|
|
const supportsWorkCycles = dataSlices.includes('work_cycles')
|
|
const isContinuousProductMode = uiFeatures.continuousProductWorkMode && supportsWorkCycles
|
|
const loadSteeringSnapshot = uiFeatures.steeringSnapshotOnWorkSprint
|
|
|
|
useEffect(() => {
|
|
if (initiativeId && loadSteeringSnapshot && !steeringSnapshot && !steeringSnapshotLoading) {
|
|
reloadSlices(['steering_snapshot'])
|
|
}
|
|
}, [
|
|
initiativeId,
|
|
loadSteeringSnapshot,
|
|
steeringSnapshot,
|
|
steeringSnapshotLoading,
|
|
reloadSlices,
|
|
])
|
|
|
|
const defaultWorkCycleId = activeWorkCycle?.id || ''
|
|
|
|
const createAction = useMemo(
|
|
() => async (payload) => {
|
|
await handleCreateAction({
|
|
...payload,
|
|
work_cycle_id: payload.work_cycle_id || defaultWorkCycleId || undefined,
|
|
})
|
|
},
|
|
[handleCreateAction, defaultWorkCycleId],
|
|
)
|
|
|
|
if (loading) {
|
|
return <LoadingState message="Lade Sprint-Backlog …" />
|
|
}
|
|
|
|
const isProductArchetype = isContinuousProductMode
|
|
|
|
return (
|
|
<>
|
|
{error && <p className="error">{error}</p>}
|
|
|
|
{!activeWorkCycle && isProductArchetype && (
|
|
<>
|
|
<section className="card sprint-context-banner">
|
|
<h2>Product-Ist (ohne aktiven Sprint)</h2>
|
|
<p className="section-lead muted">
|
|
Kein aktiver Sprint — Kairo steuert nach{' '}
|
|
<strong>{operatingContext?.method_key || 'continuous_product'}</strong> (wirkungsvollster Schritt). Sprint-Planung
|
|
unter{' '}
|
|
<Link to={hrefWithScope('/plan/sprint')} className="link-inline">
|
|
Plan → Sprint
|
|
</Link>
|
|
.
|
|
</p>
|
|
</section>
|
|
|
|
<NextActionWidget
|
|
scope="initiative"
|
|
initiativeId={initiativeId}
|
|
items={steeringSnapshot?.next_actions}
|
|
loading={steeringSnapshotLoading}
|
|
embedded
|
|
title="Nächster Schritt (Product)"
|
|
subtitle="Empfehlung ohne Sprint-Zeitbox — committete Arbeitspakete im Vorhaben."
|
|
/>
|
|
|
|
<InitiativeActionsHub
|
|
initiativeId={initiativeId}
|
|
actions={visibleActions}
|
|
projects={projects}
|
|
roadmapItems={roadmapItems}
|
|
actionContextById={actionContextById}
|
|
hideDone={hideDone}
|
|
onHideDoneChange={setHideDone}
|
|
showForm={showActionForm}
|
|
onToggleForm={() => setShowActionForm((v) => !v)}
|
|
editingActionId={editingActionId}
|
|
onEditAction={setEditingActionId}
|
|
onCancelEdit={() => setEditingActionId(null)}
|
|
onCreateAction={handleCreateAction}
|
|
onUpdateAction={handleUpdateAction}
|
|
onQuickStatus={handleQuickStatus}
|
|
onCreateBlockerForAction={handleCreateBlockerForAction}
|
|
onMoveToSprint={handleMoveActionToSprint}
|
|
onUnplanToBacklog={handleUnplanAction}
|
|
canManage={capabilities.has('kairo.action.manage')}
|
|
canManageBlocker={capabilities.has('kairo.blocker.manage')}
|
|
formBusy={formBusy}
|
|
actors={actors}
|
|
actorsLoading={actorsLoading}
|
|
actorsError={actorsError}
|
|
actorsUsedFallback={actorsUsedFallback}
|
|
onReloadActors={reloadActors}
|
|
workCycles={ops.workCycles}
|
|
activeWorkCycle={activeWorkCycle}
|
|
sectionTitle="Committete Arbeitspakete"
|
|
sectionLead="Ohne aktiven Sprint — alle offenen Arbeitspakete im Product-Vorhaben."
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{activeWorkCycle && (
|
|
<>
|
|
<section className="card sprint-context-banner">
|
|
<h2>Sprint-Backlog</h2>
|
|
<p className="section-lead muted">
|
|
<strong>{activeWorkCycle.title}</strong>
|
|
{activeWorkCycle.goal_description && ` — ${activeWorkCycle.goal_description}`}
|
|
{' · '}
|
|
Product Backlog triagierst du unter{' '}
|
|
<Link to={scopedPath('/plan/inbox', { initiativeId })} className="link-inline">
|
|
Plan → Eingang
|
|
</Link>
|
|
.
|
|
</p>
|
|
</section>
|
|
|
|
<NextActionWidget
|
|
scope="initiative"
|
|
initiativeId={initiativeId}
|
|
items={steeringSnapshot?.next_actions}
|
|
loading={steeringSnapshotLoading}
|
|
embedded
|
|
title="Nächster Schritt im Sprint"
|
|
/>
|
|
|
|
<InitiativeActionsHub
|
|
initiativeId={initiativeId}
|
|
actions={sprintActions}
|
|
projects={projects}
|
|
roadmapItems={roadmapItems}
|
|
actionContextById={actionContextById}
|
|
hideDone={hideDone}
|
|
onHideDoneChange={setHideDone}
|
|
showForm={showActionForm}
|
|
onToggleForm={() => setShowActionForm((v) => !v)}
|
|
editingActionId={editingActionId}
|
|
onEditAction={setEditingActionId}
|
|
onCancelEdit={() => setEditingActionId(null)}
|
|
onCreateAction={createAction}
|
|
onUpdateAction={handleUpdateAction}
|
|
onQuickStatus={handleQuickStatus}
|
|
onCreateBlockerForAction={handleCreateBlockerForAction}
|
|
onMoveToSprint={handleMoveActionToSprint}
|
|
onUnplanToBacklog={handleUnplanAction}
|
|
canManage={capabilities.has('kairo.action.manage')}
|
|
canManageBlocker={capabilities.has('kairo.blocker.manage')}
|
|
formBusy={formBusy}
|
|
actors={actors}
|
|
actorsLoading={actorsLoading}
|
|
actorsError={actorsError}
|
|
actorsUsedFallback={actorsUsedFallback}
|
|
onReloadActors={reloadActors}
|
|
workCycles={ops.workCycles}
|
|
activeWorkCycle={activeWorkCycle}
|
|
allActions={ops.actions}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{!activeWorkCycle && !supportsWorkCycles && (
|
|
<EmptyState message="Sprint-Ausführung ist für diesen Vorhabentyp nicht verfügbar." />
|
|
)}
|
|
</>
|
|
)
|
|
}
|
|
|
|
export function WorkSprintPage() {
|
|
return (
|
|
<RequireInitiativeScope lead="Sprint-Backlog ist vorhabenspezifisch — wähle ein passendes Vorhaben im Scope.">
|
|
<WorkSprintInner />
|
|
</RequireInitiativeScope>
|
|
)
|
|
}
|