From 44d9ce2b2981f08536cf8e463310db1afed30655 Mon Sep 17 00:00:00 2001
From: Nasser AlKawari <60303522@udst.edu.qa>
Date: Sat, 11 Apr 2026 20:01:01 +0300
Subject: [PATCH] fix: resolve quiz score calculation and MongoDB validation
errors
---
backend/models/Result.js | 45 +++++---
backend/routes/quizRoutes.js | 92 ++++++++++++++++-
backend/services/scoreService.js | 70 ++++---------
src/pages/DashboardPage.jsx | 170 +++++++++++++++++++++++--------
4 files changed, 266 insertions(+), 111 deletions(-)
diff --git a/backend/models/Result.js b/backend/models/Result.js
index c540504..335e321 100644
--- a/backend/models/Result.js
+++ b/backend/models/Result.js
@@ -1,19 +1,34 @@
const mongoose = require('mongoose');
-const resultDetailSchema = new mongoose.Schema({
- question: { type: String, required: true },
- userAnswer: { type: String },
- correctAnswer: { type: String, required: true },
- isCorrect: { type: Boolean, required: true },
-});
-
-const resultSchema = new mongoose.Schema({
- quizId: { type: mongoose.Schema.Types.ObjectId, ref: 'Quiz' },
- score: { type: Number, required: true },
- total: { type: Number, required: true },
- percentage: { type: Number, required: true },
- results: { type: [resultDetailSchema], required: true },
- submittedAt:{ type: Date, default: Date.now },
-});
+const resultSchema = new mongoose.Schema(
+ {
+ quizId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: 'Quiz',
+ default: null,
+ },
+ score: {
+ type: Number,
+ required: true,
+ },
+ total: {
+ type: Number,
+ required: true,
+ },
+ correctAnswers: {
+ type: Number,
+ required: true,
+ },
+ wrongAnswers: {
+ type: Number,
+ required: true,
+ },
+ answers: {
+ type: [String],
+ default: [],
+ },
+ },
+ { timestamps: true }
+);
module.exports = mongoose.model('Result', resultSchema);
\ No newline at end of file
diff --git a/backend/routes/quizRoutes.js b/backend/routes/quizRoutes.js
index 5a77605..270923a 100644
--- a/backend/routes/quizRoutes.js
+++ b/backend/routes/quizRoutes.js
@@ -49,25 +49,107 @@ router.post('/submit', async (req, res) => {
if (!questions || !Array.isArray(questions)) {
return res.status(400).json({ error: 'Bad Request: "questions" must be an array' });
}
+
if (!answers || !Array.isArray(answers)) {
return res.status(400).json({ error: 'Bad Request: "answers" must be an array' });
}
+
if (questions.length === 0) {
- return res.status(400).json({ error: 'Bad Request: "questions" array cannot be empty' });
+ return res.status(400).json({ error: 'Bad Request: "questions" cannot be empty' });
+ }
+
+ if (questions.length !== answers.length) {
+ return res.status(400).json({ error: 'Mismatch between questions and answers length' });
}
const scoreResult = calculateScore(questions, answers);
+ // Ensure all required fields exist
+ const normalizedResult = {
+ score: scoreResult.score,
+ total: scoreResult.total,
+ correctAnswers: scoreResult.correctAnswers,
+ wrongAnswers: scoreResult.wrongAnswers,
+ };
+
// Save result to MongoDB
const result = await Result.create({
quizId: quizId || null,
- ...scoreResult,
+ ...normalizedResult,
+ answers,
+ });
+
+ return res.status(200).json({
+ resultId: result._id,
+ ...normalizedResult,
});
- return res.status(200).json({ resultId: result._id, ...scoreResult });
} catch (error) {
- const statusCode = error.message.includes('Mismatch') || error.message.includes('malformed') ? 400 : 500;
- return res.status(statusCode).json({ error: 'Failed to calculate score', details: error.message });
+ return res.status(500).json({
+ error: 'Failed to calculate score',
+ details: error.message,
+ });
+ }
+});
+
+router.get('/results', async (req, res) => {
+ try {
+ const results = await Result.find()
+ .populate('quizId')
+ .sort({ createdAt: -1 });
+
+ return res.status(200).json(results);
+ } catch (error) {
+ return res.status(500).json({
+ error: 'Failed to fetch results',
+ details: error.message,
+ });
+ }
+});
+
+router.get('/dashboard', async (req, res) => {
+ try {
+ const results = await Result.find()
+ .populate('quizId')
+ .sort({ createdAt: -1 });
+
+ const completed = results.length;
+
+ const avgScore = completed
+ ? Math.round(
+ results.reduce((sum, item) => sum + (item.total ? (item.score / item.total) * 100 : 0), 0) / completed
+ )
+ : 0;
+
+ const recentQuizzes = results.slice(0, 5).map((item) => ({
+ id: item._id,
+ topic: item.quizId?.topic || 'Untitled Quiz',
+ score: item.score,
+ total: item.total,
+ percentage: item.total ? Math.round((item.score / item.total) * 100) : 0,
+ date: item.createdAt,
+ }));
+
+ const chartData = results
+ .slice(0, 7)
+ .reverse()
+ .map((item) => ({
+ id: item._id,
+ percentage: item.total ? Math.round((item.score / item.total) * 100) : 0,
+ }));
+
+ return res.status(200).json({
+ completed,
+ avgScore,
+ recentQuizzes,
+ chartData,
+ latestQuiz: recentQuizzes[0] || null,
+ });
+ } catch (error) {
+ return res.status(500).json({
+ error: 'Failed to fetch dashboard data',
+ details: error.message,
+ });
}
});
diff --git a/backend/services/scoreService.js b/backend/services/scoreService.js
index 013ed8a..5a8da12 100644
--- a/backend/services/scoreService.js
+++ b/backend/services/scoreService.js
@@ -1,70 +1,40 @@
-// Calculate score from quiz submission
-function calculateScore(questions, userAnswers) {
- // Validate inputs
- if (!Array.isArray(questions) || questions.length === 0) {
- throw new Error('Questions array must be a non-empty array');
+function calculateScore(questions, answers) {
+ if (!Array.isArray(questions) || !Array.isArray(answers)) {
+ throw new Error("Questions and answers must be arrays");
}
- if (!Array.isArray(userAnswers)) {
- throw new Error('User answers must be an array');
+ if (questions.length !== answers.length) {
+ throw new Error("Mismatch between questions and answers length");
}
- if (questions.length !== userAnswers.length) {
- throw new Error(
- `Mismatch: ${questions.length} questions but ${userAnswers.length} answers provided`
- );
- }
-
- let score = 0;
- const results = [];
+ let correctAnswers = 0;
for (let i = 0; i < questions.length; i++) {
const question = questions[i];
- const userAnswer = userAnswers[i];
+ const userAnswer = answers[i];
- // Validate question structure
- if (
- !question ||
- typeof question !== 'object' ||
- !question.question ||
- !question.answer
- ) {
- throw new Error(`Question ${i + 1} is malformed`);
+ if (!question || typeof question !== "object") {
+ throw new Error(`Malformed question at index ${i}`);
}
- // Handle null/undefined answers
- const normalizedUserAnswer =
- userAnswer === null || userAnswer === undefined
- ? null
- : String(userAnswer).toUpperCase().trim();
-
- const correctAnswer = String(question.answer).toUpperCase().trim();
- const isCorrect =
- normalizedUserAnswer === correctAnswer && normalizedUserAnswer !== null;
-
- if (isCorrect) {
- score++;
+ if (!question.answer) {
+ throw new Error(`Missing answer field in question at index ${i}`);
}
- results.push({
- question: question.question,
- userAnswer: normalizedUserAnswer,
- correctAnswer: correctAnswer,
- isCorrect: isCorrect,
- });
+ if (userAnswer === question.answer) {
+ correctAnswers++;
+ }
}
const total = questions.length;
- const percentage = Math.round((score / total) * 100);
+ const wrongAnswers = total - correctAnswers;
return {
- score: score,
- total: total,
- percentage: percentage,
- results: results,
+ score: correctAnswers,
+ total,
+ correctAnswers,
+ wrongAnswers,
};
}
-module.exports = {
- calculateScore,
-};
+module.exports = { calculateScore };
\ No newline at end of file
diff --git a/src/pages/DashboardPage.jsx b/src/pages/DashboardPage.jsx
index 78dcb2c..bd7ed36 100644
--- a/src/pages/DashboardPage.jsx
+++ b/src/pages/DashboardPage.jsx
@@ -1,7 +1,72 @@
+import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
function DashboardPage() {
const navigate = useNavigate();
+ const [dashboardData, setDashboardData] = useState({
+ completed: 0,
+ avgScore: 0,
+ recentQuizzes: [],
+ chartData: [],
+ latestQuiz: null,
+ });
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState("");
+
+ useEffect(() => {
+ const fetchDashboardData = async () => {
+ try {
+ setLoading(true);
+ setError("");
+
+ const response = await fetch("http://localhost:5000/api/quiz/dashboard");
+ const data = await response.json();
+
+ if (!response.ok) {
+ throw new Error(data.error || "Failed to fetch dashboard data.");
+ }
+
+ setDashboardData(data);
+ } catch (err) {
+ setError(err.message || "Failed to load dashboard.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchDashboardData();
+ }, []);
+
+ const { completed, avgScore, recentQuizzes, chartData, latestQuiz } = dashboardData;
+
+ const safeChartData = [...chartData];
+ while (safeChartData.length < 7) {
+ safeChartData.unshift({ percentage: 0 });
+ }
+
+ if (loading) {
+ return (
+
+
+
Loading dashboard...
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+
Failed to load dashboard
+
{error}
+
navigate("/generate")}>
+ Go to Generate Quiz
+
+
+
+ );
+ }
return (
@@ -19,14 +84,20 @@ function DashboardPage() {
Settings
-
+ New Quiz
+
navigate("/generate")}>
+ + New Quiz
+
Welcome back, Alex.
-
Your cognitive flow is at 84% today.
+
+ {completed > 0
+ ? `You have completed ${completed} quiz${completed > 1 ? "zes" : ""} so far.`
+ : "Start your first quiz to see your progress here."}
+
Profile
@@ -34,15 +105,26 @@ function DashboardPage() {
-
IN PROGRESS
-
Quantum Mechanics 101
+
+ {latestQuiz ? "LATEST RESULT" : "NO QUIZ YET"}
+
+
+
{latestQuiz ? latestQuiz.topic : "No quiz attempts yet"}
+
- You’ve completed 14 of 20 questions. Picking up where you left off
- will boost your recall score.
+ {latestQuiz
+ ? `You scored ${latestQuiz.score}/${latestQuiz.total} on your latest quiz.`
+ : "Generate and complete a quiz to populate your dashboard with real data."}
-
Resume Quiz
+
+
navigate("/generate")}>
+ {latestQuiz ? "Take Another Quiz" : "Start Quiz"}
+
+
+
+
+ {latestQuiz ? `${latestQuiz.percentage}%` : "0%"}
-
70%
@@ -52,11 +134,11 @@ function DashboardPage() {
- 128
+ {completed}
Completed
- 88%
+ {avgScore}%
Avg Score
@@ -64,47 +146,53 @@ function DashboardPage() {
Score History
-
Performance over the last 30 days
+
Performance over recent attempts
+
-
-
-
-
-
-
-
+ {safeChartData.map((item, index) => (
+ x.percentage)) &&
+ item.percentage > 0
+ ? "active-bar"
+ : ""
+ }
+ style={{ height: `${Math.max(item.percentage, 15) * 2}px` }}
+ >
+ ))}
Recent Quizzes
- View All
-
-
-
-
-
Modern History
-
Oct 12, 2024
-
-
9/10
+
navigate("/generate")}>
+ New Quiz
+
-
-
-
Bio-Genetics
-
Oct 10, 2024
+ {recentQuizzes.length === 0 ? (
+
+
+
No quizzes yet
+
Complete a quiz to see history here.
+
+
0/0
-
7/10
-
-
-
-
-
React Fundamentals
-
Oct 08, 2024
-
-
8/10
-
+ ) : (
+ recentQuizzes.map((item) => (
+
+
+
{item.topic}
+
{new Date(item.date).toLocaleDateString()}
+
+
+ {item.score}/{item.total}
+
+
+ ))
+ )}