From cc6baeebc188d1ff4e09e93ed1b2bdb611273699 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Mon, 4 May 2026 09:10:05 +0200 Subject: [PATCH 1/3] feat(sprint): filter popover op Product-Backlog-kolom Spiegelt het filter-patroon van de Product Backlog-pagina (PbiList) naar de Sprint-Product-Backlog-kolom: prioriteit + status pills, actieve-filter chips, localStorage-persistentie. Bestaande collapse-/expand-/alleen-niet-klaar-knoppen blijven naast de popover staan. Co-Authored-By: Claude Opus 4.7 (1M context) --- components/sprint/sprint-backlog.tsx | 216 +++++++++++++++++++++++---- 1 file changed, 190 insertions(+), 26 deletions(-) diff --git a/components/sprint/sprint-backlog.tsx b/components/sprint/sprint-backlog.tsx index 3555912..5453896 100644 --- a/components/sprint/sprint-backlog.tsx +++ b/components/sprint/sprint-backlog.tsx @@ -1,12 +1,14 @@ 'use client' -import { useState, useTransition } from 'react' +import { useState, useTransition, useEffect } from 'react' import { Trash2, MoreHorizontal, ChevronsUp, ChevronsDown, ListFilter } from 'lucide-react' import { useDroppable, useDraggable } from '@dnd-kit/core' import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import { toast } from 'sonner' import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { CodeBadge } from '@/components/shared/code-badge' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -17,6 +19,7 @@ import { UserAvatar } from '@/components/shared/user-avatar' import { DemoTooltip } from '@/components/shared/demo-tooltip' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { PRIORITY_BORDER } from '@/components/backlog/backlog-card' +import { PRIORITY_COLORS } from '@/components/shared/priority-select' import { useSprintStore } from '@/stores/sprint-store' import { claimStoryAction, unclaimStoryAction, reassignStoryAction, claimAllUnassignedInActiveSprintAction } from '@/actions/stories' import { cn } from '@/lib/utils' @@ -304,6 +307,65 @@ export function SprintBacklogLeft({ // --- Right panel: Product Backlog grouped by PBI --- +const PRIORITY_LABELS_SPRINT: Record = { + 1: 'Kritiek', + 2: 'Hoog', + 3: 'Gemiddeld', + 4: 'Laag', +} + +const PRIORITY_OPTIONS_SPRINT: Array<{ value: number | 'all'; label: string }> = [ + { value: 'all', label: 'Alle' }, + { value: 1, label: 'Kritiek' }, + { value: 2, label: 'Hoog' }, + { value: 3, label: 'Gemiddeld' }, + { value: 4, label: 'Laag' }, +] + +type StoryStatusFilter = 'OPEN' | 'IN_SPRINT' | 'DONE' | 'all' + +const STATUS_OPTIONS_SPRINT: Array<{ value: StoryStatusFilter; label: string }> = [ + { value: 'all', label: 'Alle' }, + { value: 'OPEN', label: 'Open' }, + { value: 'IN_SPRINT', label: 'In Sprint' }, + { value: 'DONE', label: 'Klaar' }, +] + +function FilterPills({ + label, + options, + value, + onChange, +}: { + label: string + options: Array<{ value: T; label: string }> + value: T + onChange: (v: T) => void +}) { + return ( +
+

{label}

+
+ {options.map((opt) => ( + + ))} +
+
+ ) +} + function DraggablePbiStoryRow({ story, isDemo, @@ -384,8 +446,46 @@ export function SprintBacklogRight({ pbisWithStories, sprintStoryIds, isDemo, on } return auto }) + const [filterPriority, setFilterPriority] = useState('all') + const [filterStatus, setFilterStatus] = useState('all') + const [prefsLoaded, setPrefsLoaded] = useState(false) const { setNodeRef, isOver } = useDroppable({ id: 'backlog-zone' }) + // Hydrate filter prefs from localStorage post-mount (avoids SSR mismatch). + // setState calls here are intentional: hydrating from localStorage on first paint. + useEffect(() => { + const savedPriority = localStorage.getItem('scrum4me:sprint_pb_filter_priority') + if (savedPriority && savedPriority !== 'all') { + const n = parseInt(savedPriority, 10) + // eslint-disable-next-line react-hooks/set-state-in-effect + if (Number.isInteger(n) && n >= 1 && n <= 4) setFilterPriority(n) + } + const savedStatus = localStorage.getItem('scrum4me:sprint_pb_filter_status') + if (savedStatus === 'OPEN' || savedStatus === 'IN_SPRINT' || savedStatus === 'DONE') { + + setFilterStatus(savedStatus) + } + + setPrefsLoaded(true) + }, []) + + useEffect(() => { if (prefsLoaded) localStorage.setItem('scrum4me:sprint_pb_filter_priority', String(filterPriority)) }, [filterPriority, prefsLoaded]) + useEffect(() => { if (prefsLoaded) localStorage.setItem('scrum4me:sprint_pb_filter_status', filterStatus) }, [filterStatus, prefsLoaded]) + + const filteredPbis = pbisWithStories + .map(pbi => ({ + ...pbi, + stories: pbi.stories.filter(s => + (filterPriority === 'all' || s.priority === filterPriority) && + (filterStatus === 'all' || s.status === filterStatus) + ), + })) + .filter(pbi => pbi.stories.length > 0) + + const activeFilterCount = + (filterPriority !== 'all' ? 1 : 0) + + (filterStatus !== 'all' ? 1 : 0) + function toggle(pbiId: string) { setCollapsed(prev => { const next = new Set(prev) @@ -395,7 +495,7 @@ export function SprintBacklogRight({ pbisWithStories, sprintStoryIds, isDemo, on } function collapseAll() { - setCollapsed(new Set(pbisWithStories.map(p => p.id))) + setCollapsed(new Set(filteredPbis.map(p => p.id))) } function expandAll() { @@ -404,7 +504,7 @@ export function SprintBacklogRight({ pbisWithStories, sprintStoryIds, isDemo, on function onlyNotDone() { const auto = new Set() - for (const pbi of pbisWithStories) { + for (const pbi of filteredPbis) { if (pbi.stories.length > 0 && pbi.stories.every(s => s.status === 'DONE')) { auto.add(pbi.id) } @@ -412,32 +512,96 @@ export function SprintBacklogRight({ pbisWithStories, sprintStoryIds, isDemo, on setCollapsed(auto) } - const collapseActions = ( - - - - - - Alles inklappen - - - - - - Alles uitklappen - - - - - - Alleen niet klaar - - + const headerActions = ( + <> + {filterPriority !== 'all' && ( + + )} + {filterStatus !== 'all' && ( + + )} + + + {`Filters${activeFilterCount > 0 ? ` (${activeFilterCount})` : ''}`} + + } + /> + + + +
+ +
+
+
+ + + + + + Alles inklappen + + + + + + Alles uitklappen + + + + + + Alleen niet klaar + + + ) return (
- +
- {pbisWithStories.map(pbi => ( + {filteredPbis.map(pbi => (
+
+ setStoryDialogState(null)} + isDemo={isDemo} + />
) } @@ -433,10 +461,11 @@ interface SprintBacklogRightProps { pbisWithStories: PbiWithStories[] sprintStoryIds: Set isDemo: boolean + productId: string onAdd: (storyId: string) => void } -export function SprintBacklogRight({ pbisWithStories, sprintStoryIds, isDemo, onAdd }: SprintBacklogRightProps) { +export function SprintBacklogRight({ pbisWithStories, sprintStoryIds, isDemo, productId, onAdd }: SprintBacklogRightProps) { const [collapsed, setCollapsed] = useState>(() => { const auto = new Set() for (const pbi of pbisWithStories) { @@ -449,6 +478,7 @@ export function SprintBacklogRight({ pbisWithStories, sprintStoryIds, isDemo, on const [filterPriority, setFilterPriority] = useState('all') const [filterStatus, setFilterStatus] = useState('all') const [prefsLoaded, setPrefsLoaded] = useState(false) + const [pbiDialogState, setPbiDialogState] = useState(null) const { setNodeRef, isOver } = useDroppable({ id: 'backlog-zone' }) // Hydrate filter prefs from localStorage post-mount (avoids SSR mismatch). @@ -611,9 +641,12 @@ export function SprintBacklogRight({ pbisWithStories, sprintStoryIds, isDemo, on > {filteredPbis.map(pbi => (
- + + + +
{!collapsed.has(pbi.id) && pbi.stories.map(story => { const inSprint = sprintStoryIds.has(story.id) @@ -654,6 +700,11 @@ export function SprintBacklogRight({ pbisWithStories, sprintStoryIds, isDemo, on
))} + setPbiDialogState(null)} + isDemo={isDemo} + /> ) } diff --git a/components/sprint/sprint-board-client.tsx b/components/sprint/sprint-board-client.tsx index 4a6501a..4437f54 100644 --- a/components/sprint/sprint-board-client.tsx +++ b/components/sprint/sprint-board-client.tsx @@ -210,6 +210,7 @@ export function SprintBoardClient({ pbisWithStories={pbisWithStories} sprintStoryIds={sprintStoryIds} isDemo={isDemo} + productId={productId} onAdd={handleAdd} />, Date: Mon, 4 May 2026 09:21:35 +0200 Subject: [PATCH 3/3] feat(sprint): edit-icoon op taak in Taken-kolom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hover-zichtbaar ✎-icoon rechts uitgelijnd op iedere taak-rij; opent dezelfde edit-dialog als een rij-klik (visuele cue, consistent met PBI/story-rijen). Co-Authored-By: Claude Opus 4.7 (1M context) --- components/sprint/task-list.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/sprint/task-list.tsx b/components/sprint/task-list.tsx index 3c1c36b..7e43d78 100644 --- a/components/sprint/task-list.tsx +++ b/components/sprint/task-list.tsx @@ -11,6 +11,7 @@ import { sortableKeyboardCoordinates, } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' +import { Pencil } from 'lucide-react' import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' @@ -120,6 +121,16 @@ function SortableTaskRow({ + + + )