generated from muhtadeetaron/nextjs-template
fix(ui): refactor results page for exam results logic
This commit is contained in:
@ -1,122 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useExam } from "@/context/ExamContext";
|
||||
import { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import SlidingGallery from "@/components/SlidingGallery";
|
||||
import { useExamStore } from "@/stores/examStore";
|
||||
import QuestionItem from "@/components/QuestionItem";
|
||||
import SlidingGallery from "@/components/SlidingGallery";
|
||||
import { getResultViews } from "@/lib/gallery-views";
|
||||
import { Question } from "@/types/exam";
|
||||
|
||||
export default function ResultsPage() {
|
||||
const router = useRouter();
|
||||
const {
|
||||
clearExam,
|
||||
isExamCompleted,
|
||||
getApiResponse,
|
||||
currentAttempt,
|
||||
isHydrated,
|
||||
} = useExam();
|
||||
const { result, clearResult } = useExamStore();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Wait for hydration first
|
||||
if (!isHydrated) return;
|
||||
|
||||
// Check if exam is completed, redirect if not
|
||||
if (!isExamCompleted() || !currentAttempt) {
|
||||
router.push("/unit");
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have exam results, we're ready to render
|
||||
if (currentAttempt?.answers) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [isExamCompleted, currentAttempt, isHydrated, router]);
|
||||
|
||||
const handleBackToHome = () => {
|
||||
clearExam();
|
||||
router.push("/unit");
|
||||
};
|
||||
|
||||
// Show loading screen while initializing or if no exam results
|
||||
if (isLoading || !currentAttempt) {
|
||||
if (!result) {
|
||||
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 className="min-h-screen flex items-center justify-center">
|
||||
<p className="text-lg font-medium">No results to display.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const apiResponse = getApiResponse();
|
||||
|
||||
// const timeTaken =
|
||||
// currentAttempt.endTime && currentAttempt.startTime
|
||||
// ? Math.round(
|
||||
// (currentAttempt.endTime.getTime() -
|
||||
// currentAttempt.startTime.getTime()) /
|
||||
// 1000 /
|
||||
// 60
|
||||
// )
|
||||
// : 0;
|
||||
|
||||
const views = getResultViews(currentAttempt);
|
||||
|
||||
// Get score-based message
|
||||
const getScoreMessage = () => {
|
||||
if (!currentAttempt.score || currentAttempt.score < 30)
|
||||
return "Try harder!";
|
||||
if (currentAttempt.score < 70) return "Getting Better";
|
||||
return "You did great!";
|
||||
const handleBackToHome = () => {
|
||||
clearResult();
|
||||
router.push("/categories");
|
||||
};
|
||||
|
||||
const views = getResultViews(result);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<button className="p-10" onClick={handleBackToHome}>
|
||||
<button className="px-10 pt-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">
|
||||
{getScoreMessage()}
|
||||
<h1 className="text-2xl font-bold text-[#113768] text-center">
|
||||
You did great!
|
||||
</h1>
|
||||
|
||||
{/* Score Display */}
|
||||
<SlidingGallery className="my-8" views={views} height="170px" />
|
||||
<SlidingGallery views={views} height={"26vh"} />
|
||||
|
||||
{apiResponse?.questions && (
|
||||
<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: Question) => (
|
||||
<QuestionItem
|
||||
key={question.id}
|
||||
question={question}
|
||||
selectedAnswer={
|
||||
currentAttempt.answers[parseInt(question.id) - 1]
|
||||
}
|
||||
mode="result"
|
||||
/>
|
||||
))}
|
||||
{/* Render questions with correctness */}
|
||||
{result.user_questions.map((q, idx) => {
|
||||
const userAnswer = result.user_answers[idx];
|
||||
const correctAnswer = result.correct_answers[idx];
|
||||
|
||||
return (
|
||||
<div key={q.question_id} className={`rounded-3xl mb-6`}>
|
||||
<QuestionItem
|
||||
question={q}
|
||||
index={idx}
|
||||
selectedAnswer={userAnswer}
|
||||
onSelect={() => {}} // disabled in results
|
||||
/>
|
||||
|
||||
{/* Answer feedback */}
|
||||
<div className="mt-2 text-sm">
|
||||
{userAnswer === null ? (
|
||||
<span className="text-yellow-600 font-medium">
|
||||
Skipped — Correct: {String.fromCharCode(65 + correctAnswer)}
|
||||
</span>
|
||||
) : userAnswer === correctAnswer ? (
|
||||
<span className="text-green-600 font-medium">Correct</span>
|
||||
) : (
|
||||
<span className="text-red-600 font-medium">
|
||||
Your Answer: {String.fromCharCode(65 + userAnswer)} |
|
||||
Correct Answer: {String.fromCharCode(65 + correctAnswer)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})}
|
||||
</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"
|
||||
className="fixed bottom-0 w-full bg-blue-900 text-white h-[74px] font-bold text-lg hover:bg-blue-800 transition-colors"
|
||||
>
|
||||
Finish
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user