feat: shared backlog filter popover + sprint header polish (v1.3.3) (#184)

- Move sprint switcher into sprint header, centered between title and actions
- Extract BacklogFilterPopover as shared component used by sprint and product backlog
- Add sort options (code/priority/status) with single-pill asc/desc toggle
- Default sprint backlog status filter to OPEN, remove "alleen niet klaar" button
- Persist collapsed state and filter popover open in localStorage
- Fix hydration flicker: defer localStorage read to useEffect with prefsLoaded gate for writes
- Increase sprint switcher text size for readability

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-10 11:12:04 +02:00 committed by GitHub
parent a9b53dedf0
commit 1f8cbacb0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 424 additions and 330 deletions

View file

@ -33,6 +33,8 @@ import {
import { updateSprintGoalAction, updateSprintDatesAction, completeSprintAction, setAllSprintTasksDoneAction } from '@/actions/sprints'
import type { SprintStory } from './sprint-backlog'
import { debugProps } from '@/lib/debug'
import { SprintSwitcher } from '@/components/shared/sprint-switcher'
import type { SprintSwitcherItem } from '@/lib/sprint-switcher-data'
interface Sprint {
id: string
@ -49,6 +51,9 @@ interface SprintHeaderProps {
sprint: Sprint
isDemo: boolean
sprintStories: SprintStory[]
switcherSprints: SprintSwitcherItem[]
switcherActiveSprint: SprintSwitcherItem | null
switcherBuildingSprintIds: string[]
}
interface ActionResult {
@ -63,7 +68,7 @@ function toDateInputValue(d: Date | null) {
return d.toISOString().slice(0, 10)
}
export function SprintHeader({ productId: _productId, productName, sprint, isDemo, sprintStories }: SprintHeaderProps) {
export function SprintHeader({ productId, productName, sprint, isDemo, sprintStories, switcherSprints, switcherActiveSprint, switcherBuildingSprintIds }: SprintHeaderProps) {
const [editingGoal, setEditingGoal] = useState(false)
const [editingDates, setEditingDates] = useState(false)
const [completeOpen, setCompleteOpen] = useState(false)
@ -132,7 +137,7 @@ export function SprintHeader({ productId: _productId, productName, sprint, isDem
return (
<div className="px-4 py-3 border-b border-border bg-surface-container-low shrink-0" {...debugProps('sprint-header', 'SprintHeader', 'components/sprint/sprint-header.tsx')}>
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-4">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground">{productName}</span>
@ -162,7 +167,16 @@ export function SprintHeader({ productId: _productId, productName, sprint, isDem
)}
</div>
<div className="flex items-center gap-2 shrink-0" data-debug-id="sprint-header__actions">
<div className="shrink-0">
<SprintSwitcher
productId={productId}
sprints={switcherSprints}
activeSprint={switcherActiveSprint}
buildingSprintIds={switcherBuildingSprintIds}
/>
</div>
<div className="flex items-center justify-end gap-2 flex-1 shrink-0" data-debug-id="sprint-header__actions">
<DemoTooltip show={isDemo}>
<Button size="sm" variant="ghost" disabled={isDemo} className="text-muted-foreground" data-debug-id="sprint-header__dates" onClick={() => !isDemo && setEditingDates(true)}>
{sprint.start_date && sprint.end_date