import { cookies } from 'next/headers' import { getIronSession } from 'iron-session' import { SessionData, sessionOptions } from '@/lib/session' import { prisma } from '@/lib/prisma' import { productAccessFilter } from '@/lib/product-access' import { getBurndownData } from '@/lib/insights/burndown' import { getSprintStatusBreakdown } from '@/lib/insights/sprint-status' import { getVerifyResultStats, getAlignmentTrend } from '@/lib/insights/verify-stats' import { getJobsPerDay } from '@/lib/insights/agent-throughput' import { getTokenStats } from '@/lib/insights/token-stats' import { getVelocity } from '@/lib/insights/velocity' import { getBacklogHealth } from '@/lib/insights/backlog-health' import { SprintInfoStrip } from './components/sprint-info-strip' import { BurndownChart } from './components/burndown-chart' import { SprintStatusDonut } from './components/sprint-status-donut' import { PlanQualityCard } from './components/plan-quality' import { AlignmentTrend } from './components/alignment-trend' import { AgentThroughputCard } from './components/agent-throughput' import { TokenUsageCard } from './components/token-usage' import { VelocityChart } from './components/velocity-chart' import { BacklogHealthCard } from './components/backlog-health' const DAY_MS = 86_400_000 const ASSUMED_SPRINT_DAYS = 14 interface InsightsPageProps { searchParams: Promise<{ product?: string }> } function MissingDatesNotice({ productId, productName }: { productId: string; productName: string }) { return (

{productName} — sprint heeft geen datums.{' '} Stel datums in

) } export default async function InsightsPage({ searchParams }: InsightsPageProps) { const session = await getIronSession(await cookies(), sessionOptions) const userId = session.userId! const { product: filterProductId } = await searchParams const [ burndownSprints, statusBreakdown, activeSprints, productList, verifyStats, alignmentTrend, jobsPerDay, velocity, backlogHealth, ] = await Promise.all([ getBurndownData(userId), getSprintStatusBreakdown(userId), prisma.sprint.findMany({ where: { status: 'OPEN', product: productAccessFilter(userId) }, select: { id: true, code: true, sprint_goal: true, created_at: true, product: { select: { id: true, name: true } }, tasks: { select: { id: true } }, }, }), prisma.product.findMany({ where: productAccessFilter(userId), select: { id: true, name: true }, orderBy: { name: 'asc' }, }), getVerifyResultStats(userId, 30), getAlignmentTrend(userId, 5), getJobsPerDay(userId, 14, filterProductId), getVelocity(userId, 5), getBacklogHealth(userId), ]) const activeSprintId = activeSprints.find(s => s.product.id === filterProductId)?.id ?? '' const tokenStats = await (activeSprints.length > 0 && filterProductId ? getTokenStats(userId, activeSprintId) : Promise.resolve({ kpi: { totalTokens: 0, totalCostUsd: 0, avgCostPerJob: 0, jobCount: 0 }, jobs: [] })) // Date.now is an impure call but used once per request — safe in a Server Component. // eslint-disable-next-line react-hooks/purity const nowMs = Date.now() const sprintInfos = activeSprints.map(s => ({ sprintId: s.id, sprintCode: s.code, productId: s.product.id, productName: s.product.name, sprintGoal: s.sprint_goal, taskCount: s.tasks.length, daysLeft: ASSUMED_SPRINT_DAYS - Math.floor((nowMs - s.created_at.getTime()) / DAY_MS), })) const burndownMap = new Map(burndownSprints.map(b => [b.sprintId, b])) return (

Insights

{/* Sprint Health */}

Sprint Health

{activeSprints.length === 0 ? (

Geen active sprints — start er een via /products/[id]/sprint.

) : ( <>
{sprintInfos.map(s => { const burndown = burndownMap.get(s.sprintId) if (!burndown || burndown.days.length === 0) { return ( ) } return })}
)}
{/* Plan-quality */}

Plan-quality

{alignmentTrend.length > 0 && }
{/* Agent throughput */}

Agent throughput

{/* Token usage */}

Token gebruik

{/* Velocity */}

Velocity

{/* Backlog health */}

Backlog health

) }