generated from muhtadeetaron/nextjs-template
fix(ui): fix timer reappearing after exam submission
This commit is contained in:
@ -27,8 +27,9 @@ export default function ExamPage() {
|
|||||||
if (fetchedTest?.metadata.time_limit_minutes) {
|
if (fetchedTest?.metadata.time_limit_minutes) {
|
||||||
resetTimer(fetchedTest.metadata.time_limit_minutes * 60, () => {
|
resetTimer(fetchedTest.metadata.time_limit_minutes * 60, () => {
|
||||||
// Timer ended → auto-submit exam
|
// Timer ended → auto-submit exam
|
||||||
|
stopTimer();
|
||||||
submitExam(type);
|
submitExam(type);
|
||||||
router.push(`/categories/${type}s`);
|
router.push(`/exam/results`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -56,6 +57,7 @@ export default function ExamPage() {
|
|||||||
|
|
||||||
const handleSubmitExam = async (type: string) => {
|
const handleSubmitExam = async (type: string) => {
|
||||||
try {
|
try {
|
||||||
|
stopTimer();
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
await submitExam(type);
|
await submitExam(type);
|
||||||
router.push(`/exam/results`);
|
router.push(`/exam/results`);
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export default function ResultsPage() {
|
|||||||
if (!result) {
|
if (!result) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center">
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
<p className="text-lg font-medium">No results to display.</p>
|
<p className="text-lg font-medium">Redirecting...</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -46,16 +46,19 @@ export default function ResultsPage() {
|
|||||||
const correctAnswer = result.correct_answers[idx];
|
const correctAnswer = result.correct_answers[idx];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={q.question_id} className={`rounded-3xl mb-6`}>
|
<div key={q.question_id} className="rounded-3xl mb-6">
|
||||||
<QuestionItem
|
<QuestionItem
|
||||||
question={q}
|
question={q}
|
||||||
index={idx}
|
index={idx}
|
||||||
selectedAnswer={userAnswer}
|
selectedAnswer={userAnswer}
|
||||||
onSelect={() => {}} // disabled in results
|
onSelect={() => {}}
|
||||||
|
userAnswer={userAnswer}
|
||||||
|
correctAnswer={correctAnswer}
|
||||||
|
showResults={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Answer feedback */}
|
{/* Optional answer feedback below the question */}
|
||||||
<div className="mt-2 text-sm">
|
{/* <div className="mt-2 text-sm">
|
||||||
{userAnswer === null ? (
|
{userAnswer === null ? (
|
||||||
<span className="text-yellow-600 font-medium">
|
<span className="text-yellow-600 font-medium">
|
||||||
Skipped — Correct: {String.fromCharCode(65 + correctAnswer)}
|
Skipped — Correct: {String.fromCharCode(65 + correctAnswer)}
|
||||||
@ -68,7 +71,7 @@ export default function ResultsPage() {
|
|||||||
Correct Answer: {String.fromCharCode(65 + correctAnswer)}
|
Correct Answer: {String.fromCharCode(65 + correctAnswer)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -9,6 +9,9 @@ interface QuestionItemProps {
|
|||||||
index: number;
|
index: number;
|
||||||
selectedAnswer: Answer;
|
selectedAnswer: Answer;
|
||||||
onSelect: (answer: Answer) => void;
|
onSelect: (answer: Answer) => void;
|
||||||
|
userAnswer?: Answer; // new
|
||||||
|
correctAnswer?: Answer; // new
|
||||||
|
showResults?: boolean; // control whether to highlight or not
|
||||||
}
|
}
|
||||||
|
|
||||||
const letters = ["A", "B", "C", "D"]; // extend if needed
|
const letters = ["A", "B", "C", "D"]; // extend if needed
|
||||||
@ -18,6 +21,9 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
|
|||||||
index,
|
index,
|
||||||
selectedAnswer,
|
selectedAnswer,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
userAnswer,
|
||||||
|
correctAnswer,
|
||||||
|
showResults = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="border border-blue-100 p-6 bg-slate-100 rounded-3xl mb-6">
|
<div className="border border-blue-100 p-6 bg-slate-100 rounded-3xl mb-6">
|
||||||
@ -25,10 +31,12 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
|
|||||||
{index + 1}. {question.question}
|
{index + 1}. {question.question}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="w-full flex justify-between">
|
{!showResults && (
|
||||||
<div></div>
|
<div className="w-full flex justify-between">
|
||||||
<Bookmark size={24} />
|
<div></div>
|
||||||
</div>
|
<Bookmark size={24} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{question.options.map((opt, optIdx) => {
|
{question.options.map((opt, optIdx) => {
|
||||||
@ -38,10 +46,41 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
|
|||||||
: Array.isArray(selectedAnswer) &&
|
: Array.isArray(selectedAnswer) &&
|
||||||
selectedAnswer.includes(optIdx);
|
selectedAnswer.includes(optIdx);
|
||||||
|
|
||||||
|
// ✅ logic for coloring after results
|
||||||
|
let btnClasses = "bg-gray-100 text-gray-900 border-gray-400";
|
||||||
|
if (isSelected) {
|
||||||
|
btnClasses = "bg-blue-600 text-white border-blue-600";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showResults && correctAnswer !== undefined) {
|
||||||
|
if (question.type === "Single") {
|
||||||
|
if (userAnswer === optIdx && userAnswer !== correctAnswer) {
|
||||||
|
btnClasses = "bg-red-500 text-white border-red-600"; // wrong
|
||||||
|
}
|
||||||
|
if (correctAnswer === optIdx) {
|
||||||
|
btnClasses = "bg-green-500 text-white border-green-600"; // correct
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Multi-select case
|
||||||
|
const userSelected =
|
||||||
|
Array.isArray(userAnswer) && userAnswer.includes(optIdx);
|
||||||
|
const isCorrect =
|
||||||
|
Array.isArray(correctAnswer) && correctAnswer.includes(optIdx);
|
||||||
|
|
||||||
|
if (userSelected && !isCorrect) {
|
||||||
|
btnClasses = "bg-red-500 text-white border-red-600";
|
||||||
|
}
|
||||||
|
if (isCorrect) {
|
||||||
|
btnClasses = "bg-green-500 text-white border-green-600";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={optIdx} className="flex items-center gap-3">
|
<div key={optIdx} className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (showResults) return; // disable changes in results mode
|
||||||
if (question.type === "Single") {
|
if (question.type === "Single") {
|
||||||
onSelect(optIdx);
|
onSelect(optIdx);
|
||||||
} else {
|
} else {
|
||||||
@ -58,12 +97,8 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
|
|||||||
}}
|
}}
|
||||||
className={`w-7 h-7 rounded-full border font-bold
|
className={`w-7 h-7 rounded-full border font-bold
|
||||||
flex items-center justify-center
|
flex items-center justify-center
|
||||||
${
|
${btnClasses}
|
||||||
isSelected
|
transition-colors`}
|
||||||
? "bg-blue-600 text-white border-blue-600"
|
|
||||||
: "bg-gray-100 text-gray-900 border-gray-400"
|
|
||||||
}
|
|
||||||
hover:bg-blue-500 hover:text-white transition-colors`}
|
|
||||||
>
|
>
|
||||||
{letters[optIdx]}
|
{letters[optIdx]}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user