feat(PBI-78): wire CostAnalysisCard onto insights page (T-904)

- Parse ?period= from searchParams (default 30d, validates against 7d/30d/90d/mtd)
- Parallel-fetch 5 cost queries via Promise.all alongside existing widgets
- New "Cost analyse" section between Sprint Health and Plan-quality
- Existing TokenUsageCard ("Token gebruik" section) stays as sprint detail

verify (lint+typecheck+test) and build pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-10 12:53:04 +02:00
parent 814f2191e8
commit de70ca5de1

View file

@ -8,6 +8,14 @@ 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 {
getCostKpi,
getCostByDay,
getCostByModel,
getCostByKind,
getCacheEfficiency,
type Period,
} from '@/lib/insights/cost-analysis'
import { getVelocity } from '@/lib/insights/velocity'
import { getBacklogHealth } from '@/lib/insights/backlog-health'
import { SprintInfoStrip } from './components/sprint-info-strip'
@ -16,6 +24,7 @@ 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 { CostAnalysisCard } from './components/cost-analysis'
import { TokenUsageCard } from './components/token-usage'
import { VelocityChart } from './components/velocity-chart'
import { BacklogHealthCard } from './components/backlog-health'
@ -24,7 +33,13 @@ const DAY_MS = 86_400_000
const ASSUMED_SPRINT_DAYS = 14
interface InsightsPageProps {
searchParams: Promise<{ product?: string }>
searchParams: Promise<{ product?: string; period?: string }>
}
const VALID_PERIODS = ['7d', '30d', '90d', 'mtd'] as const
function parsePeriod(raw: string | undefined): Period {
return (VALID_PERIODS as readonly string[]).includes(raw ?? '') ? (raw as Period) : '30d'
}
function MissingDatesNotice({ productId, productName }: { productId: string; productName: string }) {
@ -41,7 +56,8 @@ function MissingDatesNotice({ productId, productName }: { productId: string; pro
export default async function InsightsPage({ searchParams }: InsightsPageProps) {
const session = await getIronSession<SessionData>(await cookies(), sessionOptions)
const userId = session.userId!
const { product: filterProductId } = await searchParams
const { product: filterProductId, period: rawPeriod } = await searchParams
const period = parsePeriod(rawPeriod)
const [
burndownSprints,
@ -53,6 +69,11 @@ export default async function InsightsPage({ searchParams }: InsightsPageProps)
jobsPerDay,
velocity,
backlogHealth,
costKpi,
costByDay,
costByModel,
costByKind,
cacheEff,
] = await Promise.all([
getBurndownData(userId),
getSprintStatusBreakdown(userId),
@ -77,6 +98,11 @@ export default async function InsightsPage({ searchParams }: InsightsPageProps)
getJobsPerDay(userId, 14, filterProductId),
getVelocity(userId, 5),
getBacklogHealth(userId),
getCostKpi(userId, period),
getCostByDay(userId, period),
getCostByModel(userId, period),
getCostByKind(userId, period),
getCacheEfficiency(userId, period),
])
const activeSprintId = activeSprints.find(s => s.product.id === filterProductId)?.id ?? ''
@ -134,6 +160,19 @@ export default async function InsightsPage({ searchParams }: InsightsPageProps)
)}
</section>
{/* Cost analyse */}
<section className="space-y-3">
<h2 className="text-lg font-medium text-foreground">Cost analyse</h2>
<CostAnalysisCard
period={period}
kpi={costKpi}
byDay={costByDay}
byModel={costByModel}
byKind={costByKind}
cache={cacheEff}
/>
</section>
{/* Plan-quality */}
<section className="space-y-3">
<h2 className="text-lg font-medium text-foreground">Plan-quality</h2>