'use client' import { useState, useTransition } from 'react' import { toast } from 'sonner' import { startSprintRunAction, resumeSprintAction, resumePausedSprintRunAction, cancelSprintRunAction, type PreFlightBlocker, } from '@/actions/sprint-runs' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { type PauseContext, pauseReasonLabel } from '@/lib/pause-context' type SprintStatusValue = 'OPEN' | 'CLOSED' | 'ARCHIVED' | 'FAILED' type SprintRunStatusValue = | 'QUEUED' | 'RUNNING' | 'PAUSED' | 'DONE' | 'FAILED' | 'CANCELLED' | null interface Props { sprintId: string productId: string sprintStatus: SprintStatusValue activeSprintRunId: string | null activeSprintRunStatus: SprintRunStatusValue pauseContext: PauseContext | null isDemo: boolean } const BLOCKER_LABELS: Record = { task_no_plan: 'Task zonder implementation plan', open_question: 'Openstaande vraag aan jou', pbi_blocked: 'PBI is geblokkeerd of gefaald', task_cross_repo: 'Task met afwijkende repo (niet toegestaan in SPRINT_BATCH)', } function blockerHref(productId: string, blocker: PreFlightBlocker): string { switch (blocker.type) { case 'task_no_plan': return `/products/${productId}/sprint?editTask=${blocker.id}` case 'open_question': return `/products/${productId}/sprint` case 'pbi_blocked': return `/products/${productId}` case 'task_cross_repo': return `/products/${productId}/sprint?editTask=${blocker.id}` } } export function SprintRunControls({ sprintId, productId, sprintStatus, activeSprintRunId, activeSprintRunStatus, pauseContext, isDemo, }: Props) { const [pending, startTransition] = useTransition() const [blockers, setBlockers] = useState(null) const hasActiveRun = activeSprintRunId !== null && (activeSprintRunStatus === 'QUEUED' || activeSprintRunStatus === 'RUNNING' || activeSprintRunStatus === 'PAUSED') const canStart = sprintStatus === 'OPEN' && !hasActiveRun const canResume = sprintStatus === 'FAILED' const canResumePaused = activeSprintRunStatus === 'PAUSED' && pauseContext !== null const canCancel = hasActiveRun function handleStart() { startTransition(async () => { const result = await startSprintRunAction({ sprint_id: sprintId }) if (result.ok) { toast.success(`Sprint gestart (${result.jobs_count} taak(s) klaar)`) } else if (result.error === 'PRE_FLIGHT_BLOCKED' && 'blockers' in result) { setBlockers(result.blockers) } else { toast.error(result.error) } }) } function handleResume() { startTransition(async () => { const result = await resumeSprintAction({ sprint_id: sprintId }) if (result.ok) { toast.success(`Sprint hervat (${result.jobs_count} taak(s) klaar)`) } else if (result.error === 'PRE_FLIGHT_BLOCKED' && 'blockers' in result) { setBlockers(result.blockers) } else { toast.error(result.error) } }) } function handleResumePaused() { if (!activeSprintRunId || !pauseContext) return if ( !confirm( `Sprint hervatten? Bevestig dat het ${pauseReasonLabel( pauseContext.pause_reason, ).toLowerCase()} is opgelost.`, ) ) return startTransition(async () => { const result = await resumePausedSprintRunAction({ sprint_run_id: activeSprintRunId, }) if (result.ok) toast.success('Sprint hervat') else toast.error(result.error) }) } function handleCancel() { if (!activeSprintRunId) return if (!confirm('Sprint annuleren? Openstaande taken blijven TO_DO.')) return startTransition(async () => { const result = await cancelSprintRunAction({ sprint_run_id: activeSprintRunId }) if (result.ok) toast.success('Sprint geannuleerd') else toast.error(result.error) }) } return ( <> {canResumePaused && pauseContext && (
Gepauzeerd: {pauseReasonLabel(pauseContext.pause_reason)}
{pauseContext.pr_url} {pauseContext.conflict_files.length > 0 && (
    {pauseContext.conflict_files.slice(0, 5).map((f) => (
  • · {f}
  • ))} {pauseContext.conflict_files.length > 5 && (
  • · + {pauseContext.conflict_files.length - 5} meer
  • )}
)}
)}
{canStart && ( )} {canResume && ( )} {canCancel && ( )}
{ if (!open) setBlockers(null) }}> Sprint kan nog niet starten Los eerst onderstaande punten op. Klik op een item om er direct naar te navigeren.
    {blockers?.map((b, i) => (
  • {BLOCKER_LABELS[b.type]}
    {b.label}
  • ))}
) }