From e61d2a6e949bf7b0bec7d624d5d149eaa8e7dff2 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:20:48 +0530 Subject: [PATCH 01/22] fix: await grade upsert before returning JSON response Made-with: Cursor --- app/api/grades/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index b9da63d..d37502d 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -73,7 +73,7 @@ export async function POST(req: NextRequest) { const max = data.maxMarks! const term = data.term ?? 'Term 1' - const grade = Grade.findOneAndUpdate( + const grade = await Grade.findOneAndUpdate( { teacherId: userId, studentId: data.studentId, subject: data.subject, term }, { $set: { ...data, term, teacherId: userId, grade: calcGrade(data.marks, max) } }, { upsert: true, new: true } From c424525d93326153967619d766a6d5de09eb9a26 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:21:04 +0530 Subject: [PATCH 02/22] fix: default max marks when omitted on grade POST Made-with: Cursor --- app/api/grades/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index d37502d..b4ce7e5 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -70,7 +70,7 @@ export async function POST(req: NextRequest) { if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }) const data = parsed.data - const max = data.maxMarks! + const max = data.maxMarks ?? 100 const term = data.term ?? 'Term 1' const grade = await Grade.findOneAndUpdate( From 66c3847469f63191a64ac738e5c28ebdfa59f45f Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:21:23 +0530 Subject: [PATCH 03/22] fix: stop returning error stacks from grades API Made-with: Cursor --- app/api/grades/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index b4ce7e5..2aa2142 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -48,7 +48,7 @@ export async function GET(req: NextRequest) { return NextResponse.json(grades) } catch (error) { console.error('GET /api/grades error:', error instanceof Error ? error.message : error) - return NextResponse.json({ error: error instanceof Error ? error.stack : 'Internal server error' }, { status: 500 }) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } @@ -83,6 +83,6 @@ export async function POST(req: NextRequest) { if (error instanceof Error) { console.error('POST /api/grades error:', error.message) } - return NextResponse.json({ error: error instanceof Error ? error.stack : 'Internal server error' }, { status: 500 }) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } From 2499be171b929de31a0015cda6ca359d9bec7510 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:21:32 +0530 Subject: [PATCH 04/22] fix: stop returning error stack from assignments GET Made-with: Cursor --- app/api/assignments/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/assignments/route.ts b/app/api/assignments/route.ts index 19021d8..1d88b7e 100644 --- a/app/api/assignments/route.ts +++ b/app/api/assignments/route.ts @@ -52,7 +52,7 @@ export async function GET(req: NextRequest) { if (error instanceof Error) { console.error('GET /api/assignments error:', error.message) } - return NextResponse.json({ error: error instanceof Error ? error.stack : 'Internal server error' }, { status: 500 }) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } From 4359359be852c51518ea72774fa93b4f05d79086 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:24:00 +0530 Subject: [PATCH 05/22] fix: drop non-schema grade field from student update whitelist Made-with: Cursor --- app/api/students/[id]/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/students/[id]/route.ts b/app/api/students/[id]/route.ts index 2eaaf93..cbadb2f 100644 --- a/app/api/students/[id]/route.ts +++ b/app/api/students/[id]/route.ts @@ -4,7 +4,7 @@ import mongoose from 'mongoose' import { connectDB } from '@/lib/mongodb' import { Student } from '@/models/Student' -const ALLOWED_UPDATE_FIELDS = ['name', 'email', 'grade', 'rollNo', 'class', 'phone', 'address', 'parentName', 'parentPhone'] +const ALLOWED_UPDATE_FIELDS = ['name', 'email', 'rollNo', 'class', 'phone', 'address', 'parentName', 'parentPhone'] export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { const { userId } = await auth() From 147e951c599cce71f9199c70285768a92a4706bb Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:24:08 +0530 Subject: [PATCH 06/22] fix: scope student PUT and DELETE by authenticated teacher Made-with: Cursor --- app/api/students/[id]/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/students/[id]/route.ts b/app/api/students/[id]/route.ts index cbadb2f..85d4294 100644 --- a/app/api/students/[id]/route.ts +++ b/app/api/students/[id]/route.ts @@ -35,7 +35,7 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string await connectDB() const student = await Student.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, sanitizedBody, { new: true } ) @@ -65,7 +65,7 @@ export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: str } await connectDB() - const deleted = await Student.findOneAndDelete({ _id: id }) + const deleted = await Student.findOneAndDelete({ _id: id, teacherId: userId }) if (!deleted) { return NextResponse.json({ error: 'Student not found' }, { status: 404 }) From 82038229fe9e5cb11e797b1d7607099dc38c99db Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:24:21 +0530 Subject: [PATCH 07/22] fix: enforce Zod validation on student POST body Made-with: Cursor --- app/api/students/route.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/api/students/route.ts b/app/api/students/route.ts index 8f3dcc2..bed5f30 100644 --- a/app/api/students/route.ts +++ b/app/api/students/route.ts @@ -92,9 +92,12 @@ export async function POST(req: NextRequest) { return NextResponse.json({ error: 'Malformed JSON' }, { status: 400 }) } - StudentSchema.safeParse(body) + const parsed = StudentSchema.safeParse(body) + if (!parsed.success) { + return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }) + } - const student = await Student.create({ ...(body as Record), teacherId: userId }) + const student = await Student.create({ ...parsed.data, teacherId: userId }) return NextResponse.json(student, { status: 201 }) } catch (error) { if (error instanceof Error) { From 81a0c65f115f9fb20b6f00683e333a3c74f80ffc Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:24:24 +0530 Subject: [PATCH 08/22] fix: scope assignment PUT and DELETE by authenticated teacher Made-with: Cursor --- app/api/assignments/[id]/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/assignments/[id]/route.ts b/app/api/assignments/[id]/route.ts index 21fca1c..b041302 100644 --- a/app/api/assignments/[id]/route.ts +++ b/app/api/assignments/[id]/route.ts @@ -36,7 +36,7 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string } const assignment = await Assignment.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, sanitizedBody, { new: true } ) @@ -63,7 +63,7 @@ export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: str } await connectDB() - const deleted = await Assignment.findOneAndDelete({ _id: id }) + const deleted = await Assignment.findOneAndDelete({ _id: id, teacherId: userId }) if (!deleted) { return NextResponse.json({ error: 'Not found' }, { status: 404 }) From 79c29977dab0499fe27efd8b34484bb024afd7e5 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:24:40 +0530 Subject: [PATCH 09/22] fix: scope announcement PUT and DELETE by teacher Made-with: Cursor --- app/api/announcements/[id]/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/announcements/[id]/route.ts b/app/api/announcements/[id]/route.ts index 6c32f47..22d4d0a 100644 --- a/app/api/announcements/[id]/route.ts +++ b/app/api/announcements/[id]/route.ts @@ -36,7 +36,7 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string } const announcement = await Announcement.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, { $set: sanitizedBody }, { new: true, runValidators: true, context: 'query' } ) @@ -63,7 +63,7 @@ export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: str } await connectDB() - const deleted = await Announcement.findOneAndDelete({ _id: id }) + const deleted = await Announcement.findOneAndDelete({ _id: id, teacherId: userId }) if (!deleted) { return NextResponse.json({ error: 'Not found' }, { status: 404 }) From 2c1ac340aa852225f23234504c4e0f443c7e9e33 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:24:56 +0530 Subject: [PATCH 10/22] fix: map announcement body to content and trim allow list Made-with: Cursor --- app/api/announcements/[id]/route.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/api/announcements/[id]/route.ts b/app/api/announcements/[id]/route.ts index 22d4d0a..96e89c5 100644 --- a/app/api/announcements/[id]/route.ts +++ b/app/api/announcements/[id]/route.ts @@ -4,7 +4,7 @@ import mongoose from 'mongoose' import { connectDB } from '@/lib/mongodb' import { Announcement } from '@/models/Announcement' -const ALLOWED_FIELDS = ['title', 'content', 'body', 'audience', 'category', 'pinned', 'expiresAt'] +const ALLOWED_FIELDS = ['title', 'content', 'audience', 'category', 'pinned'] export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { const { userId } = await auth() @@ -27,13 +27,19 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string return NextResponse.json({ error: 'Invalid JSON request body' }, { status: 400 }) } - // Sanitize: only allow whitelisted fields + // Sanitize: only allow whitelisted fields (map legacy `body` → `content`) const sanitizedBody: Record = {} for (const key of ALLOWED_FIELDS) { if (key in body) { sanitizedBody[key] = body[key] } } + if ( + typeof body.body === 'string' && + sanitizedBody.content === undefined + ) { + sanitizedBody.content = body.body + } const announcement = await Announcement.findOneAndUpdate( { _id: id, teacherId: userId }, From 0e7a273a4bbf8d0baa80907372ce97da3a1c5b04 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:25:04 +0530 Subject: [PATCH 11/22] fix: load profile only for signed-in user session Made-with: Cursor --- app/api/profile/route.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index a3d98bf..0908513 100644 --- a/app/api/profile/route.ts +++ b/app/api/profile/route.ts @@ -3,15 +3,8 @@ import { NextRequest, NextResponse } from 'next/server' import { connectDB } from '@/lib/mongodb' import { Teacher } from '@/models/Teacher' -export async function GET(req: NextRequest) { - const { searchParams } = new URL(req.url) - const queryUserId = searchParams.get('userId') - - let userId: string | null = queryUserId - if (!userId) { - const session = await auth() - userId = session.userId - } +export async function GET(_req: NextRequest) { + const { userId } = await auth() if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) try { From 1803a9f6c86b19453fb06b8c17a699761fc0a1cb Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:25:17 +0530 Subject: [PATCH 12/22] fix: scope grade PUT and DELETE by authenticated teacher Made-with: Cursor --- app/api/grades/[id]/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/grades/[id]/route.ts b/app/api/grades/[id]/route.ts index 0141f63..a4e66ff 100644 --- a/app/api/grades/[id]/route.ts +++ b/app/api/grades/[id]/route.ts @@ -35,7 +35,7 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string await connectDB() const grade = await Grade.findOneAndUpdate( - { _id: id }, + { _id: id, teacherId: userId }, sanitizedBody, { new: true } ) @@ -56,7 +56,7 @@ export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: str try { const { id } = await ctx.params await connectDB() - const deleted = await Grade.findOneAndDelete({ _id: id }) + const deleted = await Grade.findOneAndDelete({ _id: id, teacherId: userId }) if (!deleted) { return NextResponse.json({ error: 'Grade not found' }, { status: 404 }) From bac0c629ac339ce4f36a318ab24febbfc41d66e5 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:25:31 +0530 Subject: [PATCH 13/22] fix: validate ObjectId before grade DELETE query Made-with: Cursor --- app/api/grades/[id]/route.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/api/grades/[id]/route.ts b/app/api/grades/[id]/route.ts index a4e66ff..3572308 100644 --- a/app/api/grades/[id]/route.ts +++ b/app/api/grades/[id]/route.ts @@ -55,6 +55,11 @@ export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: str try { const { id } = await ctx.params + + if (!mongoose.Types.ObjectId.isValid(id)) { + return NextResponse.json({ error: 'Invalid id' }, { status: 400 }) + } + await connectDB() const deleted = await Grade.findOneAndDelete({ _id: id, teacherId: userId }) From 35d9d1b630f7ed4eefc03de2ba3a2f1bf83fe923 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:25:50 +0530 Subject: [PATCH 14/22] fix: recompute letter grade and validate marks on grade PUT Made-with: Cursor --- app/api/grades/[id]/route.ts | 39 +++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/app/api/grades/[id]/route.ts b/app/api/grades/[id]/route.ts index 3572308..8a9abf0 100644 --- a/app/api/grades/[id]/route.ts +++ b/app/api/grades/[id]/route.ts @@ -4,7 +4,18 @@ import mongoose from 'mongoose' import { connectDB } from '@/lib/mongodb' import { Grade } from '@/models/Grade' -const ALLOWED_UPDATE_FIELDS = ['marks', 'maxMarks', 'grade'] +const ALLOWED_UPDATE_FIELDS = ['marks', 'maxMarks'] + +function calcGrade(marks: number, max: number): string { + const pct = (marks / max) * 100 + if (pct > 90) return 'A+' + if (pct >= 80) return 'A' + if (pct >= 70) return 'B+' + if (pct >= 60) return 'B' + if (pct >= 50) return 'C' + if (pct >= 40) return 'D' + return 'F' +} export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { const { userId } = await auth() @@ -34,10 +45,32 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string } await connectDB() + const existing = await Grade.findOne({ _id: id, teacherId: userId }).lean() + if (!existing) { + return NextResponse.json({ error: 'Not found' }, { status: 404 }) + } + + const nextMarks = + typeof sanitizedBody.marks === 'number' ? sanitizedBody.marks : existing.marks + const nextMax = + typeof sanitizedBody.maxMarks === 'number' + ? sanitizedBody.maxMarks + : existing.maxMarks + + if (nextMarks > nextMax) { + return NextResponse.json( + { error: 'marks must be less than or equal to maxMarks' }, + { status: 400 }, + ) + } + + const updatePayload: Record = { ...sanitizedBody } + updatePayload.grade = calcGrade(nextMarks, nextMax) + const grade = await Grade.findOneAndUpdate( { _id: id, teacherId: userId }, - sanitizedBody, - { new: true } + { $set: updatePayload }, + { new: true }, ) if (!grade) return NextResponse.json({ error: 'Not found' }, { status: 404 }) return NextResponse.json(grade) From bd4f8ec9b6694ae7a6e44c37283b85f3e24567c7 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:25:55 +0530 Subject: [PATCH 15/22] fix: scope attendance uniqueness per teacher and student Made-with: Cursor --- models/Attendance.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/models/Attendance.ts b/models/Attendance.ts index 3f05fb1..aebb1c1 100644 --- a/models/Attendance.ts +++ b/models/Attendance.ts @@ -24,6 +24,9 @@ const AttendanceSchema = new Schema( { timestamps: true } ) -AttendanceSchema.index({ studentId: 1, date: 1 }, { unique: true }) +AttendanceSchema.index( + { teacherId: 1, studentId: 1, date: 1 }, + { unique: true }, +) export const Attendance = models.Attendance ?? model('Attendance', AttendanceSchema) From 29166299a87ddfcf4a17f23e9beef031b4a2a67f Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:26:01 +0530 Subject: [PATCH 16/22] fix: parse YYYY-MM-DD attendance dates without UTC shift Made-with: Cursor --- app/api/attendance/route.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/app/api/attendance/route.ts b/app/api/attendance/route.ts index 14b6c4d..1aedf8f 100644 --- a/app/api/attendance/route.ts +++ b/app/api/attendance/route.ts @@ -30,13 +30,27 @@ export async function GET(req: NextRequest) { const query: Record = { teacherId: userId }; - // Helper to validate and normalize date strings to YYYY-MM-DD format + // Normalize calendar dates to YYYY-MM-DD without UTC day-shift (date-only input) const normalizeDate = (dateStr: string): string | null => { + const trimmed = dateStr.trim(); + const ymd = /^(\d{4})-(\d{2})-(\d{2})$/.exec(trimmed); + if (ymd) { + const y = Number(ymd[1]); + const m = Number(ymd[2]); + const day = Number(ymd[3]); + const local = new Date(y, m - 1, day); + if ( + local.getFullYear() === y && + local.getMonth() === m - 1 && + local.getDate() === day + ) { + return `${ymd[1]}-${ymd[2]}-${ymd[3]}`; + } + return null; + } try { - // Try to parse as ISO date (YYYY-MM-DD or full ISO 8601) - const d = new Date(dateStr); + const d = new Date(trimmed); if (isNaN(d.getTime())) return null; - // Return in YYYY-MM-DD format for MongoDB string comparison return d.toISOString().split("T")[0]; } catch { return null; From 76d57edc9df945b1040061c898301b5e3619afc4 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:26:08 +0530 Subject: [PATCH 17/22] fix: load class roster via class query not search Made-with: Cursor --- app/dashboard/attendance/AttendanceClient.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dashboard/attendance/AttendanceClient.tsx b/app/dashboard/attendance/AttendanceClient.tsx index f97e7db..0411e0f 100644 --- a/app/dashboard/attendance/AttendanceClient.tsx +++ b/app/dashboard/attendance/AttendanceClient.tsx @@ -75,7 +75,7 @@ export function AttendanceClient() { } try { const res = await fetch( - `/api/students?search=${encodeURIComponent(selectedClass)}&limit=100`, + `/api/students?class=${encodeURIComponent(selectedClass)}&limit=100`, ); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); From 6fdc891db8ff60548ba3903476edcb67c012b913 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:26:21 +0530 Subject: [PATCH 18/22] fix: handle non-OK responses when loading students list Made-with: Cursor --- app/dashboard/students/StudentsClient.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/dashboard/students/StudentsClient.tsx b/app/dashboard/students/StudentsClient.tsx index 82a1d96..9ec720b 100644 --- a/app/dashboard/students/StudentsClient.tsx +++ b/app/dashboard/students/StudentsClient.tsx @@ -548,14 +548,27 @@ export function StudentsClient() { if (debouncedSearch) params.set("search", debouncedSearch); if (classFilter !== "all") params.set("class", classFilter); const res = await fetch(`/api/students?${params}`); + if (!res.ok) { + const errBody = await res.json().catch(() => ({})); + const msg = + typeof (errBody as { error?: string }).error === "string" + ? (errBody as { error: string }).error + : `Request failed (${res.status})`; + throw new Error(msg); + } const data = await res.json(); setStudents(data.students ?? []); setTotal(data.total ?? 0); setPages(data.pages ?? 1); + } catch (e) { + toast(e instanceof Error ? e.message : "Failed to load students", "error"); + setStudents([]); + setTotal(0); + setPages(1); } finally { setLoading(false); } - }, [page, debouncedSearch, classFilter]); + }, [page, debouncedSearch, classFilter, toast]); useEffect(() => { fetchStudents(); From 9fe5c6b9601692c7a2890d464ac59b6b204c0872 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:26:34 +0530 Subject: [PATCH 19/22] fix: sort drawer recent grades using term order list Made-with: Cursor --- app/dashboard/students/StudentsClient.tsx | 37 +++++++++++++++-------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/app/dashboard/students/StudentsClient.tsx b/app/dashboard/students/StudentsClient.tsx index 9ec720b..d864363 100644 --- a/app/dashboard/students/StudentsClient.tsx +++ b/app/dashboard/students/StudentsClient.tsx @@ -188,25 +188,36 @@ function StudentDrawer({ }, [attendance]) const recentGrades = useMemo(() => { + const TERM_ORDER = [ + "Term 1", + "Term 2", + "Term 3", + "Term 4", + "Semester 1", + "Semester 2", + "Semester 3", + "Semester 4", + "Semester 5", + "Semester 6", + "Semester 7", + "Semester 8", + ]; const SEASON_ORDER: Record = { Spring: 1, Summer: 2, Fall: 3, Winter: 4, }; - const sortedGrades = [...grades].sort((a, b) => { - // Parse term string (e.g., "Spring 2024") to extract season and year - const parseTermKey = (term: string): number => { - const parts = term.split(" "); - const season = parts[0] || ""; - const year = parseInt(parts[1] || "0", 10); - const seasonVal = SEASON_ORDER[season] ?? 0; - return year * 100 + seasonVal; // e.g., 202401 for Spring 2024 - }; - const aKey = parseTermKey(a.term); - const bKey = parseTermKey(b.term); - return bKey - aKey; // Descending chronological order - }); + const termRank = (term: string): number => { + const i = TERM_ORDER.indexOf(term); + if (i !== -1) return 1000 + i; + const parts = term.split(" "); + const season = parts[0] || ""; + const year = parseInt(parts[1] || "0", 10); + const seasonVal = SEASON_ORDER[season] ?? 0; + return year * 100 + seasonVal; + }; + const sortedGrades = [...grades].sort((a, b) => termRank(b.term) - termRank(a.term)); return sortedGrades.slice(0, 6); }, [grades]); From 921a9aca9c524750ec1af613814f521b2dc7a478 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:27:04 +0530 Subject: [PATCH 20/22] fix: check dashboard API responses before parsing bodies Made-with: Cursor --- app/dashboard/OverviewClient.tsx | 56 +++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/app/dashboard/OverviewClient.tsx b/app/dashboard/OverviewClient.tsx index 169795e..94e1c9a 100644 --- a/app/dashboard/OverviewClient.tsx +++ b/app/dashboard/OverviewClient.tsx @@ -200,14 +200,52 @@ export function OverviewClient() { fetch("/api/announcements?limit=5"), ]); - const [students, assignmentsData, attendance, grades, announcements] = - await Promise.all([ - studentsRes.json(), - assignmentsRes.json(), - attendanceRes.json(), - gradesRes.json(), - announcementsRes.json(), - ]); + const students = await studentsRes.json(); + if (!studentsRes.ok) { + throw new Error( + typeof students.error === "string" + ? students.error + : `Students failed (${studentsRes.status})`, + ); + } + const assignmentsData = await assignmentsRes.json(); + if (!assignmentsRes.ok) { + throw new Error( + typeof assignmentsData.error === "string" + ? assignmentsData.error + : `Assignments failed (${assignmentsRes.status})`, + ); + } + const attendanceRaw = await attendanceRes.json(); + if (!attendanceRes.ok) { + throw new Error( + typeof attendanceRaw.error === "string" + ? attendanceRaw.error + : `Attendance failed (${attendanceRes.status})`, + ); + } + const gradesRaw = await gradesRes.json(); + if (!gradesRes.ok) { + throw new Error( + typeof gradesRaw.error === "string" + ? gradesRaw.error + : `Grades failed (${gradesRes.status})`, + ); + } + const announcementsRaw = await announcementsRes.json(); + if (!announcementsRes.ok) { + throw new Error( + typeof announcementsRaw.error === "string" + ? announcementsRaw.error + : `Announcements failed (${announcementsRes.status})`, + ); + } + + const attendance = Array.isArray(attendanceRaw) ? attendanceRaw : []; + const grades = Array.isArray(gradesRaw) ? gradesRaw : []; + const announcements = Array.isArray(announcementsRaw) + ? announcementsRaw + : []; const assignments = assignmentsData.assignments ?? assignmentsData; @@ -316,7 +354,7 @@ export function OverviewClient() { .slice(0, 5); setStats({ - totalStudents: students.students?.length ?? 0, + totalStudents: students.total ?? students.students?.length ?? 0, totalAssignments: Array.isArray(assignments) ? assignments.length : (assignments.length ?? 0), From 4c2d8784b0c1648e2609c46a8b1116550e15aaf2 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:27:16 +0530 Subject: [PATCH 21/22] fix: align overview D grade point with grades page Made-with: Cursor --- app/dashboard/OverviewClient.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dashboard/OverviewClient.tsx b/app/dashboard/OverviewClient.tsx index 94e1c9a..f1c19bd 100644 --- a/app/dashboard/OverviewClient.tsx +++ b/app/dashboard/OverviewClient.tsx @@ -292,7 +292,7 @@ export function OverviewClient() { "B+": 8, B: 7, C: 6, - D: 4, + D: 5, F: 0, }; const termMap: Record = {}; From 952b002f38a1d26b1db7f6743b7b6095fa8f65e4 Mon Sep 17 00:00:00 2001 From: hdRutvik114 Date: Sat, 18 Apr 2026 12:28:49 +0530 Subject: [PATCH 22/22] fix: guard invalid assignment deadlines in days-until badge Made-with: Cursor --- app/dashboard/assignments/AssignmentsClient.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/dashboard/assignments/AssignmentsClient.tsx b/app/dashboard/assignments/AssignmentsClient.tsx index e0e7f13..dfc6623 100644 --- a/app/dashboard/assignments/AssignmentsClient.tsx +++ b/app/dashboard/assignments/AssignmentsClient.tsx @@ -59,11 +59,14 @@ const COLUMNS: { ]; function daysUntil(deadline: string) { - return Math.ceil((new Date(deadline).getTime() - Date.now()) / (1000 * 60 * 60 * 24)) + const t = new Date(deadline).getTime(); + if (!Number.isFinite(t)) return NaN; + return Math.ceil((t - Date.now()) / (1000 * 60 * 60 * 24)); } function DeadlineBadge({ deadline }: { deadline: string }) { const days = daysUntil(deadline); + if (!Number.isFinite(days)) return Invalid date; if (days < 0) return Overdue; if (days <= 2) return {days}d left; if (days <= 7) return {days}d left;