feat: ST-301-ST-312 M3 Sprint Backlog en Sprint Planning
- useSprintStore met sprintStoryOrder/taskOrder (ST-301) - Sprint aanmaken modal met Sprint Goal validatie (ST-302) - Sprint Backlog pagina SplitPane layout met Sprint Goal header (ST-303) - Stories toevoegen aan Sprint via knop in rechterpaneel (ST-304) - Sprint Backlog volgorde aanpassen via dnd-kit (ST-305) - Story uit Sprint verwijderen met status terug naar OPEN (ST-306) - Sprint Planning pagina SplitPane met story selectie (ST-307) - Taken aanmaken inline in rechterpaneel (ST-308) - Taak drag-and-drop verticaal met optimistische update (ST-309) - Taakstatus toggle TO_DO/IN_PROGRESS/DONE met voortgangsindicator (ST-310) - Taak inline bewerken en verwijderen (ST-311) - Sprint afronden dialoog met per-story Done/Terug keuze (ST-312) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4dd62c199c
commit
d92e548f88
12 changed files with 1480 additions and 6 deletions
96
components/sprint/start-sprint-button.tsx
Normal file
96
components/sprint/start-sprint-button.tsx
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
'use client'
|
||||
|
||||
import { useState, useActionState } from 'react'
|
||||
import { useFormStatus } from 'react-dom'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { createSprintAction } from '@/actions/sprints'
|
||||
|
||||
interface StartSprintButtonProps {
|
||||
productId: string
|
||||
}
|
||||
|
||||
function SubmitButton() {
|
||||
const { pending } = useFormStatus()
|
||||
return (
|
||||
<Button type="submit" disabled={pending}>
|
||||
{pending ? 'Aanmaken…' : 'Sprint starten'}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export function StartSprintButton({ productId }: StartSprintButtonProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
const [state, formAction] = useActionState(
|
||||
async (_prev: unknown, fd: FormData) => {
|
||||
const result = await createSprintAction(_prev, fd)
|
||||
if (result.success) {
|
||||
setOpen(false)
|
||||
router.push(`/products/${productId}/sprint`)
|
||||
}
|
||||
return result
|
||||
},
|
||||
undefined
|
||||
)
|
||||
|
||||
const globalError = typeof state?.error === 'string' ? state.error : undefined
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button size="sm" onClick={() => setOpen(true)}>
|
||||
Sprint starten
|
||||
</Button>
|
||||
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Nieuwe Sprint starten</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form action={formAction} className="space-y-4 p-1">
|
||||
<input type="hidden" name="productId" value={productId} />
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
Sprint Goal <span className="text-error">*</span>
|
||||
</label>
|
||||
<Textarea
|
||||
name="sprint_goal"
|
||||
required
|
||||
rows={3}
|
||||
placeholder="Wat wil je aan het einde van deze Sprint bereikt hebben?"
|
||||
autoFocus
|
||||
/>
|
||||
{typeof state?.error === 'object' && (state.error as Record<string, string[]>).sprint_goal && (
|
||||
<p className="text-xs text-error">{(state.error as Record<string, string[]>).sprint_goal[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{globalError && (
|
||||
<div className="bg-error-container text-error-container-foreground rounded-lg px-3 py-2 text-sm border-l-4 border-error">
|
||||
{globalError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button type="button" variant="ghost" onClick={() => setOpen(false)}>
|
||||
Annuleren
|
||||
</Button>
|
||||
<SubmitButton />
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue