2026-02-14 15:26:42 +01:00
|
|
|
'use client'
|
|
|
|
|
|
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
2026-03-06 13:37:50 +01:00
|
|
|
import { Badge } from '@/components/ui/badge'
|
|
|
|
|
import { Progress } from '@/components/ui/progress'
|
|
|
|
|
import {
|
|
|
|
|
Table,
|
|
|
|
|
TableBody,
|
|
|
|
|
TableCell,
|
|
|
|
|
TableHead,
|
|
|
|
|
TableHeader,
|
|
|
|
|
TableRow,
|
|
|
|
|
} from '@/components/ui/table'
|
|
|
|
|
import { scoreGradient } from './chart-theme'
|
|
|
|
|
import { ArrowRight } from 'lucide-react'
|
2026-02-14 15:26:42 +01:00
|
|
|
|
|
|
|
|
interface StageComparison {
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
|
|
|
roundId: string
|
|
|
|
|
roundName: string
|
2026-02-14 15:26:42 +01:00
|
|
|
projectCount: number
|
|
|
|
|
evaluationCount: number
|
|
|
|
|
completionRate: number
|
|
|
|
|
averageScore: number | null
|
|
|
|
|
scoreDistribution: { score: number; count: number }[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface CrossStageComparisonProps {
|
|
|
|
|
data: StageComparison[]
|
|
|
|
|
}
|
|
|
|
|
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
export function CrossStageComparisonChart({
|
|
|
|
|
data,
|
|
|
|
|
}: CrossStageComparisonProps) {
|
2026-02-20 13:42:31 +01:00
|
|
|
if (!data?.length) {
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardContent className="flex items-center justify-center py-12">
|
|
|
|
|
<p className="text-muted-foreground">No comparison data available</p>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 13:37:50 +01:00
|
|
|
const maxProjects = Math.max(...data.map((d) => d.projectCount), 1)
|
2026-02-14 15:26:42 +01:00
|
|
|
|
|
|
|
|
return (
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
2026-03-06 13:37:50 +01:00
|
|
|
<CardTitle className="text-base">Round Progression</CardTitle>
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
2026-03-06 13:37:50 +01:00
|
|
|
{/* Pipeline funnel visualization */}
|
|
|
|
|
<div className="flex items-center gap-2 mb-6 overflow-x-auto pb-2">
|
|
|
|
|
{data.map((round, idx) => (
|
|
|
|
|
<div key={round.roundId} className="flex items-center gap-2">
|
|
|
|
|
<div className="flex flex-col items-center min-w-[100px]">
|
|
|
|
|
<div
|
|
|
|
|
className="rounded-lg bg-[#053d57] flex items-center justify-center text-white font-bold text-lg tabular-nums transition-all"
|
|
|
|
|
style={{
|
|
|
|
|
width: `${Math.max(60, (round.projectCount / maxProjects) * 120)}px`,
|
|
|
|
|
height: `${Math.max(40, (round.projectCount / maxProjects) * 60)}px`,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{round.projectCount}
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-muted-foreground mt-1.5 text-center leading-tight max-w-[100px] truncate">
|
|
|
|
|
{round.roundName}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
{idx < data.length - 1 && (
|
|
|
|
|
<div className="flex flex-col items-center shrink-0">
|
|
|
|
|
<ArrowRight className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
{data[idx + 1].projectCount < round.projectCount && (
|
|
|
|
|
<span className="text-[10px] text-rose-500 tabular-nums font-medium">
|
|
|
|
|
-{round.projectCount - data[idx + 1].projectCount}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-02-14 15:26:42 +01:00
|
|
|
|
2026-03-06 13:37:50 +01:00
|
|
|
{/* Detailed metrics table */}
|
|
|
|
|
<div className="rounded-md border">
|
|
|
|
|
<Table>
|
|
|
|
|
<TableHeader>
|
|
|
|
|
<TableRow>
|
|
|
|
|
<TableHead>Round</TableHead>
|
|
|
|
|
<TableHead className="text-right tabular-nums">Projects</TableHead>
|
|
|
|
|
<TableHead className="text-right tabular-nums">Evaluations</TableHead>
|
|
|
|
|
<TableHead>Completion</TableHead>
|
|
|
|
|
<TableHead className="text-right">Avg Score</TableHead>
|
|
|
|
|
</TableRow>
|
|
|
|
|
</TableHeader>
|
|
|
|
|
<TableBody>
|
|
|
|
|
{data.map((round, idx) => {
|
|
|
|
|
const prevCount = idx > 0 ? data[idx - 1].projectCount : null
|
|
|
|
|
const attrition = prevCount !== null && prevCount > 0
|
|
|
|
|
? Math.round(((prevCount - round.projectCount) / prevCount) * 100)
|
|
|
|
|
: null
|
2026-02-14 15:26:42 +01:00
|
|
|
|
2026-03-06 13:37:50 +01:00
|
|
|
return (
|
|
|
|
|
<TableRow key={round.roundId}>
|
|
|
|
|
<TableCell>
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<span className="font-medium text-sm">{round.roundName}</span>
|
|
|
|
|
{attrition !== null && attrition > 0 && (
|
|
|
|
|
<Badge variant="outline" className="text-[10px] text-rose-600 border-rose-200">
|
|
|
|
|
-{attrition}%
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="text-right tabular-nums font-medium">
|
|
|
|
|
{round.projectCount}
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="text-right tabular-nums">
|
|
|
|
|
{round.evaluationCount > 0 ? round.evaluationCount : '—'}
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>
|
|
|
|
|
{round.evaluationCount > 0 ? (
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Progress value={round.completionRate} className="w-16 h-2" />
|
|
|
|
|
<span className="text-xs tabular-nums text-muted-foreground">
|
|
|
|
|
{round.completionRate}%
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<span className="text-xs text-muted-foreground">—</span>
|
|
|
|
|
)}
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="text-right">
|
|
|
|
|
{round.averageScore !== null ? (
|
|
|
|
|
<span
|
|
|
|
|
className="inline-flex items-center justify-center rounded-md px-2 py-0.5 text-xs font-semibold tabular-nums min-w-[36px]"
|
|
|
|
|
style={{
|
|
|
|
|
backgroundColor: scoreGradient(round.averageScore),
|
|
|
|
|
color: '#ffffff',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{round.averageScore.toFixed(1)}
|
|
|
|
|
</span>
|
|
|
|
|
) : (
|
|
|
|
|
<span className="text-xs text-muted-foreground">—</span>
|
|
|
|
|
)}
|
|
|
|
|
</TableCell>
|
|
|
|
|
</TableRow>
|
|
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
</TableBody>
|
|
|
|
|
</Table>
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
2026-02-14 15:26:42 +01:00
|
|
|
)
|
|
|
|
|
}
|