Files
examjam-frontend/app/exam/results/page.tsx

268 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useRouter } from "next/navigation";
import { useExam, useExamResults } from "@/context/ExamContext";
import { useEffect, useState } from "react";
import React from "react";
import { ArrowLeft } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import Image from "next/image";
import SlidingGallery from "@/components/SlidingGallery";
interface Question {
correctAnswer: string;
id: number;
question: string;
options: Record<string, string>;
solution?: string;
}
interface QuestionItemProps {
question: Question;
selectedAnswer: string | undefined;
}
const QuestionItem = ({ question, selectedAnswer }: QuestionItemProps) => (
<div className="border border-[#8abdff]/50 rounded-2xl p-4 flex flex-col gap-7">
<h3 className="text-xl font-medium">
{question.id}. {question.question}
</h3>
<div className="flex justify-between items-center">
<div></div>
{!selectedAnswer ? (
<Badge className="bg-yellow-500" variant="destructive">
Skipped
</Badge>
) : selectedAnswer.answer === question.correctAnswer ? (
<Badge className="bg-green-500 text-white" variant="default">
Correct
</Badge>
) : (
<Badge className="bg-red-500 text-white" variant="default">
Incorrect
</Badge>
)}
</div>
<div className="flex flex-col gap-4 items-start">
{Object.entries(question.options).map(([key, value]) => {
const isCorrect = key === question.correctAnswer;
const isSelected = key === selectedAnswer?.answer;
let optionStyle =
"px-2 py-1 flex items-center rounded-full border font-medium text-sm";
if (isCorrect) {
optionStyle += " bg-green-600 text-white border-green-600";
}
if (isSelected && !isCorrect) {
optionStyle += " bg-red-600 text-white border-red-600";
}
if (!isCorrect && !isSelected) {
optionStyle += " border-gray-300 text-gray-700";
}
return (
<div key={key} className="flex items-center gap-3">
<span className={optionStyle}>{key.toUpperCase()}</span>
<span className="option-description">{value}</span>
</div>
);
})}
</div>
<div className="h-[0.5px] border-[0.5px] border-dashed border-black/20"></div>
<div className="flex flex-col gap-2">
<h3 className="text-lg font-bold text-black/40">Solution:</h3>
<p className="text-lg">{question.solution}</p>
</div>
</div>
);
export default function ResultsPage() {
const router = useRouter();
const { clearExam, isExamCompleted, getApiResponse } = useExam();
let examResults;
useEffect(() => {
if (!isExamCompleted()) {
router.push("/unit");
return;
}
}, [isExamCompleted, router]);
try {
examResults = useExamResults();
console.log(examResults);
} catch (error) {
// Handle case where there's no completed exam
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="mt-60 flex flex-col items-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mb-4"></div>
<p className="text-xl font-medium text-center">Loading...</p>
</div>
</div>
</div>
);
}
// Get API response data
const apiResponse = getApiResponse();
const handleBackToHome = () => {
clearExam();
// Give time for state to fully reset before pushing new route
setTimeout(() => {
router.push("/unit");
}, 400); // 50100ms is usually enough
};
const timeTaken =
examResults.endTime && examResults.startTime
? Math.round(
(examResults.endTime.getTime() - examResults.startTime.getTime()) /
1000 /
60
)
: 0;
const resultViews = [
{
id: 1,
content: (
<div className="w-full">
<div className="bg-blue-50/60 border border-[#113678]/50 rounded-4xl h-[170px] flex flex-col items-center justify-center gap-4">
<div className="text-xl text-black ">
<span className="font-bold">Accuracy</span> Rate:
</div>
<div className="flex gap-4">
<Image
src="/images/icons/accuracy.png"
alt="accuracy"
width={60}
height={60}
/>
<h2 className="text-6xl font-bold text-[#113678]">
{(
(examResults.score / examResults.totalQuestions) *
100
).toFixed(1)}
%
</h2>
</div>
<div></div>
</div>
</div>
),
},
{
id: 2,
content: (
<div className=" w-full">
<div className="bg-blue-50/60 border border-[#113678]/50 rounded-4xl h-[170px] flex flex-col items-center justify-center gap-3">
<div className="text-xl text-black ">
<span className="font-bold">Error</span> Rate:
</div>
<div className="flex gap-4">
<Image
src="/images/icons/error.png"
alt="accuracy"
width={60}
height={60}
/>
<h2 className="text-6xl font-bold text-[#113678]">
{(
((examResults.totalQuestions - examResults.score) /
examResults.totalQuestions) *
100
).toFixed(1)}
%
</h2>
</div>
<div></div>
</div>
</div>
),
},
{
id: 3,
content: (
<div className="my-8 w-full">
<div className="bg-blue-50/60 border border-[#113678]/50 rounded-4xl h-[170px] flex flex-col items-center justify-center gap-4">
<div className="text-xl text-black">
<span className="font-bold">Attempt</span> Rate:
</div>
<div className="flex gap-4">
<Image
src="/images/icons/attempt.png"
alt="accuracy"
width={60}
height={60}
/>
<h2 className="text-6xl font-bold text-[#113678]">
{(
(examResults.answers.length / examResults.totalQuestions) *
100
).toFixed(1)}
%
</h2>
</div>
<div></div>
</div>
</div>
),
},
];
return (
<div className="min-h-screen bg-white">
<button className="p-10" onClick={() => handleBackToHome()}>
<ArrowLeft size={30} color="black" />
</button>
<div className="bg-white rounded-lg shadow-lg px-10 pb-20">
<h1 className="text-2xl font-bold text-[#113768] mb-4 text-center">
{examResults?.score < 30
? "Try harder!"
: examResults?.score < 70
? "Getting Better"
: "You did great!"}
</h1>
{/* Score Display */}
<SlidingGallery className="my-8" views={resultViews} height="170px" />
{apiResponse && (
<div className="mb-8">
<h3 className="text-2xl font-bold text-[#113768] mb-4">
Solutions
</h3>
<div className="flex flex-col gap-7">
{apiResponse.questions?.map((question) => (
<QuestionItem
key={question.id}
question={question}
selectedAnswer={examResults.answers?.[question.id]}
/>
))}
</div>
</div>
)}
{/* Action Buttons */}
</div>
<button
onClick={handleBackToHome}
className="fixed bottom-0 w-full bg-blue-900 text-white h-[74px] font-bold text-lg disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Finish
</button>
</div>
);
}