diff --git a/src/app/(applicant)/applicant/evaluations/page.tsx b/src/app/(applicant)/applicant/evaluations/page.tsx index fb4975d..8db12b1 100644 --- a/src/app/(applicant)/applicant/evaluations/page.tsx +++ b/src/app/(applicant)/applicant/evaluations/page.tsx @@ -8,69 +8,72 @@ import { CardTitle, } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' -import { Progress } from '@/components/ui/progress' import { Skeleton } from '@/components/ui/skeleton' import { AnimatedCard } from '@/components/shared/animated-container' -import { - Star, - MessageSquare, - Trophy, - Vote, - TrendingUp, - BarChart3, - Award, - ShieldCheck, -} from 'lucide-react' +import { Star, MessageSquare, Trophy, Vote, ShieldCheck } from 'lucide-react' import { cn } from '@/lib/utils' +type Criterion = { + id?: string + type?: string + label?: string + name?: string + scale?: string + maxScore?: number +} + +type Evaluation = { + id: string + submittedAt: Date | null + globalScore: number | null + criterionScores: unknown + feedbackText: string | null + criteria: unknown +} + type EvaluationRound = { roundId: string roundName: string roundType: string evaluationCount: number - evaluations: Array<{ - id: string - submittedAt: Date | null - globalScore: number | null - criterionScores: unknown - feedbackText: string | null - criteria: unknown - }> + evaluations: Evaluation[] } -function computeRoundStats(round: EvaluationRound) { - const maxScore = round.roundType === 'LIVE_FINAL' ? 10 : 100 +const HIDDEN_CRITERION_TYPES = new Set(['boolean', 'advance']) + +function parseScaleMax(scale: string | undefined, fallback = 10): number { + if (!scale) return fallback + const m = scale.match(/^\s*\d+\s*-\s*(\d+)\s*$/) + if (m) return Number(m[1]) + return fallback +} + +function getCriterionMax(c: Criterion): number { + if (typeof c.maxScore === 'number' && c.maxScore > 0) return c.maxScore + return parseScaleMax(c.scale) +} + +function visibleCriteria(criteria: unknown): Criterion[] { + if (!Array.isArray(criteria)) return [] + return (criteria as Criterion[]).filter((c) => { + if (!c) return false + if (!c.id && !c.label && !c.name) return false + if (c.type && HIDDEN_CRITERION_TYPES.has(c.type)) return false + return true + }) +} + +function globalScoreSummary(round: EvaluationRound) { + if (round.roundType === 'DELIBERATION') return null const scores = round.evaluations .map((ev) => ev.globalScore) .filter((s): s is number => s !== null) - const avg = scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / scores.length : null - const highest = scores.length > 0 ? Math.max(...scores) : null - const lowest = scores.length > 0 ? Math.min(...scores) : null - return { maxScore, avg, highest, lowest, scores } -} - -function ScoreBar({ score, maxScore, color }: { score: number; maxScore: number; color: string }) { - const pct = (score / maxScore) * 100 - return ( -
+ {value} +
+Anonymous evaluations from jury members
-- Anonymous evaluations from jury members + {hasEvaluations + ? `Anonymous evaluations from jury members across ${rounds.length} round${rounds.length === 1 ? '' : 's'}.` + : 'Anonymous evaluations from jury members.'}
{totalEvaluations}
-- {globalAvg !== null ? globalAvg.toFixed(1) : '—'} - {globalAvg !== null && / 100} -
-- {globalHighest !== null ? globalHighest : '—'} - {globalHighest !== null && / 100} -
-- Average: {avg.toFixed(1)} / {maxScore} - {highest !== null && lowest !== null && highest !== lowest && ( - - Range: {lowest}–{highest} - + Average {summary.avg.toFixed(1)} / {summary.max} + {summary.lowest !== summary.highest && ( + · Lowest {summary.lowest} · Highest {summary.highest} )}
)}Score Comparison
- {round.evaluations.map((ev, idx) => { - if (ev.globalScore === null) return null - return ( -+ {ev.feedbackText} +
+Criteria Breakdown
-- {ev.feedbackText} -
-
diff --git a/src/components/settings/settings-content.tsx b/src/components/settings/settings-content.tsx
index 2c76fea..3856651 100644
--- a/src/components/settings/settings-content.tsx
+++ b/src/components/settings/settings-content.tsx
@@ -624,8 +624,9 @@ function SettingToggle({
settingKey: string
value: string
}) {
+ const [optimistic, setOptimistic] = useState