diff --git a/actions/sprint-draft.ts b/actions/sprint-draft.ts index ddbe395..37beb54 100644 --- a/actions/sprint-draft.ts +++ b/actions/sprint-draft.ts @@ -26,8 +26,8 @@ const StoryOverridesSchema = z.object({ const DraftSchema = z.object({ goal: z.string().min(1), - startAt: z.string().datetime().optional(), - endAt: z.string().datetime().optional(), + startAt: z.string().date().optional(), + endAt: z.string().date().optional(), pbiIntent: z.record(z.string(), z.enum(['all', 'none'])).default({}), storyOverrides: z.record(z.string(), StoryOverridesSchema).default({}), }).strict() diff --git a/app/(app)/products/[id]/page.tsx b/app/(app)/products/[id]/page.tsx index 386fe56..8ea05fe 100644 --- a/app/(app)/products/[id]/page.tsx +++ b/app/(app)/products/[id]/page.tsx @@ -15,7 +15,8 @@ import { UrlTaskSync } from '@/components/backlog/url-task-sync' import { TaskDialog } from '@/app/_components/tasks/task-dialog' import { EditTaskLoader } from '@/app/_components/tasks/edit-task-loader' import { TaskDialogSkeleton } from '@/app/_components/tasks/task-dialog-skeleton' -import { StartSprintButton } from '@/components/sprint/start-sprint-button' +import { NewSprintTrigger } from '@/components/backlog/new-sprint-trigger' +import { SprintDraftBanner } from '@/components/backlog/sprint-draft-banner' import { ActivateProductButton } from '@/components/shared/activate-product-button' import { EditProductButton } from '@/components/products/edit-product-button' import { SprintSwitcher } from '@/components/shared/sprint-switcher' @@ -118,13 +119,12 @@ export default async function ProductBacklogPage({ params, searchParams }: Props {!isActiveProduct && ( )} - {hasOpenSprint ? ( + {hasOpenSprint && ( Sprint actief → - ) : ( - !isDemo && )} + {!isDemo && } {!isDemo && product.user_id === session.userId && ( + {/* Sprint definition banner (state A′) */} + + {/* Split pane */}
void +} + +function todayLocalDate(): string { + return new Date().toLocaleDateString('en-CA') +} + +function plusWeeks(weeks: number): string { + const d = new Date() + d.setDate(d.getDate() + weeks * 7) + return d.toLocaleDateString('en-CA') +} + +export function NewSprintMetadataDialog({ + open, + productId, + onOpenChange, +}: NewSprintMetadataDialogProps) { + const [sprintGoal, setSprintGoal] = useState('') + const [startDate, setStartDate] = useState(todayLocalDate()) + const [endDate, setEndDate] = useState(plusWeeks(2)) + const [error, setError] = useState(null) + const [dirty, setDirty] = useState(false) + const [isPending, startTransition] = useTransition() + const formRef = useRef(null) + const setPendingSprintDraft = useUserSettingsStore( + (s) => s.setPendingSprintDraft, + ) + + function reset() { + setSprintGoal('') + setStartDate(todayLocalDate()) + setEndDate(plusWeeks(2)) + setError(null) + setDirty(false) + } + + const closeGuard = useDirtyCloseGuard(dirty, () => { + onOpenChange(false) + reset() + }) + + function handleSubmit(e: React.FormEvent) { + e.preventDefault() + const goal = sprintGoal.trim() + if (!goal) return + setError(null) + startTransition(async () => { + try { + await setPendingSprintDraft(productId, { + goal, + startAt: startDate || undefined, + endAt: endDate || undefined, + pbiIntent: {}, + storyOverrides: {}, + }) + reset() + onOpenChange(false) + } catch (err) { + const message = + err instanceof Error ? err.message : 'Onbekende fout bij opslaan' + setError(message) + toast.error(message) + } + }) + } + + const handleKeyDown = useDialogSubmitShortcut(() => + formRef.current?.requestSubmit(), + ) + + return ( + <> + { + if (!o) closeGuard.attemptClose() + else onOpenChange(o) + }} + > + +
+ + Nieuwe sprint + +

+ Geef het sprint-doel en periode op. Je selecteert daarna PBI's + en stories via vinkjes in de backlog. +

+
+ +
setDirty(true)} + className="flex-1 overflow-y-auto px-6 py-6 space-y-6" + > +
+ +