diff --git a/app/(app)/insights/components/alignment-trend.tsx b/app/(app)/insights/components/alignment-trend.tsx
new file mode 100644
index 0000000..45375d1
--- /dev/null
+++ b/app/(app)/insights/components/alignment-trend.tsx
@@ -0,0 +1,73 @@
+'use client'
+
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ Tooltip,
+ ResponsiveContainer,
+} from 'recharts'
+import type { TrendPoint } from '@/lib/insights/verify-stats'
+
+interface Props {
+ trend: TrendPoint[]
+}
+
+interface TooltipPayload {
+ payload?: { total: number; alignedRatio: number; sprintGoal: string }
+}
+
+function CustomTooltip({ active, payload }: { active?: boolean; payload?: TooltipPayload[] }) {
+ if (!active || !payload?.length) return null
+ const d = payload[0].payload
+ if (!d) return null
+ const aligned = Math.round((d.alignedRatio / 100) * d.total)
+ return (
+
+
{d.sprintGoal}
+
+ {aligned} / {d.total} aligned ({d.alignedRatio}%)
+
+
+ )
+}
+
+function sprintLabel(goal: string): string {
+ return goal.length > 20 ? goal.slice(0, 18) + '…' : goal
+}
+
+export function AlignmentTrend({ trend }: Props) {
+ if (trend.length === 0) {
+ return (
+
+ Geen voltooide sprints met verify-data gevonden.
+
+ )
+ }
+
+ const data = trend.map(p => ({
+ ...p,
+ label: sprintLabel(p.sprintGoal),
+ }))
+
+ return (
+
+
+ % Aligned per sprint (laatste {trend.length})
+
+
+
+
+ `${v}%`} tick={{ fontSize: 11 }} />
+ } />
+
+
+
+
+ )
+}
diff --git a/app/(app)/insights/components/plan-quality.tsx b/app/(app)/insights/components/plan-quality.tsx
new file mode 100644
index 0000000..ec92d5c
--- /dev/null
+++ b/app/(app)/insights/components/plan-quality.tsx
@@ -0,0 +1,112 @@
+'use client'
+
+import Link from 'next/link'
+import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer } from 'recharts'
+import type { VerifyResultStats, VerifyResultKey, TopJob, TrendPoint } from '@/lib/insights/verify-stats'
+import { AlignmentTrend } from './alignment-trend'
+
+interface Props {
+ stats: VerifyResultStats
+ trend: TrendPoint[]
+ nowMs: number
+}
+
+const VERIFY_COLORS: Record = {
+ ALIGNED: 'var(--status-done)',
+ PARTIAL: 'var(--priority-medium)',
+ EMPTY: 'var(--priority-critical)',
+ DIVERGENT: 'var(--priority-high)',
+}
+
+const VERIFY_LABELS: Record = {
+ ALIGNED: 'Aligned',
+ PARTIAL: 'Partial',
+ EMPTY: 'Empty',
+ DIVERGENT: 'Divergent',
+}
+
+function daysAgo(date: Date, nowMs: number): string {
+ const diff = Math.floor((nowMs - new Date(date).getTime()) / 86_400_000)
+ return diff === 0 ? 'vandaag' : `${diff}d geleden`
+}
+
+function TopTable({ title, jobs, nowMs }: { title: string; jobs: TopJob[]; nowMs: number }) {
+ if (jobs.length === 0) return null
+ return (
+
+
{title}
+
+
+ {jobs.map(j => (
+
+ |
+
+ {j.taskTitle}
+
+ |
+ {j.productName} |
+ {daysAgo(j.finishedAt, nowMs)} |
+
+ ))}
+
+
+
+ )
+}
+
+export function PlanQualityCard({ stats, trend, nowMs }: Props) {
+ if (stats.counts.length === 0) {
+ return (
+
+
+ Plan-verify gating moet eerst geactiveerd worden.{' '}
+
+ Bekijk de Plan-verify gating story
+
+
+
+ )
+ }
+
+ const labeled = stats.counts.map(c => ({
+ ...c,
+ name: VERIFY_LABELS[c.result],
+ }))
+
+ return (
+
+
+
+
+
+ {labeled.map(entry => (
+ |
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}