feat(PBI-79/ST-1339): createSprintWithSelectionAction + banner wire-up
actions/sprints.ts:
- Nieuwe createSprintWithSelectionAction(productId, metadata, pbiIntent,
storyOverrides).
- Server-side intent-resolve:
1. Voor elke PBI met intent='all': fetch child-story-IDs minus
storyOverrides[pbi].remove.
2. Plus storyOverrides[*].add (cross-PBI cherrypick toegestaan).
- Eligibility-filter via partitionByEligibility (sprint_id IS NULL + status
!= DONE; stories in andere OPEN sprint → conflicts.crossSprint).
- Transactie wrapt sprint.create + story.updateMany (status='IN_SPRINT') +
task.updateMany (sprint_id cascade) — alles atomair.
- setActiveSprintInSettings na success.
- Return: { success, sprintId, affectedStoryIds, affectedPbiIds,
affectedTaskIds, conflicts: { notEligible, crossSprint } } of error.
components/backlog/sprint-definition-banner.tsx:
- 'Sprint aanmaken'-knop sluit aan op createSprintWithSelectionAction;
toast bij conflicts, success-toast anders, router.refresh() voor SSR
cycle. Pending draft wordt door de action zelf nog niet expliciet gewist
— dat gebeurt via revalidatePath en kan in ST-1340 finetuned worden.
Tests: __tests__/actions/create-sprint-with-selection.test.ts (6 cases)
dekken intent-resolve, override-respect, cross-sprint conflict, transactie-
binding van story.status + task.sprint_id, return-shape, en error-pad.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
947d970231
commit
d21011cdfa
3 changed files with 480 additions and 4 deletions
|
|
@ -1,6 +1,7 @@
|
|||
'use client'
|
||||
|
||||
import { useMemo, useState, useTransition } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { toast } from 'sonner'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
|
|
@ -16,6 +17,7 @@ import {
|
|||
import { useUserSettingsStore } from '@/stores/user-settings/store'
|
||||
import { useProductWorkspaceStore } from '@/stores/product-workspace/store'
|
||||
import type { PendingSprintDraft } from '@/lib/user-settings'
|
||||
import { createSprintWithSelectionAction } from '@/actions/sprints'
|
||||
import { debugProps } from '@/lib/debug'
|
||||
|
||||
interface SprintDefinitionBannerProps {
|
||||
|
|
@ -74,6 +76,7 @@ export function SprintDefinitionBanner({
|
|||
(s) => s.clearPendingSprintDraft,
|
||||
)
|
||||
const pbiSummary = useProductWorkspaceStore((s) => s.sprintMembership.pbiSummary)
|
||||
const router = useRouter()
|
||||
const [isPending, startTransition] = useTransition()
|
||||
const [confirmCancel, setConfirmCancel] = useState(false)
|
||||
|
||||
|
|
@ -100,10 +103,33 @@ export function SprintDefinitionBanner({
|
|||
}
|
||||
|
||||
function handleCreate() {
|
||||
// PBI-79 ST-1339 wires de createSprintWithSelectionAction in.
|
||||
toast.info(
|
||||
'Sprint aanmaken is nog niet aangesloten (wordt afgerond in ST-1339).',
|
||||
)
|
||||
startTransition(async () => {
|
||||
const result = await createSprintWithSelectionAction({
|
||||
productId,
|
||||
metadata: {
|
||||
goal: draft.goal,
|
||||
startAt: draft.startAt,
|
||||
endAt: draft.endAt,
|
||||
},
|
||||
pbiIntent: draft.pbiIntent,
|
||||
storyOverrides: draft.storyOverrides,
|
||||
})
|
||||
if ('error' in result) {
|
||||
toast.error(result.error)
|
||||
return
|
||||
}
|
||||
const { conflicts } = result
|
||||
if (conflicts.notEligible.length > 0) {
|
||||
toast.warning(
|
||||
`${conflicts.notEligible.length} stor${
|
||||
conflicts.notEligible.length === 1 ? 'y is' : 'ies zijn'
|
||||
} overgeslagen (al in een andere sprint of afgerond).`,
|
||||
)
|
||||
} else {
|
||||
toast.success('Sprint aangemaakt')
|
||||
}
|
||||
router.refresh()
|
||||
})
|
||||
}
|
||||
|
||||
const storyLabel = counts.hasUnknownTotal
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue