'use client' import { useState, useActionState, useRef } from 'react' import { useRouter } from 'next/navigation' import { Button } from '@/components/ui/button' import { Textarea } from '@/components/ui/textarea' import { Dialog, DialogContent, DialogTitle, } from '@/components/ui/dialog' import { DemoTooltip } from '@/components/shared/demo-tooltip' import { useDirtyCloseGuard, DirtyCloseGuardDialog, } from '@/components/shared/use-dirty-close-guard' import { useDialogSubmitShortcut } from '@/components/shared/use-dialog-submit-shortcut' import { entityDialogContentClasses, entityDialogFooterClasses, entityDialogHeaderClasses, } from '@/components/shared/entity-dialog-layout' import { createSprintAction } from '@/actions/sprints' import { useSelectionStore } from '@/stores/selection-store' import { useBacklogStore } from '@/stores/backlog-store' interface StartSprintButtonProps { productId: string isDemo?: boolean } interface ActionResult { success?: boolean error?: string code?: number fieldErrors?: Record sprintId?: string } function todayLocalDate() { return new Date().toLocaleDateString('en-CA') } export function StartSprintButton({ productId, isDemo = false }: StartSprintButtonProps) { const [open, setOpen] = useState(false) const [dirty, setDirty] = useState(false) const formRef = useRef(null) const router = useRouter() const selectedPbiId = useSelectionStore((s) => s.selectedPbiId) const selectedPbi = useBacklogStore((s) => selectedPbiId ? s.pbis.find((p) => p.id === selectedPbiId) ?? null : null, ) const freeStoryCount = useBacklogStore((s) => { if (!selectedPbiId) return 0 return (s.storiesByPbi[selectedPbiId] ?? []).filter((story) => story.sprint_id === null).length }) const [state, formAction, pending] = useActionState( async (_prev, fd) => { const result = await createSprintAction(_prev, fd) as ActionResult if (result?.success) { setOpen(false) setDirty(false) router.refresh() } else if (result?.code !== 422 && result?.error) { // Toast handled by caller; here we just keep the form open } return result }, undefined, ) const fieldError = (field: string) => state?.fieldErrors?.[field]?.[0] const globalError = state?.code !== 422 ? state?.error : undefined const closeGuard = useDirtyCloseGuard(dirty, () => setOpen(false)) const handleKeyDown = useDialogSubmitShortcut(() => formRef.current?.requestSubmit()) return ( <> { if (!o) closeGuard.attemptClose(); else setOpen(o) }}>
Nieuwe Sprint starten
setDirty(true)} className="flex-1 overflow-y-auto px-6 py-6 space-y-6" > {selectedPbiId && } {!selectedPbi ? (
Geen PBI geselecteerd — de sprint wordt leeg aangemaakt. Je kunt later stories toevoegen via slepen.
) : freeStoryCount === 0 ? (
PBI {selectedPbi.code ?? selectedPbi.id.slice(0, 8)} heeft geen vrije stories (alle stories zitten al in een andere sprint of zijn afgerond) — de sprint wordt leeg aangemaakt.
) : (
{freeStoryCount} {freeStoryCount === 1 ? 'story' : 'stories'} van PBI {selectedPbi.code ?? selectedPbi.id.slice(0, 8)} {selectedPbi.title ? ` (${selectedPbi.title})` : ''} worden toegevoegd aan deze sprint.
)}