feat(PBI-79/ST-1338): state B vinkjes-UI + 'Sprint opslaan'-knop met teller

State B (actieve sprint geselecteerd, geen draft) hangt nu aan dezelfde
vinkje-UI als state A′, maar muteert de transient pending-buffer in plaats
van de draft.

- PbiList: nieuwe prop activeSprintId. selectionMode = hasDraft ||
  stateBMode. togglePbiInDraft routeert naar upsertPbiIntent (A′) of bulk-
  toggleStorySprintMembership over eligible child-stories (B, skip blocked).
- StoryPanel: idem prop activeSprintId. StoryBlockWithCherrypick muteert
  draft via upsertStoryOverride in A′ of pending buffer via
  toggleStorySprintMembership in B (cross-sprint blocked = disabled).
- SaveSprintButton (nieuw): client component in page header, alleen
  zichtbaar als er een actieve sprint is. Disabled bij clean buffer,
  enabled met teller bij dirty. Klikken calls commitSprintMembershipAction
  → applyMembershipCommitResult gericht in store + toast bij conflicts.
- page.tsx: activeSprintItem.id wordt doorgegeven aan PbiList, StoryPanel
  en SaveSprintButton.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-11 17:07:21 +02:00
parent 4c6e99958b
commit 117616f28b
4 changed files with 191 additions and 45 deletions

View file

@ -17,6 +17,7 @@ import { EditTaskLoader } from '@/app/_components/tasks/edit-task-loader'
import { TaskDialogSkeleton } from '@/app/_components/tasks/task-dialog-skeleton'
import { NewSprintTrigger } from '@/components/backlog/new-sprint-trigger'
import { SprintDraftBanner } from '@/components/backlog/sprint-draft-banner'
import { SaveSprintButton } from '@/components/backlog/save-sprint-button'
import { ActivateProductButton } from '@/components/shared/activate-product-button'
import { EditProductButton } from '@/components/products/edit-product-button'
import { SprintSwitcher } from '@/components/shared/sprint-switcher'
@ -124,6 +125,9 @@ export default async function ProductBacklogPage({ params, searchParams }: Props
Sprint actief
</Link>
)}
{activeSprintItem && !isDemo && (
<SaveSprintButton activeSprintId={activeSprintItem.id} />
)}
{!isDemo && <NewSprintTrigger productId={id} isDemo={isDemo} />}
{!isDemo && product.user_id === session.userId && (
<EditProductButton
@ -171,11 +175,13 @@ export default async function ProductBacklogPage({ params, searchParams }: Props
key="pbi"
productId={id}
isDemo={isDemo}
activeSprintId={activeSprintItem?.id ?? null}
/>,
<StoryPanel
key="story"
productId={id}
isDemo={isDemo}
activeSprintId={activeSprintItem?.id ?? null}
/>,
<TaskPanel
key="tasks"