feat(ST-354): add bulk-claim button to Sprint Backlog panel header

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-04-26 16:26:07 +02:00
parent 358c88a9d9
commit 802aa58157

View file

@ -15,7 +15,7 @@ import { PanelNavBar } from '@/components/shared/panel-nav-bar'
import { UserAvatar } from '@/components/shared/user-avatar'
import { DemoTooltip } from '@/components/shared/demo-tooltip'
import { useSprintStore } from '@/stores/sprint-store'
import { claimStoryAction, unclaimStoryAction, reassignStoryAction } from '@/actions/stories'
import { claimStoryAction, unclaimStoryAction, reassignStoryAction, claimAllUnassignedInActiveSprintAction } from '@/actions/stories'
import { cn } from '@/lib/utils'
const STATUS_COLORS: Record<string, string> = {
@ -222,6 +222,24 @@ export function SprintBacklogLeft({
}: SprintBacklogLeftProps) {
const { sprintStoryOrder } = useSprintStore()
const { setNodeRef, isOver } = useDroppable({ id: 'sprint-zone' })
const [isPending, startTransition] = useTransition()
const unassignedCount = stories.filter(s => s.assignee_id === null).length
const currentUserUsername = members.find(m => m.userId === currentUserId)?.username ?? null
function handleClaimAll() {
const unassigned = stories.filter(s => s.assignee_id === null)
unassigned.forEach(s => onAssigneeChange(s.id, currentUserId, currentUserUsername))
startTransition(async () => {
const result = await claimAllUnassignedInActiveSprintAction(productId)
if (!result.success) {
unassigned.forEach(s => onAssigneeChange(s.id, null, null))
toast.error(result.error ?? 'Claimen mislukt')
} else {
toast.success(`${result.count} ${result.count === 1 ? 'story' : 'stories'} geclaimd`)
}
})
}
const storyMap = Object.fromEntries(stories.map(s => [s.id, s]))
const order = sprintStoryOrder[sprintId] ?? stories.map(s => s.id)
@ -229,7 +247,20 @@ export function SprintBacklogLeft({
return (
<div className="flex flex-col h-full">
<PanelNavBar title="Sprint Backlog" />
<PanelNavBar
title="Sprint Backlog"
actions={
<DemoTooltip show={isDemo}>
<button
onClick={handleClaimAll}
disabled={isDemo || unassignedCount === 0 || isPending}
className="text-xs text-primary hover:text-primary/80 disabled:opacity-40 disabled:cursor-not-allowed whitespace-nowrap"
>
{isPending ? 'Claimen…' : `Claim ongeclaimd (${unassignedCount})`}
</button>
</DemoTooltip>
}
/>
<div
ref={setNodeRef}
className={cn(