Kairo-Jinkendo/frontend/src/pages/modes/WorkSprintPage.jsx

206 lines
7.1 KiB
React
Raw Normal View History

import { useMemo } from 'react'
import { Link } from 'react-router-dom'
import { RequireInitiativeScope } from '../../components/RequireInitiativeScope.jsx'
import { ScopedInitiativeProvider } from '../../layout/ScopedInitiativeProvider.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,
steeringSnapshot,
steeringSnapshotLoading,
visibleActions,
} = ops
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 = initiative?.archetype_key === 'initiative.product'
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>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 && !isProductArchetype && (
<EmptyState message="Sprint-Ausführung ist für Product-Vorhaben — wähle ein passendes Vorhaben im Scope." />
)}
</>
)
}
export function WorkSprintPage() {
return (
<RequireInitiativeScope lead="Sprint-Backlog ist vorhabenspezifisch — wähle ein Product-Vorhaben im Scope.">
<ScopedInitiativeProvider>
<WorkSprintInner />
</ScopedInitiativeProvider>
</RequireInitiativeScope>
)
}