feat: revamp applicant jury feedback UI with score summaries and observer-style design
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled

Dashboard sidebar card now shows per-round avg score with progress bars.
Evaluations page redesigned with stats strip, score comparison bars,
criteria progress bars, animated cards, and styled feedback blocks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 10:16:37 +01:00
parent 6852278f92
commit 0d9a985377
2 changed files with 329 additions and 99 deletions

View File

@@ -19,6 +19,7 @@ import { CompetitionTimelineSidebar } from '@/components/applicant/competition-t
import { MentoringRequestCard } from '@/components/applicant/mentoring-request-card'
import { AnimatedCard } from '@/components/shared/animated-container'
import { ProjectLogoUpload } from '@/components/shared/project-logo-upload'
import { Progress } from '@/components/ui/progress'
import {
FileText,
Calendar,
@@ -35,6 +36,8 @@ import {
Check,
X,
UserCircle,
Trophy,
Vote,
} from 'lucide-react'
import { toast } from 'sonner'
@@ -390,7 +393,9 @@ export default function ApplicantDashboardPage() {
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<Star className="h-5 w-5" />
<div className="rounded-lg bg-yellow-500/10 p-1.5">
<Star className="h-4 w-4 text-yellow-500" />
</div>
Jury Feedback
</CardTitle>
<Button variant="ghost" size="sm" asChild>
@@ -400,15 +405,47 @@ export default function ApplicantDashboardPage() {
</Button>
</div>
</CardHeader>
<CardContent className="space-y-2">
{evaluations?.map((round) => (
<div key={round.roundId} className="flex items-center justify-between text-sm rounded-lg border p-2.5">
<span className="font-medium">{round.roundName}</span>
<Badge variant="secondary" className="text-xs">
{round.evaluationCount} review{round.evaluationCount !== 1 ? 's' : ''}
</Badge>
</div>
))}
<CardContent className="space-y-3">
{evaluations?.map((round) => {
const scores = round.evaluations
.map((ev) => ev.globalScore)
.filter((s): s is number => s !== null)
const avgScore = scores.length > 0
? scores.reduce((a, b) => a + b, 0) / scores.length
: null
const maxScore = round.roundType === 'LIVE_FINAL' ? 10 : 100
const pct = avgScore !== null ? (avgScore / maxScore) * 100 : 0
const roundIcon = round.roundType === 'LIVE_FINAL'
? <Trophy className="h-3.5 w-3.5 text-amber-500" />
: round.roundType === 'DELIBERATION'
? <Vote className="h-3.5 w-3.5 text-violet-500" />
: <Star className="h-3.5 w-3.5 text-yellow-500" />
return (
<div key={round.roundId} className="rounded-lg border p-3 space-y-2">
<div className="flex items-center justify-between">
<span className="flex items-center gap-1.5 text-sm font-medium">
{roundIcon}
{round.roundName}
</span>
<Badge variant="secondary" className="text-xs">
{round.evaluationCount} review{round.evaluationCount !== 1 ? 's' : ''}
</Badge>
</div>
{avgScore !== null && (
<div className="space-y-1">
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span>Avg Score</span>
<span className="font-semibold text-foreground tabular-nums">
{avgScore.toFixed(1)}<span className="text-muted-foreground font-normal"> / {maxScore}</span>
</span>
</div>
<Progress value={pct} className="h-1.5" />
</div>
)}
</div>
)
})}
</CardContent>
</Card>
</AnimatedCard>