diff --git a/package-lock.json b/package-lock.json
index baa8926..bcc6ed9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2489,35 +2489,6 @@
"tailwindcss": "4.1.8"
}
},
- "node_modules/@tailwindcss/oxide": {
- "version": "4.1.8",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.8.tgz",
- "integrity": "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "dependencies": {
- "detect-libc": "^2.0.4",
- "tar": "^7.4.3"
- },
- "engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.1.8",
- "@tailwindcss/oxide-darwin-arm64": "4.1.8",
- "@tailwindcss/oxide-darwin-x64": "4.1.8",
- "@tailwindcss/oxide-freebsd-x64": "4.1.8",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8",
- "@tailwindcss/oxide-linux-arm64-musl": "4.1.8",
- "@tailwindcss/oxide-linux-x64-gnu": "4.1.8",
- "@tailwindcss/oxide-linux-x64-musl": "4.1.8",
- "@tailwindcss/oxide-wasm32-wasi": "4.1.8",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8",
- "@tailwindcss/oxide-win32-x64-msvc": "4.1.8"
- }
- },
"node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.8.tgz",
@@ -3317,6 +3288,69 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/d3-array": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
+ "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
+ "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -5263,6 +5297,12 @@
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
"license": "MIT"
},
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
+ "license": "MIT"
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -8196,18 +8236,18 @@
"license": "MIT"
},
"node_modules/motion-dom": {
- "version": "12.16.0",
- "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.16.0.tgz",
- "integrity": "sha512-Z2nGwWrrdH4egLEtgYMCEN4V2qQt1qxlKy/uV7w691ztyA41Q5Rbn0KNGbsNVDZr9E8PD2IOQ3hSccRnB6xWzw==",
+ "version": "12.18.1",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.18.1.tgz",
+ "integrity": "sha512-dR/4EYT23Snd+eUSLrde63Ws3oXQtJNw/krgautvTfwrN/2cHfCZMdu6CeTxVfRRWREW3Fy1f5vobRDiBb/q+w==",
"license": "MIT",
"dependencies": {
- "motion-utils": "^12.12.1"
+ "motion-utils": "^12.18.1"
}
},
"node_modules/motion-utils": {
- "version": "12.12.1",
- "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.12.1.tgz",
- "integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==",
+ "version": "12.18.1",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.18.1.tgz",
+ "integrity": "sha512-az26YDU4WoDP0ueAkUtABLk2BIxe28d8NH1qWT8jPGhPyf44XTdDUh8pDk9OPphaSrR9McgpcJlgwSOIw/sfkA==",
"license": "MIT"
},
"node_modules/ms": {
@@ -9267,6 +9307,66 @@
}
}
},
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz",
+ "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.0.0",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.21",
+ "react-is": "^18.3.1",
+ "react-smooth": "^4.0.4",
+ "recharts-scale": "^0.4.4",
+ "tiny-invariant": "^1.3.1",
+ "victory-vendor": "^36.6.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/recharts-scale": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
+ "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+ "license": "MIT",
+ "dependencies": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
+ "node_modules/recharts/node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
+ "node_modules/recharts/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
"node_modules/reflect-metadata": {
"version": "0.1.14",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz",
@@ -10189,6 +10289,12 @@
"node": ">=0.8"
}
},
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
"node_modules/tinyglobby": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
diff --git a/src/app/(test)/student-results/page.tsx b/src/app/(test)/student-results/page.tsx
index 1a80f12..f4adea2 100644
--- a/src/app/(test)/student-results/page.tsx
+++ b/src/app/(test)/student-results/page.tsx
@@ -12,6 +12,7 @@ import {
TestSummary,
} from "@/components/results";
import { MockResultsAPI } from "@/lib/results-api";
+import { StudentPerformanceTrendsChart } from "@/components/results/student/performance-trends-chart";
export default function StudentResultsPage() {
const router = useRouter();
@@ -170,7 +171,10 @@ export default function StudentResultsPage() {
Track your academic performance and progress
- {/* Overview cards removed as requested */}
+
+ {/* Performance Trends Chart */}
+
+
{/* Recent Tests */}
diff --git a/src/app/(test)/teacher-results/page.tsx b/src/app/(test)/teacher-results/page.tsx
index ab0d2a7..2b2c7ef 100644
--- a/src/app/(test)/teacher-results/page.tsx
+++ b/src/app/(test)/teacher-results/page.tsx
@@ -13,9 +13,7 @@ import {
CoursesGrid,
RecentTestsCard,
CourseTestsTable,
- PerformanceDistributionChart,
StudentResultsTable,
- QuestionStatsTable,
SortOption,
SortDropdown,
} from "@/components/results/teacher";
@@ -29,9 +27,16 @@ import {
BookOpen,
Clock,
FileBarChart,
+ LayoutGrid,
+ Rows3,
} from "lucide-react";
import { Separator } from "@/components/ui/separator";
import { toast } from "sonner";
+import { QuestionBarChart } from "@/components/results/common/question-bar-chart";
+import { MarksPieChart } from "@/components/results/common/marks-pie-chart";
+import { StudentPerformancePieChart } from "@/components/results/teacher/student-performance-pie-chart";
+import { MarksFrequencyBarChart } from "@/components/results/teacher/marks-frequency-bar-chart";
+import { QuestionWiseBarChart } from "@/components/results/teacher/question-wise-bar-chart";
export default function TeacherResultsPage() {
const router = useRouter();
@@ -55,6 +60,7 @@ export default function TeacherResultsPage() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState
(null);
+ const [chartsStacked, setChartsStacked] = useState(false);
// Load initial data (courses and recent tests)
const loadInitialData = React.useCallback(async () => {
@@ -491,7 +497,12 @@ export default function TeacherResultsPage() {
Average Score
- {testStatistics.averageScore.toFixed(1)}%
+ {testStatistics.averageScore.toFixed(1)}% (
+ {(
+ (testStatistics.averageScore / 100) *
+ testStatistics.totalMarks
+ ).toFixed(1)}
+ /{testStatistics.totalMarks})
@@ -502,7 +513,10 @@ export default function TeacherResultsPage() {
Submissions
- {testStatistics.totalSubmissions}
+ {testStatistics.totalSubmissions}{" "}
+
+ / {testStatistics.totalStudents ?? "-"}
+
@@ -513,6 +527,17 @@ export default function TeacherResultsPage() {
High / Low
+ {testStatistics.highestScore}% (
+ {(
+ (testStatistics.highestScore / 100) *
+ testStatistics.totalMarks
+ ).toFixed(1)}
+ ) / {testStatistics.lowestScore}% (
+ {(
+ (testStatistics.lowestScore / 100) *
+ testStatistics.totalMarks
+ ).toFixed(1)}
+ )
{testStatistics.highestScore}% /
{testStatistics.lowestScore}%
@@ -525,13 +550,99 @@ export default function TeacherResultsPage() {
Median Score
- {testStatistics.medianScore}%
+ {testStatistics.medianScore}% (
+ {(
+ (testStatistics.medianScore / 100) *
+ testStatistics.totalMarks
+ ).toFixed(1)}
+ /{testStatistics.totalMarks})
+ {/* --- PERFORMANCE GRAPHS SECTION --- */}
+
+
+
+
+
Performance Graphs
+
+
+
+ Chart Layout:
+
+
+
+
+
+
+
+
+
+ s.percentage,
+ )}
+ />
+
+
+ s.percentage,
+ )}
+ totalMarks={testStatistics.totalMarks}
+ />
+
+
+ {
+ const correctCount = Math.round(
+ (q.correctPercentage / 100) * q.attemptedCount,
+ );
+ return {
+ question: `Q${i + 1}`,
+ correct: correctCount,
+ wrong: q.attemptedCount - correctCount,
+ };
+ })}
+ />
+
+
+
{/* Performance Distribution Chart */}
Score Distribution
@@ -554,18 +665,46 @@ export default function TeacherResultsPage() {
/>
{/* Question Stats Table */}
-
+ {/*
Question Analysis
Performance metrics for each question on the test
+
+
+
+
+
+
*/}
>
)}
{currentView === "student-detail" && selectedStudentId && (
-
+ <>
+ {/* Move charts to the top for better visibility */}
+
+
+
+ Right/Wrong by Question
+
+
+
+
+
+ Mark Distribution
+
+
+
+
+ {/* Then show the individual student's detailed info */}
+
+ >
)}
diff --git a/src/components/results/common/detailed-test-result.tsx b/src/components/results/common/detailed-test-result.tsx
index 1803bde..f5b87b6 100644
--- a/src/components/results/common/detailed-test-result.tsx
+++ b/src/components/results/common/detailed-test-result.tsx
@@ -136,6 +136,34 @@ export const DetailedTestResultView: React.FC = ({
case "mcq":
return (
+ {question.options?.map((option, index) => {
+ const isCorrect = option.isCorrect;
+ const isSelected = question.studentAnswer === option.id;
+ return (
+
+
+
+ {String.fromCharCode(65 + index)}.
+
+
{option.text}
+ {isCorrect && (
+
+ Correct
+
+ )}
+ {isSelected && !isCorrect && (
{question.options?.map((option, index) => (
= ({
Your Answer
)}
+
-
- ))}
+ );
+ })}
);
@@ -304,7 +333,9 @@ export const DetailedTestResultView: React.FC = ({
Score
{result.percentage.toFixed(1)}%
@@ -367,6 +398,7 @@ export const DetailedTestResultView: React.FC = ({
Questions & Answers
+ {/* Remove the charts for students */}
{result.questions.map((question, index) => (
diff --git a/src/components/results/common/marks-pie-chart.tsx b/src/components/results/common/marks-pie-chart.tsx
new file mode 100644
index 0000000..2c69296
--- /dev/null
+++ b/src/components/results/common/marks-pie-chart.tsx
@@ -0,0 +1,47 @@
+"use client";
+import React from "react";
+import { Pie } from "react-chartjs-2";
+import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
+import type { QuestionResult } from "./types";
+
+ChartJS.register(ArcElement, Tooltip, Legend);
+
+interface MarksPieChartProps {
+ questions: QuestionResult[];
+}
+
+export const MarksPieChart: React.FC = ({ questions }) => {
+ const correctMarks = questions
+ .filter((q) => q.isCorrect === true)
+ .reduce((sum, q) => sum + (q.marks || 0), 0);
+ const wrongMarks = questions
+ .filter((q) => q.isCorrect === false)
+ .reduce((sum, q) => sum + (q.marks || 0), 0);
+ const unansweredMarks = questions
+ .filter((q) => q.isCorrect === undefined)
+ .reduce((sum, q) => sum + (q.marks || 0), 0);
+
+ return (
+
+ );
+};
diff --git a/src/components/results/common/question-bar-chart.tsx b/src/components/results/common/question-bar-chart.tsx
new file mode 100644
index 0000000..60ca442
--- /dev/null
+++ b/src/components/results/common/question-bar-chart.tsx
@@ -0,0 +1,66 @@
+"use client";
+import React from "react";
+import { Bar } from "react-chartjs-2";
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Tooltip,
+ Legend,
+} from "chart.js";
+import type { QuestionResult } from "./types";
+
+ChartJS.register(CategoryScale, LinearScale, BarElement, Tooltip, Legend);
+
+interface QuestionBarChartProps {
+ questions: QuestionResult[];
+}
+
+export const QuestionBarChart: React.FC = ({
+ questions,
+}) => {
+ const labels = questions.map((q, i) => `Q${i + 1}`);
+ const correct = questions.map((q) => (q.isCorrect === true ? 1 : 0));
+ const wrong = questions.map((q) => (q.isCorrect === false ? 1 : 0));
+
+ return (
+
+
+
+ );
+};
diff --git a/src/components/results/student/performance-distribution-chart.tsx b/src/components/results/student/performance-distribution-chart.tsx
new file mode 100644
index 0000000..2bdc313
--- /dev/null
+++ b/src/components/results/student/performance-distribution-chart.tsx
@@ -0,0 +1,153 @@
+"use client";
+
+import React, { useState } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Bar } from "react-chartjs-2";
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend,
+} from "chart.js";
+import type { RecentTestResult } from "../common/types";
+
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend,
+);
+
+const getBarColor = (lowerBound: number): string => {
+ if (lowerBound < 20) return "#ef4444";
+ if (lowerBound < 40) return "#f97316";
+ if (lowerBound < 60) return "#eab308";
+ if (lowerBound < 80) return "#10b981";
+ return "#16a34a";
+};
+
+interface StudentPerformanceDistributionChartProps {
+ tests: RecentTestResult[];
+}
+
+type GroupingSize = "20%" | "10%" | "5%";
+
+export function StudentPerformanceDistributionChart({
+ tests,
+}: StudentPerformanceDistributionChartProps) {
+ const [grouping, setGrouping] = useState("20%");
+
+ const handleGroupingChange = (value: string) => {
+ setGrouping(value as GroupingSize);
+ };
+
+ const generateDistribution = (
+ results: RecentTestResult[],
+ groupSize: number,
+ ) => {
+ if (!results || results.length === 0) return [];
+ const numberOfBins = 100 / groupSize;
+ const bins: { count: number; range: string }[] = [];
+ for (let i = 0; i < numberOfBins; i++) {
+ const lowerBound = i * groupSize;
+ const upperBound =
+ lowerBound + groupSize - (i === numberOfBins - 1 ? 0 : 1);
+ bins.push({ count: 0, range: `${lowerBound}-${upperBound}` });
+ }
+ results.forEach((result) => {
+ const scorePercentage = result.percentage;
+ if (scorePercentage === 100) {
+ bins[bins.length - 1].count++;
+ } else {
+ const binIndex = Math.floor(scorePercentage / groupSize);
+ if (binIndex >= 0 && binIndex < bins.length) {
+ bins[binIndex].count++;
+ }
+ }
+ });
+ const total = results.length;
+ return bins.map((bin) => {
+ const lowerBound = parseInt(bin.range.split("-")[0]);
+ return {
+ range: bin.range,
+ count: bin.count,
+ percentage: total > 0 ? Math.round((bin.count / total) * 100) : 0,
+ lowerBound,
+ color: getBarColor(lowerBound),
+ };
+ });
+ };
+
+ const groupSize = parseInt(grouping);
+ const distribution = generateDistribution(tests, groupSize);
+
+ return (
+
+
+
+
+
+ My Score Distribution
+
+
+
+ Group by score range:
+
+
+
+
+
+
+
+ `${item.range}%`),
+ datasets: [
+ {
+ label: "Number of Tests",
+ data: distribution.map((item) => item.count),
+ backgroundColor: distribution.map((item) => item.color),
+ borderColor: distribution.map((item) => item.color),
+ borderWidth: 1,
+ },
+ ],
+ }}
+ options={{
+ responsive: true,
+ plugins: {
+ legend: { display: false },
+ title: { display: false },
+ tooltip: { enabled: true },
+ },
+ scales: {
+ x: { grid: { color: "#e5e7eb22" } },
+ y: { beginAtZero: true, grid: { color: "#e5e7eb22" } },
+ },
+ }}
+ />
+
+
+
+ );
+}
diff --git a/src/components/results/student/performance-trends-chart.tsx b/src/components/results/student/performance-trends-chart.tsx
new file mode 100644
index 0000000..5b878c6
--- /dev/null
+++ b/src/components/results/student/performance-trends-chart.tsx
@@ -0,0 +1,221 @@
+"use client";
+
+import React, { useState } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Line } from "react-chartjs-2";
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Title,
+ Tooltip,
+ Legend,
+} from "chart.js";
+import type { CourseResult } from "./types";
+
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Title,
+ Tooltip,
+ Legend,
+);
+
+interface StudentPerformanceTrendsChartProps {
+ courses: CourseResult[];
+}
+
+export function StudentPerformanceTrendsChart({
+ courses,
+}: StudentPerformanceTrendsChartProps) {
+ // Track which courses are visible
+ const [visibleCourses, setVisibleCourses] = useState(
+ courses.map((c) => c.courseId),
+ );
+
+ // Find the max number of tests in any course
+ const maxQuizzes = Math.max(...courses.map((c) => c.testResults.length));
+ // X-axis: Quiz numbers (1, 2, 3, ...)
+ const labels = Array.from({ length: maxQuizzes }, (_, i) => `Quiz ${i + 1}`);
+
+ // Custom color palette for better distinction and accessibility
+ const palette = [
+ "#2563eb", // blue-600
+ "#059669", // emerald-600
+ "#f59e42", // orange-400
+ "#e11d48", // rose-600
+ "#a21caf", // purple-800
+ "#f472b6", // pink-400
+ "#0ea5e9", // sky-500
+ "#eab308", // yellow-500
+ "#7c3aed", // violet-600
+ "#14b8a6", // teal-500
+ ];
+
+ // Only show datasets for visible courses
+ const filteredCourses = courses.filter((c) =>
+ visibleCourses.includes(c.courseId),
+ );
+
+ const datasets = filteredCourses.map((course, idx) => {
+ // Sort tests by their order in the course
+ const sortedTests = [...course.testResults].sort((a, b) =>
+ a.completedAt.localeCompare(b.completedAt),
+ );
+ // Map to quiz number
+ const data = labels.map((_, i) =>
+ sortedTests[i] ? sortedTests[i].percentage : null,
+ );
+ return {
+ label: course.courseName,
+ data,
+ borderColor: palette[idx % palette.length],
+ backgroundColor: palette[idx % palette.length] + "33", // 20% opacity fill
+ pointBackgroundColor: palette[idx % palette.length],
+ pointBorderColor: "#fff",
+ pointHoverBackgroundColor: "#fff",
+ pointHoverBorderColor: palette[idx % palette.length],
+ borderWidth: 3,
+ pointRadius: 5,
+ pointHoverRadius: 8,
+ tension: 0.4,
+ fill: true,
+ spanGaps: true,
+ };
+ });
+
+ // Add SSR-safe dark mode detection helper
+ const isDarkMode = (): boolean => {
+ if (typeof window === "undefined" || typeof document === "undefined")
+ return false;
+ return document.documentElement.classList.contains("dark");
+ };
+
+ return (
+
+
+
+
+ Performance Trends
+
+
+
+ {courses.map((course, idx) => {
+ const isActive = visibleCourses.includes(course.courseId);
+ return (
+
+ );
+ })}
+
+
+
+
+ {
+ return isDarkMode() ? "#1e293b" : "#fff";
+ },
+ titleColor: () => {
+ return isDarkMode() ? "#60a5fa" : "#1e293b";
+ },
+ bodyColor: () => {
+ return isDarkMode() ? "#f1f5f9" : "#0f172a";
+ },
+ borderColor: () => {
+ return isDarkMode() ? "#334155" : "#2563eb";
+ },
+ borderWidth: 1,
+ titleFont: { weight: "bold" },
+ bodyFont: { weight: "medium" },
+ padding: 12,
+ callbacks: {
+ label: (ctx) => {
+ const quizNumber = ctx.dataIndex + 1;
+ const marks = ctx.parsed.y ?? "-";
+ return `Quiz ${quizNumber}: ${marks}`;
+ },
+ labelTextColor: (ctx) => {
+ // Use course color for tooltip text
+ const courseIdx = datasets.findIndex(
+ (ds) => ds.label === ctx.dataset.label,
+ );
+ return palette[courseIdx % palette.length];
+ },
+ },
+ },
+ },
+ scales: {
+ x: {
+ grid: { color: "#e5e7eb33" },
+ ticks: { color: "#64748b", font: { weight: "bold" } },
+ title: {
+ display: true,
+ text: "Quiz Number",
+ color: "#334155",
+ font: { weight: "bold" },
+ },
+ },
+ y: {
+ beginAtZero: true,
+ max: 100,
+ grid: { color: "#e5e7eb33" },
+ ticks: {
+ color: "#64748b",
+ font: { weight: "bold" },
+ stepSize: 25,
+ callback: (v) => v + "%",
+ },
+ title: {
+ display: true,
+ text: "Score (%)",
+ color: "#334155",
+ font: { weight: "bold" },
+ },
+ },
+ },
+ }}
+ />
+
+
+
+ );
+}
diff --git a/src/components/results/teacher/index.ts b/src/components/results/teacher/index.ts
index c8dd5f4..457860b 100644
--- a/src/components/results/teacher/index.ts
+++ b/src/components/results/teacher/index.ts
@@ -3,7 +3,6 @@ export * from "./performance-distribution-chart";
export * from "./courses-grid";
export * from "./course-tests-table";
export * from "./course-summary-card";
-export * from "./question-stats-table";
export * from "./recent-tests-card";
export * from "./student-results-table";
export * from "./types";
diff --git a/src/components/results/teacher/marks-frequency-bar-chart.tsx b/src/components/results/teacher/marks-frequency-bar-chart.tsx
new file mode 100644
index 0000000..60c56a4
--- /dev/null
+++ b/src/components/results/teacher/marks-frequency-bar-chart.tsx
@@ -0,0 +1,118 @@
+import React from "react";
+import { Line } from "react-chartjs-2";
+import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Tooltip,
+ Legend,
+} from "chart.js";
+
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Tooltip,
+ Legend,
+);
+
+export interface MarksFrequencyBarChartProps {
+ scores: number[];
+ binSize?: number; // e.g., 10 for 0-9, 10-19, ...
+}
+
+function getBins(scores: number[], binSize: number) {
+ const max = 100;
+ const bins = Array(Math.ceil(max / binSize)).fill(0);
+ scores.forEach((score) => {
+ const idx = Math.min(Math.floor(score / binSize), bins.length - 1);
+ bins[idx]++;
+ });
+ return bins;
+}
+
+export const MarksFrequencyBarChart: React.FC = ({
+ scores,
+ binSize = 10,
+}) => {
+ const bins = getBins(scores, binSize);
+ const labels = bins.map(
+ (_, i) => `${i * binSize}-${i * binSize + binSize - 1}`,
+ );
+ const data = {
+ labels,
+ datasets: [
+ {
+ label: "Number of Students",
+ data: bins,
+ borderColor: "#3b82f6",
+ backgroundColor: "rgba(59, 130, 246, 0.2)",
+ pointBackgroundColor: "#3b82f6",
+ pointBorderColor: "#fff",
+ pointRadius: 5,
+ pointHoverRadius: 7,
+ tension: 0.4,
+ fill: true,
+ },
+ ],
+ };
+ // Utility to safely get CSS variable (SSR-safe)
+ const getCSSVariable = (variable: string, fallback: string) => {
+ if (typeof window === "undefined") return fallback;
+ return (
+ getComputedStyle(document.documentElement).getPropertyValue(variable) ||
+ fallback
+ );
+ };
+
+ const options = {
+ plugins: {
+ legend: { display: false },
+ tooltip: { enabled: true },
+ title: {
+ display: false,
+ },
+ },
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ x: {
+ title: {
+ display: true,
+ text: "Percentage Range",
+ color: getCSSVariable("--foreground", "#fff"),
+ font: { size: 14 },
+ },
+ ticks: {
+ color: getCSSVariable("--foreground", "#fff"),
+ },
+ },
+ y: {
+ beginAtZero: true,
+ title: {
+ display: true,
+ text: "Number of Students",
+ color: getCSSVariable("--foreground", "#fff"),
+ font: { size: 14 },
+ },
+ ticks: {
+ color: getCSSVariable("--foreground", "#fff"),
+ },
+ },
+ },
+ };
+ return (
+
+
+ Marks Distribution (Marks vs Frequency)
+
+
+
+
+
+ );
+};
diff --git a/src/components/results/teacher/question-wise-bar-chart.tsx b/src/components/results/teacher/question-wise-bar-chart.tsx
new file mode 100644
index 0000000..117ed56
--- /dev/null
+++ b/src/components/results/teacher/question-wise-bar-chart.tsx
@@ -0,0 +1,113 @@
+import React, { useEffect, useState } from "react";
+import { Bar } from "react-chartjs-2";
+import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Tooltip,
+ Legend,
+} from "chart.js";
+
+ChartJS.register(CategoryScale, LinearScale, BarElement, Tooltip, Legend);
+
+// Use a distinct name to avoid conflict with types.ts
+export interface QuestionStatChart {
+ question: string;
+ correct: number;
+ wrong: number;
+}
+
+export interface QuestionWiseBarChartProps {
+ questionStats: QuestionStatChart[];
+ horizontalMode?: boolean;
+}
+
+// Custom hook for theme-aware colors (SSR safe)
+const useThemeColors = () => {
+ const [colors, setColors] = useState({ foreground: "#222" });
+
+ useEffect(() => {
+ const updateColors = () => {
+ const style = getComputedStyle(document.documentElement);
+ setColors({
+ foreground: style.getPropertyValue("--foreground") || "#222",
+ });
+ };
+ updateColors();
+ }, []);
+
+ return colors;
+};
+
+export const QuestionWiseBarChart: React.FC = ({
+ questionStats,
+ horizontalMode = false,
+}) => {
+ const themeColors = useThemeColors();
+ // Only show first 15 questions, rest scrollable in horizontal mode; for vertical, scroll if >30
+ const scrollThreshold = horizontalMode ? 15 : 30;
+ const isScrollable = questionStats.length > scrollThreshold;
+ const visibleStats =
+ isScrollable && horizontalMode ? questionStats.slice(0, 15) : questionStats;
+ const chart = (
+ `Q${i + 1}`),
+ datasets: [
+ {
+ label: "Correct",
+ data: visibleStats.map((q) => q.correct),
+ backgroundColor: "#22c55e",
+ borderRadius: 4,
+ },
+ {
+ label: "Wrong",
+ data: visibleStats.map((q) => q.wrong),
+ backgroundColor: "#ef4444",
+ borderRadius: 4,
+ },
+ ],
+ }}
+ options={{
+ plugins: {
+ legend: {
+ display: true,
+ position: "bottom",
+ labels: {
+ color: themeColors.foreground,
+ },
+ },
+ tooltip: { enabled: true },
+ },
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ x: {
+ stacked: false,
+ ticks: {
+ color: themeColors.foreground,
+ },
+ },
+ y: {
+ beginAtZero: true,
+ stacked: false,
+ ticks: {
+ color: themeColors.foreground,
+ },
+ },
+ },
+ }}
+ height={220}
+ />
+ );
+ return (
+
+
+ Question-wise Correct/Wrong
+
+ {chart}
+
+ );
+};
diff --git a/src/components/results/teacher/question-wise-histogram.tsx b/src/components/results/teacher/question-wise-histogram.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/results/teacher/student-performance-pie-chart.tsx b/src/components/results/teacher/student-performance-pie-chart.tsx
new file mode 100644
index 0000000..1621483
--- /dev/null
+++ b/src/components/results/teacher/student-performance-pie-chart.tsx
@@ -0,0 +1,81 @@
+import React from "react";
+import { Pie } from "react-chartjs-2";
+import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
+import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
+
+ChartJS.register(ArcElement, Tooltip, Legend);
+
+export interface StudentPerformancePieChartProps {
+ scores: number[];
+}
+
+// Distribution: Excellent: 90%+, Good: 75-89%, Average: 50-74%, Needs Improvement: <50%
+function getPerformanceDistribution(scores: number[]) {
+ let excellent = 0,
+ good = 0,
+ average = 0,
+ needsImprovement = 0;
+ scores.forEach((score) => {
+ if (score >= 90) excellent++;
+ else if (score >= 75) good++;
+ else if (score >= 50) average++;
+ else needsImprovement++;
+ });
+ return { excellent, good, average, needsImprovement };
+}
+
+export const StudentPerformancePieChart: React.FC<
+ StudentPerformancePieChartProps
+> = ({ scores }) => {
+ const { excellent, good, average, needsImprovement } =
+ getPerformanceDistribution(scores);
+ const data = {
+ labels: [
+ "Excellent (90%+)",
+ "Good (75-89%)",
+ "Average (50-74%)",
+ "Needs Improvement (<50%)",
+ ],
+ datasets: [
+ {
+ data: [excellent, good, average, needsImprovement],
+ backgroundColor: [
+ "#0ea5e9", // blue for Excellent
+ "#22c55e", // green for Good
+ "#fbbf24", // yellow for Average
+ "#ef4444", // red for Needs Improvement
+ ],
+ borderWidth: 1,
+ },
+ ],
+ };
+ const options = {
+ plugins: {
+ legend: {
+ display: true,
+ position: "bottom",
+ labels: {
+ color:
+ typeof window !== "undefined"
+ ? getComputedStyle(document.documentElement).getPropertyValue(
+ "--foreground",
+ ) || "#222"
+ : "#222",
+ },
+ },
+ tooltip: { enabled: true },
+ },
+ responsive: true,
+ maintainAspectRatio: false,
+ };
+ return (
+
+
+ Student Performance Distribution
+
+
+
+
+
+ );
+};
diff --git a/src/lib/results-api.ts b/src/lib/results-api.ts
index 8089040..d10703f 100644
--- a/src/lib/results-api.ts
+++ b/src/lib/results-api.ts
@@ -166,12 +166,12 @@ export const MockResultsAPI = {
courseId: "course-1",
courseName: "Data Structures and Algorithms",
courseCode: "CS301",
- totalTests: 5,
- completedTests: 4,
- averageScore: 82.5,
- highestScore: 95,
+ totalTests: 8,
+ completedTests: 8,
+ averageScore: 81.1,
+ highestScore: 98,
lastTestDate: new Date(
- Date.now() - 2 * 24 * 60 * 60 * 1000,
+ Date.now() - 1 * 24 * 60 * 60 * 1000,
).toISOString(),
testResults: [
{
@@ -182,7 +182,7 @@ export const MockResultsAPI = {
percentage: 78,
timeTaken: 55,
completedAt: new Date(
- Date.now() - 60 * 24 * 60 * 60 * 1000,
+ Date.now() - 80 * 24 * 60 * 60 * 1000,
).toISOString(),
difficulty: "medium",
questionCount: 15,
@@ -197,7 +197,7 @@ export const MockResultsAPI = {
percentage: 85,
timeTaken: 62,
completedAt: new Date(
- Date.now() - 45 * 24 * 60 * 60 * 1000,
+ Date.now() - 70 * 24 * 60 * 60 * 1000,
).toISOString(),
difficulty: "medium",
questionCount: 20,
@@ -212,7 +212,7 @@ export const MockResultsAPI = {
percentage: 72,
timeTaken: 75,
completedAt: new Date(
- Date.now() - 30 * 24 * 60 * 60 * 1000,
+ Date.now() - 60 * 24 * 60 * 60 * 1000,
).toISOString(),
difficulty: "hard",
questionCount: 18,
@@ -227,25 +227,85 @@ export const MockResultsAPI = {
percentage: 95,
timeTaken: 48,
completedAt: new Date(
- Date.now() - 2 * 24 * 60 * 60 * 1000,
+ Date.now() - 50 * 24 * 60 * 60 * 1000,
).toISOString(),
difficulty: "hard",
questionCount: 20,
correctAnswers: 19,
status: "completed",
},
+ {
+ testId: "test-1-5",
+ testName: "Hash Tables",
+ score: 88,
+ maxScore: 100,
+ percentage: 88,
+ timeTaken: 53,
+ completedAt: new Date(
+ Date.now() - 40 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "medium",
+ questionCount: 16,
+ correctAnswers: 14,
+ status: "completed",
+ },
+ {
+ testId: "test-1-6",
+ testName: "Greedy Algorithms",
+ score: 81,
+ maxScore: 100,
+ percentage: 81,
+ timeTaken: 60,
+ completedAt: new Date(
+ Date.now() - 30 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "medium",
+ questionCount: 18,
+ correctAnswers: 15,
+ status: "completed",
+ },
+ {
+ testId: "test-1-7",
+ testName: "Dynamic Programming",
+ score: 98,
+ maxScore: 100,
+ percentage: 98,
+ timeTaken: 70,
+ completedAt: new Date(
+ Date.now() - 15 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "hard",
+ questionCount: 22,
+ correctAnswers: 21,
+ status: "completed",
+ },
+ {
+ testId: "test-1-8",
+ testName: "Graph Algorithms",
+ score: 67,
+ maxScore: 100,
+ percentage: 67,
+ timeTaken: 80,
+ completedAt: new Date(
+ Date.now() - 1 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "hard",
+ questionCount: 20,
+ correctAnswers: 12,
+ status: "completed",
+ },
],
},
{
courseId: "course-2",
courseName: "Database Management Systems",
courseCode: "CS302",
- totalTests: 3,
- completedTests: 3,
- averageScore: 88.3,
- highestScore: 92,
+ totalTests: 6,
+ completedTests: 6,
+ averageScore: 78.3,
+ highestScore: 97,
lastTestDate: new Date(
- Date.now() - 4 * 24 * 60 * 60 * 1000,
+ Date.now() - 1 * 24 * 60 * 60 * 1000,
).toISOString(),
testResults: [
{
@@ -256,7 +316,7 @@ export const MockResultsAPI = {
percentage: 90,
timeTaken: 40,
completedAt: new Date(
- Date.now() - 40 * 24 * 60 * 60 * 1000,
+ Date.now() - 60 * 24 * 60 * 60 * 1000,
).toISOString(),
difficulty: "easy",
questionCount: 30,
@@ -266,31 +326,76 @@ export const MockResultsAPI = {
{
testId: "test-2-2",
testName: "Database Design",
- score: 83,
+ score: 63,
maxScore: 100,
- percentage: 83,
+ percentage: 63,
timeTaken: 65,
completedAt: new Date(
- Date.now() - 15 * 24 * 60 * 60 * 1000,
+ Date.now() - 50 * 24 * 60 * 60 * 1000,
).toISOString(),
difficulty: "medium",
questionCount: 18,
- correctAnswers: 15,
+ correctAnswers: 11,
status: "completed",
},
{
testId: "test-2-3",
testName: "Transactions and Concurrency",
- score: 92,
+ score: 47,
maxScore: 100,
- percentage: 92,
+ percentage: 47,
timeTaken: 55,
completedAt: new Date(
- Date.now() - 4 * 24 * 60 * 60 * 1000,
+ Date.now() - 40 * 24 * 60 * 60 * 1000,
).toISOString(),
difficulty: "hard",
questionCount: 25,
- correctAnswers: 23,
+ correctAnswers: 10,
+ status: "completed",
+ },
+ {
+ testId: "test-2-4",
+ testName: "Indexing and Optimization",
+ score: 97,
+ maxScore: 100,
+ percentage: 97,
+ timeTaken: 50,
+ completedAt: new Date(
+ Date.now() - 30 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "medium",
+ questionCount: 20,
+ correctAnswers: 19,
+ status: "completed",
+ },
+ {
+ testId: "test-2-5",
+ testName: "NoSQL Databases",
+ score: 71,
+ maxScore: 100,
+ percentage: 71,
+ timeTaken: 60,
+ completedAt: new Date(
+ Date.now() - 15 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "medium",
+ questionCount: 18,
+ correctAnswers: 13,
+ status: "completed",
+ },
+ {
+ testId: "test-2-6",
+ testName: "Database Security",
+ score: 54,
+ maxScore: 100,
+ percentage: 54,
+ timeTaken: 55,
+ completedAt: new Date(
+ Date.now() - 1 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "hard",
+ questionCount: 22,
+ correctAnswers: 12,
status: "completed",
},
],
@@ -299,12 +404,12 @@ export const MockResultsAPI = {
courseId: "course-3",
courseName: "Object Oriented Programming",
courseCode: "CS201",
- totalTests: 4,
- completedTests: 3,
- averageScore: 75.7,
- highestScore: 85,
+ totalTests: 6,
+ completedTests: 6,
+ averageScore: 65.7,
+ highestScore: 89,
lastTestDate: new Date(
- Date.now() - 7 * 24 * 60 * 60 * 1000,
+ Date.now() - 1 * 24 * 60 * 60 * 1000,
).toISOString(),
testResults: [
{
@@ -315,7 +420,7 @@ export const MockResultsAPI = {
percentage: 85,
timeTaken: 52,
completedAt: new Date(
- Date.now() - 35 * 24 * 60 * 60 * 1000,
+ Date.now() - 60 * 24 * 60 * 60 * 1000,
).toISOString(),
difficulty: "easy",
questionCount: 20,
@@ -325,16 +430,16 @@ export const MockResultsAPI = {
{
testId: "test-3-2",
testName: "Inheritance and Polymorphism",
- score: 72,
+ score: 59,
maxScore: 100,
- percentage: 72,
+ percentage: 59,
timeTaken: 60,
completedAt: new Date(
- Date.now() - 20 * 24 * 60 * 60 * 1000,
+ Date.now() - 50 * 24 * 60 * 60 * 1000,
).toISOString(),
difficulty: "medium",
questionCount: 25,
- correctAnswers: 18,
+ correctAnswers: 13,
status: "completed",
},
{
@@ -345,13 +450,58 @@ export const MockResultsAPI = {
percentage: 70,
timeTaken: 58,
completedAt: new Date(
- Date.now() - 7 * 24 * 60 * 60 * 1000,
+ Date.now() - 40 * 24 * 60 * 60 * 1000,
).toISOString(),
difficulty: "medium",
questionCount: 20,
correctAnswers: 14,
status: "completed",
},
+ {
+ testId: "test-3-4",
+ testName: "Interfaces and Abstract Classes",
+ score: 41,
+ maxScore: 100,
+ percentage: 41,
+ timeTaken: 55,
+ completedAt: new Date(
+ Date.now() - 30 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "medium",
+ questionCount: 18,
+ correctAnswers: 7,
+ status: "completed",
+ },
+ {
+ testId: "test-3-5",
+ testName: "Collections Framework",
+ score: 77,
+ maxScore: 100,
+ percentage: 77,
+ timeTaken: 60,
+ completedAt: new Date(
+ Date.now() - 15 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "medium",
+ questionCount: 20,
+ correctAnswers: 15,
+ status: "completed",
+ },
+ {
+ testId: "test-3-6",
+ testName: "Generics and Streams",
+ score: 36,
+ maxScore: 100,
+ percentage: 36,
+ timeTaken: 58,
+ completedAt: new Date(
+ Date.now() - 1 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "hard",
+ questionCount: 22,
+ correctAnswers: 6,
+ status: "completed",
+ },
],
},
{
@@ -546,6 +696,199 @@ export const MockResultsAPI = {
},
],
},
+ {
+ courseId: "course-7",
+ courseName: "Computer Networks",
+ courseCode: "CS410",
+ totalTests: 5,
+ completedTests: 5,
+ averageScore: 84.2,
+ highestScore: 92,
+ lastTestDate: new Date(
+ Date.now() - 2 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ testResults: [
+ {
+ testId: "test-7-1",
+ testName: "Network Basics",
+ score: 80,
+ maxScore: 100,
+ percentage: 80,
+ timeTaken: 40,
+ completedAt: new Date(
+ Date.now() - 50 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "easy",
+ questionCount: 15,
+ correctAnswers: 13,
+ status: "completed",
+ },
+ {
+ testId: "test-7-2",
+ testName: "OSI Model",
+ score: 85,
+ maxScore: 100,
+ percentage: 85,
+ timeTaken: 50,
+ completedAt: new Date(
+ Date.now() - 40 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "medium",
+ questionCount: 18,
+ correctAnswers: 15,
+ status: "completed",
+ },
+ {
+ testId: "test-7-3",
+ testName: "TCP/IP Protocols",
+ score: 92,
+ maxScore: 100,
+ percentage: 92,
+ timeTaken: 60,
+ completedAt: new Date(
+ Date.now() - 30 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "hard",
+ questionCount: 20,
+ correctAnswers: 19,
+ status: "completed",
+ },
+ {
+ testId: "test-7-4",
+ testName: "Routing Algorithms",
+ score: 78,
+ maxScore: 100,
+ percentage: 78,
+ timeTaken: 55,
+ completedAt: new Date(
+ Date.now() - 15 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "medium",
+ questionCount: 17,
+ correctAnswers: 13,
+ status: "completed",
+ },
+ {
+ testId: "test-7-5",
+ testName: "Network Security",
+ score: 86,
+ maxScore: 100,
+ percentage: 86,
+ timeTaken: 65,
+ completedAt: new Date(
+ Date.now() - 2 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "hard",
+ questionCount: 22,
+ correctAnswers: 18,
+ status: "completed",
+ },
+ ],
+ },
+ {
+ courseId: "course-8",
+ courseName: "Software Engineering",
+ courseCode: "CS420",
+ totalTests: 6,
+ completedTests: 6,
+ averageScore: 77.5,
+ highestScore: 90,
+ lastTestDate: new Date(
+ Date.now() - 1 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ testResults: [
+ {
+ testId: "test-8-1",
+ testName: "Software Process Models",
+ score: 70,
+ maxScore: 100,
+ percentage: 70,
+ timeTaken: 45,
+ completedAt: new Date(
+ Date.now() - 60 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "easy",
+ questionCount: 12,
+ correctAnswers: 10,
+ status: "completed",
+ },
+ {
+ testId: "test-8-2",
+ testName: "Requirements Engineering",
+ score: 75,
+ maxScore: 100,
+ percentage: 75,
+ timeTaken: 50,
+ completedAt: new Date(
+ Date.now() - 50 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "medium",
+ questionCount: 15,
+ correctAnswers: 12,
+ status: "completed",
+ },
+ {
+ testId: "test-8-3",
+ testName: "Design Patterns",
+ score: 90,
+ maxScore: 100,
+ percentage: 90,
+ timeTaken: 60,
+ completedAt: new Date(
+ Date.now() - 40 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "hard",
+ questionCount: 18,
+ correctAnswers: 17,
+ status: "completed",
+ },
+ {
+ testId: "test-8-4",
+ testName: "Testing & QA",
+ score: 80,
+ maxScore: 100,
+ percentage: 80,
+ timeTaken: 55,
+ completedAt: new Date(
+ Date.now() - 30 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "medium",
+ questionCount: 16,
+ correctAnswers: 13,
+ status: "completed",
+ },
+ {
+ testId: "test-8-5",
+ testName: "Agile Methodologies",
+ score: 76,
+ maxScore: 100,
+ percentage: 76,
+ timeTaken: 50,
+ completedAt: new Date(
+ Date.now() - 15 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "medium",
+ questionCount: 14,
+ correctAnswers: 11,
+ status: "completed",
+ },
+ {
+ testId: "test-8-6",
+ testName: "Project Management",
+ score: 65,
+ maxScore: 100,
+ percentage: 65,
+ timeTaken: 60,
+ completedAt: new Date(
+ Date.now() - 1 * 24 * 60 * 60 * 1000,
+ ).toISOString(),
+ difficulty: "hard",
+ questionCount: 20,
+ correctAnswers: 13,
+ status: "completed",
+ },
+ ],
+ },
];
},
getTestSummaries: async (
@@ -1095,9 +1438,9 @@ export const MockTeacherResultsAPI = {
{ range: "81-100", count: 20, percentage: 31.2 },
];
- // Generate mock student results
+ // Generate mock student results (now 30 students)
const studentResults: StudentTestResult[] = Array.from(
- { length: 20 },
+ { length: 30 },
(_, i) => {
const score = Math.floor(Math.random() * 60) + 40; // Random score between 40 and 100
return {
@@ -1116,8 +1459,8 @@ export const MockTeacherResultsAPI = {
},
);
- // Generate mock question stats
- const questionStats: QuestionStat[] = Array.from({ length: 10 }, (_, i) => {
+ // Generate mock question stats (now 20 questions)
+ const questionStats: QuestionStat[] = Array.from({ length: 20 }, (_, i) => {
const correctPct = Math.floor(Math.random() * 60) + 40; // Random percentage between 40 and 100
return {
questionId: `q-${i + 1}`,
@@ -1149,7 +1492,7 @@ export const MockTeacherResultsAPI = {
courseId: "course-1",
courseName: "Data Structures and Algorithms",
courseCode: "CSE-301",
- totalQuestions: 10,
+ totalQuestions: 20,
totalMarks: 100,
averageScore: 77.9,
highestScore: 97,
@@ -1158,6 +1501,7 @@ export const MockTeacherResultsAPI = {
standardDeviation: 12.5,
averageCompletionTime: 72, // in minutes
totalSubmissions: 64,
+ totalStudents: 70, // <-- Added totalStudents to mock data
performanceDistribution,
studentResults,
questionStats,