Kairo-Jinkendo/frontend/src/pages/modes/WorkSprintPage.jsx
Lars c79bfa844f
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
AP2.3f: Restschuld ? uiFeatures, Route-Registry und slice-bewusste Mutationen.
Archetyp-Profile und Seitenlogik auf Operating Context migriert; methodUiDefaults nur noch Fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-25 10:52:12 +02:00

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>
)
}