Server Component met data-fetching via getSprintTokenHistory/getDayTokenData/ getPbiTokenAggregates. SprintTokenHistoryTable, TokenDayChart (Recharts LineChart), PbiTokenTable en TokenSelectors (client, URL-params). Link toegevoegd in insights/page.tsx. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
81 lines
2.3 KiB
TypeScript
81 lines
2.3 KiB
TypeScript
'use client'
|
|
|
|
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
|
|
import { useTransition } from 'react'
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select'
|
|
|
|
interface Sprint {
|
|
id: string
|
|
sprint_goal: string
|
|
}
|
|
|
|
interface Props {
|
|
productList: { id: string; name: string }[]
|
|
sprintList: Sprint[]
|
|
currentProductId?: string
|
|
currentSprintId?: string
|
|
}
|
|
|
|
export function TokenSelectors({ productList, sprintList, currentProductId, currentSprintId }: Props) {
|
|
const router = useRouter()
|
|
const pathname = usePathname()
|
|
const searchParams = useSearchParams()
|
|
const [isPending, startTransition] = useTransition()
|
|
|
|
function setParam(key: string, value: string | null) {
|
|
startTransition(() => {
|
|
const params = new URLSearchParams(searchParams.toString())
|
|
if (value === null || value === '') {
|
|
params.delete(key)
|
|
} else {
|
|
params.set(key, value)
|
|
}
|
|
router.replace(`${pathname}?${params.toString()}`)
|
|
})
|
|
}
|
|
|
|
return (
|
|
<div className="flex gap-3 flex-wrap">
|
|
{productList.length > 0 && (
|
|
<Select
|
|
value={currentProductId ?? '__all__'}
|
|
onValueChange={v => setParam('product', v === '__all__' ? null : v)}
|
|
>
|
|
<SelectTrigger className="w-44" disabled={isPending}>
|
|
<SelectValue placeholder="Product" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="__all__">Alle producten</SelectItem>
|
|
{productList.map(p => (
|
|
<SelectItem key={p.id} value={p.id}>{p.name}</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
)}
|
|
|
|
{sprintList.length > 0 && (
|
|
<Select
|
|
value={currentSprintId ?? ''}
|
|
onValueChange={v => setParam('sprint', v)}
|
|
>
|
|
<SelectTrigger className="w-64" disabled={isPending}>
|
|
<SelectValue placeholder="Sprint kiezen" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{sprintList.map(s => (
|
|
<SelectItem key={s.id} value={s.id}>
|
|
{s.sprint_goal.length > 50 ? s.sprint_goal.slice(0, 47) + '…' : s.sprint_goal}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|