SolyQuiz/app/quiz/[code]/results/ResultsClient.tsx
corenthin-lebreton 28aa3b0e10 initial project
2026-02-26 20:10:14 +01:00

190 lines
7.6 KiB
TypeScript

'use client'
import { CheckCircle, XCircle, Trophy, Target } from 'lucide-react'
import { cn } from '@/lib/utils'
interface QuestionResult {
question_id: string
question_text: string
explanation: string | null
student_answer: { id: string; text: string } | null
correct_answer: { id: string; text: string } | null
is_correct: boolean
all_answers: { id: string; text: string; is_correct: boolean }[]
}
interface Results {
student: {
first_name: string
last_name: string
score: number
}
questions: QuestionResult[]
summary: {
total: number
correct: number
score: number
}
}
interface Props {
results: Results
sessionCode: string
}
function getScoreColor(score: number): string {
if (score >= 14) return 'text-green-400'
if (score >= 10) return 'text-amber-400'
return 'text-red-400'
}
function getScoreMessage(score: number): string {
if (score >= 18) return 'Excellent ! Performance remarquable !'
if (score >= 14) return 'Très bien ! Vous maîtrisez le sujet.'
if (score >= 10) return 'Bien. Quelques points à revoir.'
return 'À retravailler. N\'hésitez pas à reprendre le cours.'
}
export default function ResultsClient({ results, sessionCode }: Props) {
const { student, questions, summary } = results
const percentage = Math.round((summary.correct / summary.total) * 100)
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="border-b border-border bg-background-secondary px-8 py-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z" fill="white"/>
</svg>
</div>
<span className="font-bold text-text-primary">SolyQuiz</span>
</div>
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-primary/20 rounded-full flex items-center justify-center text-primary text-xs font-bold">
{student.first_name[0]}{student.last_name[0]}
</div>
<span className="text-sm text-text-secondary">{student.first_name} {student.last_name}</span>
</div>
</header>
<div className="max-w-3xl mx-auto px-4 py-10">
{/* Score Card */}
<div className="card p-8 mb-8 text-center bg-gradient-to-br from-background-card to-background-elevated relative overflow-hidden">
<div className="absolute w-64 h-64 rounded-full bg-primary/5 blur-3xl -right-10 -top-10" />
<div className="relative z-10">
<div className="w-16 h-16 bg-primary/20 rounded-full flex items-center justify-center mx-auto mb-4">
<Trophy size={28} className="text-primary" />
</div>
<h1 className="text-2xl font-bold text-text-primary mb-1">Quiz terminé !</h1>
<p className="text-text-secondary mb-6">
{student.first_name} {student.last_name} · Session #{sessionCode}
</p>
<div className="flex items-end justify-center gap-2 mb-3">
<span className={cn('text-6xl font-bold', getScoreColor(summary.score))}>
{summary.score.toFixed(2)}
</span>
<span className="text-2xl text-text-muted mb-2">/20</span>
</div>
<p className={cn('text-sm font-medium mb-6', getScoreColor(summary.score))}>
{getScoreMessage(summary.score)}
</p>
<div className="flex items-center justify-center gap-8 text-sm">
<div className="flex items-center gap-2 text-green-400">
<CheckCircle size={16} />
<span><strong>{summary.correct}</strong> correctes</span>
</div>
<div className="flex items-center gap-2 text-red-400">
<XCircle size={16} />
<span><strong>{summary.total - summary.correct}</strong> incorrectes</span>
</div>
<div className="flex items-center gap-2 text-text-secondary">
<Target size={16} />
<span><strong>{percentage}%</strong> de réussite</span>
</div>
</div>
</div>
</div>
{/* Detailed Results */}
<h2 className="text-lg font-semibold text-text-primary mb-4">
Correction détaillée
</h2>
<div className="space-y-4">
{questions.map((q, index) => (
<div
key={q.question_id}
className={cn(
'card p-5 border-l-4',
q.is_correct ? 'border-l-green-500' : 'border-l-red-500'
)}
>
<div className="flex items-start gap-3 mb-4">
<div className={cn(
'w-7 h-7 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5',
q.is_correct ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
)}>
{q.is_correct ? <CheckCircle size={16} /> : <XCircle size={16} />}
</div>
<div className="flex-1">
<p className="text-xs text-text-muted mb-1">Question {index + 1}</p>
<p className="font-medium text-text-primary">{q.question_text}</p>
</div>
</div>
<div className="ml-10 space-y-2">
{q.all_answers.map((answer) => {
const isStudentAnswer = q.student_answer?.id === answer.id
const isCorrectAnswer = q.correct_answer?.id === answer.id
return (
<div
key={answer.id}
className={cn(
'flex items-center gap-2 p-3 rounded-lg text-sm border',
isCorrectAnswer
? 'bg-green-500/10 border-green-500/30 text-green-300'
: isStudentAnswer && !q.is_correct
? 'bg-red-500/10 border-red-500/30 text-red-300'
: 'bg-background-elevated border-border text-text-secondary'
)}
>
<div className={cn(
'w-4 h-4 rounded-full border flex-shrink-0',
isCorrectAnswer
? 'border-green-400 bg-green-400'
: isStudentAnswer && !q.is_correct
? 'border-red-400 bg-red-400'
: 'border-border-light'
)} />
<span>{answer.text}</span>
{isCorrectAnswer && (
<span className="ml-auto text-xs text-green-400 font-medium"> Bonne réponse</span>
)}
{isStudentAnswer && !q.is_correct && (
<span className="ml-auto text-xs text-red-400 font-medium"> Votre réponse</span>
)}
</div>
)
})}
{q.explanation && (
<div className="mt-3 p-3 bg-blue-500/5 border border-blue-500/20 rounded-lg">
<p className="text-xs text-blue-400 font-medium mb-1">💡 Explication</p>
<p className="text-xs text-text-secondary">{q.explanation}</p>
</div>
)}
</div>
</div>
))}
</div>
</div>
</div>
)
}