Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 261 additions & 54 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@tiptap/starter-kit": "^2.12.0",
"@types/jsonwebtoken": "^9.0.9",
"axios": "^1.9.0",
"chart.js": "^4.4.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
Expand All @@ -63,11 +64,13 @@
"next-auth": "^5.0.0-beta.28",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-chartjs-2": "^5.3.0",
"react-day-picker": "^9.7.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.57.0",
"react-katex": "^3.1.0",
"react-resizable-panels": "^3.0.2",
"recharts": "^2.15.3",
"sonner": "^2.0.3",
"tailwind-merge": "^3.2.0",
"uuid": "^11.1.0",
Expand Down
9 changes: 0 additions & 9 deletions src/app/(evalify)/(academics)/result/page.tsx

This file was deleted.

10 changes: 10 additions & 0 deletions src/app/(test)/results-data/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use client";

// This file is a redirect. The student results functionality has been moved to:
// src/app/(test)/student-results/page.tsx

import { redirect } from "next/navigation";

export default function ResultsDataPage() {
redirect("/student-results");
}
10 changes: 10 additions & 0 deletions src/app/(test)/results/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use client";

// This file is a redirect. The student results functionality has been moved to:
// src/app/(test)/student-results/page.tsx

import { redirect } from "next/navigation";

export default function ResultsPage() {
redirect("/student-results");
}
208 changes: 208 additions & 0 deletions src/app/(test)/student-results/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
"use client";

import React, { useState, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import TopBar from "@/components/question_creation/top-bar";
import {
StudentRecentTestsCard,
CourseResultsGrid,
TestSummaries,
StudentOverallResult,
CourseResult,
TestSummary,
} from "@/components/results";
import { MockResultsAPI } from "@/lib/results-api";

export default function StudentResultsPage() {
const router = useRouter();
const searchParams = useSearchParams();
const [currentView, setCurrentView] = useState<
"overview" | "course" | "tests"
>("overview");
Comment on lines +19 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove unused “tests” view or render it.

currentView union includes "tests" and the URL handler sets it, but the JSX never renders that branch. Dead code adds complexity and can confuse routing logic. Drop the "tests" path or implement its UI.

Also applies to: 84-88

🤖 Prompt for AI Agents
In src/app/(test)/student-results/page.tsx around lines 19-21 and 84-88, the
"tests" option in the currentView state union type is unused in the JSX
rendering, causing dead code and potential routing confusion. Either remove
"tests" from the currentView type and any related URL handling code or implement
the corresponding UI rendering branch for "tests" in the JSX to ensure it is
properly handled and displayed.

const [selectedCourseId, setSelectedCourseId] = useState<string | null>(null);
const [studentData, setStudentData] = useState<StudentOverallResult | null>(
null,
);
const [courseResults, setCourseResults] = useState<CourseResult[]>([]);
const [testSummaries, setTestSummaries] = useState<TestSummary[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

// Define functions before useEffect hooks
const loadInitialData = React.useCallback(async () => {
// Use a mock student ID since we're not requiring login
const mockStudentId = "student-1";

setLoading(true);
setError(null);

try {
// Load both student overview and course results in parallel
const [overview, courses] = await Promise.all([
MockResultsAPI.getStudentOverview(mockStudentId),
MockResultsAPI.getCourseResults(mockStudentId),
]);

setStudentData(overview);
setCourseResults(courses);
} catch (err) {
console.error("Failed to load results data:", err);
setError("Failed to load results. Please try again.");
} finally {
setLoading(false);
}
}, []);

const loadTestSummaries = React.useCallback(async (courseId: string) => {
// Use a mock student ID since we're not requiring login
const mockStudentId = "student-1";

setLoading(true);
try {
const tests = await MockResultsAPI.getTestSummaries(
mockStudentId,
courseId,
);
setTestSummaries(tests);
} catch (err) {
console.error("Failed to load test summaries:", err);
setError("Failed to load test summaries. Please try again.");
} finally {
setLoading(false);
}
}, []);

// Handle URL parameters for navigation
useEffect(() => {
const view = searchParams.get("view");
const courseId = searchParams.get("courseId");

if (view === "course" && courseId) {
setCurrentView("course");
setSelectedCourseId(courseId);
loadTestSummaries(courseId);
} else if (view === "tests" && courseId) {
setCurrentView("tests");
setSelectedCourseId(courseId);
loadTestSummaries(courseId);
} else {
setCurrentView("overview");
setSelectedCourseId(null);
}
}, [searchParams, loadTestSummaries]);

// Load initial data
useEffect(() => {
// Load data immediately without waiting for session
loadInitialData();
}, [loadInitialData]);
const handleViewCourse = (courseId: string) => {
router.push(`/student-results?view=course&courseId=${courseId}`);
};

const handleViewTest = (testId: string) => {
router.push(`/student-results/test/${testId}`);
};
const handleBack = () => {
if (currentView === "course") {
router.push("/student-results");
}
};

const selectedCourse = courseResults.find(
(course) => course.courseId === selectedCourseId,
);

if (loading) {
return (
<div className="container mx-auto px-4 py-8">
<div className="flex items-center justify-center min-h-96">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
<p className="text-muted-foreground">Loading results...</p>
</div>
</div>
</div>
);
}

if (error) {
return (
<div className="container mx-auto px-4 py-8">
<div className="flex items-center justify-center min-h-96">
<div className="text-center">
<h2 className="text-xl font-semibold mb-2">
Error Loading Results
</h2>
<p className="text-muted-foreground mb-4">{error}</p>
<button
onClick={loadInitialData}
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
>
Try Again
</button>
</div>
</div>
</div>
);
}

if (!studentData) {
return (
<div className="container mx-auto px-4 py-8">
<div className="flex items-center justify-center min-h-96">
<div className="text-center">
<p className="text-muted-foreground">No results data available</p>
</div>
</div>
</div>
);
}
return (
<div className="flex flex-col min-h-screen">
<TopBar />
<div className="container mx-auto px-4 py-6 space-y-6">
{currentView === "overview" && (
<>
{/* Student Overview */}
<div>
<h1 className="text-3xl font-bold mb-2">My Results</h1>
<p className="text-muted-foreground mb-4">
Track your academic performance and progress
</p>
{/* Overview cards removed as requested */}
</div>{" "}
{/* Recent Tests */}
<div>
<h2 className="text-xl font-bold mb-4">Recent Tests</h2>
<StudentRecentTestsCard
studentId={studentData.studentId || "current-student"}
tests={studentData.recentTests}
onViewTest={handleViewTest}
showHeader={false}
/>
</div>
{/* Course Results */}
<div>
<h2 className="text-xl font-bold mb-4">Course Results</h2>
<CourseResultsGrid
courses={courseResults}
onViewCourse={handleViewCourse}
onViewTest={handleViewTest}
/>
</div>
</>
)}
{currentView === "course" && selectedCourse && (
<TestSummaries
courseName={selectedCourse.courseName}
courseCode={selectedCourse.courseCode}
tests={testSummaries}
onBack={handleBack}
onViewTest={handleViewTest}
/>
)}
</div>
</div>
);
}
98 changes: 98 additions & 0 deletions src/app/(test)/student-results/test/[testId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"use client";

import React, { useState, useEffect } from "react";
import { useRouter, useParams } from "next/navigation";
import TopBar from "@/components/question_creation/top-bar";
import {
DetailedTestResultView,
DetailedTestResult,
} from "@/components/results";
import { MockResultsAPI } from "@/lib/results-api";

export default function TestResultPage() {
const router = useRouter();
const params = useParams();
const [result, setResult] = useState<DetailedTestResult | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
const loadTestResult = async () => {
// Use a mock student ID since we're not requiring login
const mockStudentId = "student-1";

try {
setLoading(true);
setError(null);
const testId = params.testId as string;

const data = await MockResultsAPI.getTestResult(mockStudentId, testId);
setResult(data);
} catch (err) {
console.error("Failed to load test result:", err);
setError("Failed to load test result. Please try again.");
} finally {
setLoading(false);
}
};

if (params.testId) {
loadTestResult();
}
}, [params.testId]);

const handleBack = () => {
router.back();
};

if (loading) {
return (
<div className="flex flex-col min-h-screen">
<TopBar />
<div className="container mx-auto px-4 py-8">
<div className="flex items-center justify-center min-h-96">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
<p className="text-muted-foreground">Loading test result...</p>
</div>
</div>
</div>
</div>
);
}

if (error || !result) {
return (
<div className="flex flex-col min-h-screen">
<TopBar />
<div className="container mx-auto px-4 py-8">
<div className="flex items-center justify-center min-h-96">
<div className="text-center">
<h2 className="text-xl font-semibold mb-2">
Error Loading Test Result
</h2>
<p className="text-muted-foreground mb-4">
{error || "Test result not found"}
</p>
<button
onClick={() => router.back()}
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
>
Go Back
</button>
</div>
</div>
</div>
</div>
);
}

return (
<div className="flex flex-col min-h-screen">
<TopBar />
<div className="container mx-auto px-4 py-8">
<DetailedTestResultView result={result} onBack={handleBack} />
</div>
</div>
);
}
Loading