feat(sprint-dialogs): conform aan dialog-pattern + entity-profile

Story 5 van PBI "Alle dialogen conform docs/patterns/dialog.md".

- lib/schemas/sprint.ts — gedeelde zod-schemas (create/dates/goal)
- actions/sprints.ts — code+fieldErrors voor 422; code: 403 voor
  auth/demo errors
- StartSprintButton dialog: useDirtyCloseGuard, useDialogSubmitShortcut,
  entityDialog* layout-classes; DemoTooltip op trigger; veld-niveau
  errors via fieldErrors
- SprintHeader's date- en complete-dialogen: zelfde behandeling; date-
  dialog krijgt dirty-guard, complete-dialog krijgt DemoTooltip op
  bevestigen
- docs/specs/dialogs/sprint.md — entity-profile dat alle drie de modes
  documenteert; consolidatie naar één SprintDialog component bewust
  uitgesteld
- Sprint-dates tests aangepast aan nieuwe action-shape

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-04 07:30:46 +02:00
parent 01e77fc560
commit 784791d8f9
7 changed files with 320 additions and 125 deletions

38
lib/schemas/sprint.ts Normal file
View file

@ -0,0 +1,38 @@
import { z } from 'zod'
const dateField = z.string().optional().nullable().transform(v => (v && v.trim() !== '' ? new Date(v) : null))
export function validateDateOrder(
data: { start_date: Date | null; end_date: Date | null },
ctx: z.RefinementCtx,
) {
if (data.start_date && data.end_date && data.end_date < data.start_date) {
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['end_date'], message: 'Einddatum moet na startdatum liggen' })
}
}
export const createSprintSchema = z
.object({
productId: z.string(),
sprint_goal: z.string().min(1, 'Sprint Goal is verplicht').max(500),
start_date: dateField,
end_date: dateField,
})
.superRefine(validateDateOrder)
export const updateSprintDatesSchema = z
.object({
id: z.string(),
start_date: dateField,
end_date: dateField,
})
.superRefine(validateDateOrder)
export const updateSprintGoalSchema = z.object({
id: z.string(),
sprint_goal: z.string().min(1, 'Sprint Goal is verplicht').max(500),
})
export type CreateSprintInput = z.infer<typeof createSprintSchema>
export type UpdateSprintDatesInput = z.infer<typeof updateSprintDatesSchema>
export type UpdateSprintGoalInput = z.infer<typeof updateSprintGoalSchema>