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

149 lines
4.7 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,
editingAction,
setEditingAction,
actionContextById,
actors,
actorsLoading,
actorsError,
actorsUsedFallback,
reloadActors,
handleCreateAction,
handleUpdateAction,
handleQuickStatus,
handleCreateBlockerForAction,
initiative,
steeringSnapshot,
steeringSnapshotLoading,
} = 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">
<EmptyState
message="Kein aktiver Sprint — lege unter Plan → Sprint eine Zeitbox an und aktiviere sie."
/>
<p>
<Link to={hrefWithScope('/plan/sprint')} className="link-inline">
Zur Sprint-Planung
</Link>
</p>
</section>
)}
{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={editingAction?.id}
onEditAction={setEditingAction}
onCancelEdit={() => setEditingAction(null)}
onCreateAction={createAction}
onUpdateAction={handleUpdateAction}
onQuickStatus={handleQuickStatus}
onCreateBlockerForAction={handleCreateBlockerForAction}
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}
/>
</>
)}
</>
)
}
export function WorkSprintPage() {
return (
<RequireInitiativeScope lead="Sprint-Backlog ist vorhabenspezifisch — wähle ein Product-Vorhaben im Scope.">
<ScopedInitiativeProvider>
<WorkSprintInner />
</ScopedInitiativeProvider>
</RequireInitiativeScope>
)
}