190 lines
7.7 KiB
TypeScript
190 lines
7.7 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-4 md:px-8 py-3 md: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 flex-shrink-0">
|
|
<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">SkillQuiz</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-7 h-7 md:w-8 md:h-8 bg-primary/20 rounded-full flex items-center justify-center text-primary text-xs font-bold flex-shrink-0">
|
|
{student.first_name[0]}{student.last_name[0]}
|
|
</div>
|
|
<span className="text-xs md:text-sm text-text-secondary">{student.first_name} {student.last_name}</span>
|
|
</div>
|
|
</header>
|
|
|
|
<div className="max-w-3xl mx-auto px-3 md:px-4 py-6 md:py-10">
|
|
{/* Score Card */}
|
|
<div className="card p-5 md:p-8 mb-6 md: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 flex-wrap items-center justify-center gap-4 md: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>
|
|
)
|
|
}
|