-
Notifications
You must be signed in to change notification settings - Fork 6
Results page for teacher and students #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Aksaykanthan
merged 13 commits into
evalify:development
from
GowthamDhanaraju:development
Jun 16, 2025
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
c541efd
fix: modify EditQuestionPage component
GowthamDhanaraju 7e9cc9f
feat: enhance question settings and type selector with icons and impr…
GowthamDhanaraju e52cb0f
feat: improve error handling and loading state in TestSelectPage comp…
GowthamDhanaraju df58365
feat: enhance question creation UI with icons and improve interaction
GowthamDhanaraju 9e9924d
fix: modify EditQuestionPage component
GowthamDhanaraju d8f7809
feat: enhance question settings and type selector with icons and impr…
GowthamDhanaraju dee48d5
chore: update package-lock.json after resolving rebase conflicts
GowthamDhanaraju 70eab76
Add results types and API utilities for student and teacher results m…
GowthamDhanaraju 90ed65d
Remove workflows directory, keeping only copilot-instructions.md
GowthamDhanaraju a350a2c
Merge remote-tracking branch 'upstream/development' into development
GowthamDhanaraju 3873826
feat: add RecentTestsCard component to display recent tests for teachers
GowthamDhanaraju f953196
Merge upstream changes and fix duplicate props in question-editor
GowthamDhanaraju bb6a463
Merge branch 'development' into development
Aksaykanthan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"); | ||
| 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> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> | ||
| ); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.
currentViewunion 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