- Verwijdert setActiveSprintCookie uit sprint board page (kon geen cookie schrijven vanuit een Server Component — alleen Server Action / Route Handler) - Verplaatst sprint-pulldown uit NavBar naar de productpagina action-row (dezelfde rij als 'Sprint starten' / 'Instellingen') - Nieuwe SprintSwitcher-component in zelfde stijl als product-dropdown: truncated tekst + ChevronDown, geen status-badges - NavBar krijgt activeSprintId voor sprint-link href; layout fetcht alleen nog activeSprint-id (geen volledige sprints[] meer) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
84 lines
2.5 KiB
TypeScript
84 lines
2.5 KiB
TypeScript
'use client'
|
|
|
|
import { useTransition } from 'react'
|
|
import { usePathname, useRouter } from 'next/navigation'
|
|
import { ChevronDown } from 'lucide-react'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu'
|
|
import { cn } from '@/lib/utils'
|
|
import { setActiveSprintAction } from '@/actions/active-sprint'
|
|
|
|
type SprintItem = { id: string; code: string }
|
|
|
|
interface SprintSwitcherProps {
|
|
productId: string
|
|
sprints: SprintItem[]
|
|
activeSprintId: string | null
|
|
className?: string
|
|
}
|
|
|
|
export function SprintSwitcher({
|
|
productId,
|
|
sprints,
|
|
activeSprintId,
|
|
className,
|
|
}: SprintSwitcherProps) {
|
|
const router = useRouter()
|
|
const pathname = usePathname()
|
|
const [isPending, startTransition] = useTransition()
|
|
|
|
if (sprints.length === 0) return null
|
|
|
|
const active = sprints.find(s => s.id === activeSprintId) ?? sprints[0]
|
|
|
|
function handleSwitch(sprintId: string) {
|
|
if (sprintId === active.id) return
|
|
startTransition(async () => {
|
|
const result = await setActiveSprintAction(productId, sprintId)
|
|
if (result?.error) {
|
|
toast.error(typeof result.error === 'string' ? result.error : 'Wisselen mislukt')
|
|
return
|
|
}
|
|
if (pathname.includes('/sprint')) {
|
|
router.push(`/products/${productId}/sprint/${sprintId}`)
|
|
} else {
|
|
router.refresh()
|
|
}
|
|
})
|
|
}
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger
|
|
disabled={isPending}
|
|
className={cn(
|
|
'flex items-center gap-1 text-sm font-medium text-foreground hover:text-primary transition-colors px-2 rounded-md hover:bg-surface-container focus:outline-none',
|
|
className,
|
|
)}
|
|
>
|
|
<span className="truncate max-w-[180px]">
|
|
{active.code.length > 22 ? active.code.slice(0, 22) + '…' : active.code}
|
|
</span>
|
|
<ChevronDown className="w-3.5 h-3.5 shrink-0 text-muted-foreground" />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="center" className="w-56">
|
|
{sprints.map(s => (
|
|
<DropdownMenuItem
|
|
key={s.id}
|
|
onClick={() => handleSwitch(s.id)}
|
|
className={cn(
|
|
s.id === active.id && 'bg-primary-container text-primary-container-foreground font-medium',
|
|
)}
|
|
>
|
|
<span className="truncate">{s.code}</span>
|
|
</DropdownMenuItem>
|
|
))}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)
|
|
}
|