242 lines
8.7 KiB
TypeScript
242 lines
8.7 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import { useRouter, useSearchParams } from 'next/navigation';
|
|
import { CheckCircle, XCircle, Clock, Award, BarChart3, Home, FileText } from 'lucide-react';
|
|
import { cn } from '@/utils/cn';
|
|
|
|
interface ExamResult {
|
|
examId: string;
|
|
examTitle: string;
|
|
totalQuestions: number;
|
|
correctAnswers: number;
|
|
score: number;
|
|
timeSpent: string;
|
|
completedAt: string;
|
|
answers: Array<{
|
|
questionId: string;
|
|
question: string;
|
|
userAnswer: any;
|
|
correctAnswer: any;
|
|
isCorrect: boolean;
|
|
type: string;
|
|
}>;
|
|
}
|
|
|
|
export default function ExamSummaryPage() {
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const [examResult, setExamResult] = useState<ExamResult | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
// Simulate loading exam results
|
|
// In real app, this would fetch from API or localStorage
|
|
const mockResult: ExamResult = {
|
|
examId: 'exam-1',
|
|
examTitle: 'Ujian Keamanan Pangan MBG',
|
|
totalQuestions: 10,
|
|
correctAnswers: 8,
|
|
score: 80,
|
|
timeSpent: '25 menit 30 detik',
|
|
completedAt: new Date().toLocaleString('id-ID'),
|
|
answers: [
|
|
{
|
|
questionId: '1',
|
|
question: 'Suhu ideal untuk menyimpan daging segar adalah...',
|
|
userAnswer: 'Di bawah 4°C',
|
|
correctAnswer: 'Di bawah 4°C',
|
|
isCorrect: true,
|
|
type: 'multiple_choice'
|
|
},
|
|
{
|
|
questionId: '2',
|
|
question: 'Langkah pertama dalam sistem HACCP adalah...',
|
|
userAnswer: 'Analisis bahaya',
|
|
correctAnswer: 'Analisis bahaya',
|
|
isCorrect: true,
|
|
type: 'multiple_choice'
|
|
},
|
|
// Add more mock answers as needed
|
|
]
|
|
};
|
|
|
|
setTimeout(() => {
|
|
setExamResult(mockResult);
|
|
setLoading(false);
|
|
}, 1000);
|
|
}, []);
|
|
|
|
const getScoreColor = (score: number) => {
|
|
if (score >= 80) return 'text-green-600';
|
|
if (score >= 60) return 'text-yellow-600';
|
|
return 'text-red-600';
|
|
};
|
|
|
|
const getScoreBgColor = (score: number) => {
|
|
if (score >= 80) return 'bg-green-100 border-green-200';
|
|
if (score >= 60) return 'bg-yellow-100 border-yellow-200';
|
|
return 'bg-red-100 border-red-200';
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
<div className="text-center">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
|
<p className="text-gray-600">Memproses hasil ujian...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!examResult) {
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
<div className="text-center">
|
|
<XCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-2">Hasil ujian tidak ditemukan</h2>
|
|
<button
|
|
onClick={() => router.push('/exams')}
|
|
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
|
|
>
|
|
Kembali ke Daftar Ujian
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 py-8">
|
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
{/* Header */}
|
|
<div className="text-center mb-8">
|
|
<div className="flex justify-center mb-4">
|
|
{examResult.score >= 80 ? (
|
|
<CheckCircle className="w-16 h-16 text-green-500" />
|
|
) : examResult.score >= 60 ? (
|
|
<Award className="w-16 h-16 text-yellow-500" />
|
|
) : (
|
|
<XCircle className="w-16 h-16 text-red-500" />
|
|
)}
|
|
</div>
|
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
|
{examResult.score >= 80 ? 'Selamat!' : examResult.score >= 60 ? 'Cukup Baik!' : 'Perlu Perbaikan'}
|
|
</h1>
|
|
<p className="text-gray-600">Anda telah menyelesaikan ujian</p>
|
|
</div>
|
|
|
|
{/* Score Summary */}
|
|
<div className={cn(
|
|
"bg-white rounded-xl shadow-lg p-6 mb-8 border-2",
|
|
getScoreBgColor(examResult.score)
|
|
)}>
|
|
<div className="text-center">
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-4">{examResult.examTitle}</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
<div className="text-center">
|
|
<div className={cn("text-4xl font-bold mb-2", getScoreColor(examResult.score))}>
|
|
{examResult.score}%
|
|
</div>
|
|
<p className="text-gray-600">Skor Akhir</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-gray-900 mb-2">
|
|
{examResult.correctAnswers}/{examResult.totalQuestions}
|
|
</div>
|
|
<p className="text-gray-600">Jawaban Benar</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-2xl font-bold text-gray-900 mb-2 flex items-center justify-center gap-2">
|
|
<Clock className="w-6 h-6" />
|
|
{examResult.timeSpent}
|
|
</div>
|
|
<p className="text-gray-600">Waktu Pengerjaan</p>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-lg font-semibold text-gray-900 mb-2">
|
|
{examResult.completedAt}
|
|
</div>
|
|
<p className="text-gray-600">Selesai Pada</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Detailed Results */}
|
|
<div className="bg-white rounded-xl shadow-lg p-6 mb-8">
|
|
<h3 className="text-xl font-bold text-gray-900 mb-6 flex items-center gap-2">
|
|
<BarChart3 className="w-6 h-6" />
|
|
Detail Jawaban
|
|
</h3>
|
|
<div className="space-y-4">
|
|
{examResult.answers.map((answer, index) => (
|
|
<div
|
|
key={answer.questionId}
|
|
className={cn(
|
|
"p-4 rounded-lg border-2",
|
|
answer.isCorrect
|
|
? "bg-green-50 border-green-200"
|
|
: "bg-red-50 border-red-200"
|
|
)}
|
|
>
|
|
<div className="flex items-start gap-3">
|
|
<div className="flex-shrink-0">
|
|
{answer.isCorrect ? (
|
|
<CheckCircle className="w-6 h-6 text-green-600" />
|
|
) : (
|
|
<XCircle className="w-6 h-6 text-red-600" />
|
|
)}
|
|
</div>
|
|
<div className="flex-1">
|
|
<h4 className="font-semibold text-gray-900 mb-2">
|
|
Soal {index + 1}: {answer.question}
|
|
</h4>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<p className="text-sm text-gray-600 mb-1">Jawaban Anda:</p>
|
|
<p className={cn(
|
|
"font-medium",
|
|
answer.isCorrect ? "text-green-700" : "text-red-700"
|
|
)}>
|
|
{typeof answer.userAnswer === 'string' ? answer.userAnswer : 'Tidak dijawab'}
|
|
</p>
|
|
</div>
|
|
{!answer.isCorrect && (
|
|
<div>
|
|
<p className="text-sm text-gray-600 mb-1">Jawaban Benar:</p>
|
|
<p className="font-medium text-green-700">
|
|
{typeof answer.correctAnswer === 'string' ? answer.correctAnswer : 'N/A'}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
<button
|
|
onClick={() => router.push('/exams')}
|
|
className="flex items-center justify-center gap-2 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium"
|
|
>
|
|
<FileText className="w-5 h-5" />
|
|
Lihat Ujian Lain
|
|
</button>
|
|
<button
|
|
onClick={() => router.push('/')}
|
|
className="flex items-center justify-center gap-2 px-6 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors font-medium"
|
|
>
|
|
<Home className="w-5 h-5" />
|
|
Kembali ke Dashboard
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |