feat(PBI-75): sprint task-edit client-side via workspace-store (#183)

Klik op een taak in het sprint-scherm opent de edit-dialog nu
client-side via setActiveTask op de sprint-workspace-store.
Geen URL-navigatie, geen volledige server re-render — alleen
GET /api/tasks/{id} voor het detail. SSE propageert server-saves
automatisch terug.

- TaskDialog: optionele onClose/onSaved callbacks (closePath
  optional gemaakt — backwards compatible)
- SprintTaskDialogMount: nieuwe client-component die
  selectActiveTask consumeert en TaskDialog rendert
- SprintUrlTaskSync: deeplink (?editTask=<id>) → store
- Sprint page: mounts toegevoegd, editTask searchParam +
  EditTaskLoader-Suspense verwijderd
- TaskList.openEditDialog roept setActiveTask aan ipv router.push
- Vitest integratie-test voor SprintTaskDialogMount

Out-of-scope (follow-up PBIs): newTask-flow, mobile, product-backlog.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-10 08:21:42 +02:00 committed by GitHub
parent 3b5cee823c
commit a9b53dedf0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 335 additions and 25 deletions

View file

@ -60,7 +60,9 @@ interface TaskDialogProps {
task?: TaskDialogTask
storyId?: string
productId: string
closePath: string
closePath?: string
onClose?: () => void
onSaved?: (taskId: string) => void
isDemo?: boolean
}
@ -81,7 +83,7 @@ const textareaClass = cn(
'overflow-y-auto',
)
export function TaskDialog({ task, storyId, productId, closePath, isDemo = false }: TaskDialogProps) {
export function TaskDialog({ task, storyId, productId, closePath, onClose, onSaved, isDemo = false }: TaskDialogProps) {
const router = useRouter()
const [isPending, startTransition] = useTransition()
const [confirmDelete, setConfirmDelete] = useState(false)
@ -100,11 +102,12 @@ export function TaskDialog({ task, storyId, productId, closePath, isDemo = false
},
})
function handleClose() {
router.push(closePath)
function close() {
if (onClose) { onClose(); return }
if (closePath) router.push(closePath)
}
const closeGuard = useDirtyCloseGuard(form.formState.isDirty, handleClose)
const closeGuard = useDirtyCloseGuard(form.formState.isDirty, close)
const handleKeyDown = useDialogSubmitShortcut(() => form.handleSubmit(onSubmit)())
function onSubmit(data: TaskInput) {
@ -117,7 +120,8 @@ export function TaskDialog({ task, storyId, productId, closePath, isDemo = false
if (result.ok) {
toast.success(isEdit ? 'Taak opgeslagen' : 'Taak aangemaakt')
router.push(closePath)
onSaved?.(result.task.id)
close()
return
}
@ -152,7 +156,7 @@ export function TaskDialog({ task, storyId, productId, closePath, isDemo = false
const result = await deleteTask(task.id, { productId })
if (result.ok) {
toast.success('Taak verwijderd')
router.push(closePath)
close()
return
}
if (result.code === 403) {