From 88877040f57878d81ccf225983cd0a5629488f24 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 18 Apr 2026 20:46:16 +0530 Subject: [PATCH 1/9] fix: resolve validation bypass in announcements route --- app/api/announcements/[id]/route.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/app/api/announcements/[id]/route.ts b/app/api/announcements/[id]/route.ts index 6c32f47..5e49b33 100644 --- a/app/api/announcements/[id]/route.ts +++ b/app/api/announcements/[id]/route.ts @@ -1,10 +1,17 @@ import { auth } from '@clerk/nextjs/server' import { NextRequest, NextResponse } from 'next/server' import mongoose from 'mongoose' +import { z } from 'zod' import { connectDB } from '@/lib/mongodb' import { Announcement } from '@/models/Announcement' -const ALLOWED_FIELDS = ['title', 'content', 'body', 'audience', 'category', 'pinned', 'expiresAt'] +const AnnouncementUpdateSchema = z.object({ + title: z.string().min(1).optional(), + content: z.string().min(1).optional(), + audience: z.string().optional(), + category: z.enum(['academic', 'events', 'admin', 'general']).optional(), + pinned: z.boolean().optional(), +}) export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { const { userId } = await auth() @@ -27,17 +34,18 @@ 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 - const sanitizedBody: Record = {} - for (const key of ALLOWED_FIELDS) { - if (key in body) { - sanitizedBody[key] = body[key] - } + const parsed = AnnouncementUpdateSchema.safeParse(body) + if (!parsed.success) { + return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }) + } + + if (Object.keys(parsed.data).length === 0) { + return NextResponse.json({ error: 'No valid fields to update' }, { status: 400 }) } const announcement = await Announcement.findOneAndUpdate( - { _id: id }, - { $set: sanitizedBody }, + { _id: id, teacherId: userId }, + { $set: parsed.data }, { new: true, runValidators: true, context: 'query' } ) if (!announcement) return NextResponse.json({ error: 'Not found' }, { status: 404 }) @@ -63,7 +71,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 e70054c63a92a5158312d219e6a186dc041534ec Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 18 Apr 2026 20:46:16 +0530 Subject: [PATCH 2/9] fix: enforce strict payload schema in assignments endpoint --- app/api/assignments/[id]/route.ts | 35 +++++++++++++++++++++---------- app/api/assignments/route.ts | 6 ++++-- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/app/api/assignments/[id]/route.ts b/app/api/assignments/[id]/route.ts index 21fca1c..bc18188 100644 --- a/app/api/assignments/[id]/route.ts +++ b/app/api/assignments/[id]/route.ts @@ -1,10 +1,22 @@ import { auth } from '@clerk/nextjs/server' import { NextRequest, NextResponse } from 'next/server' import mongoose from 'mongoose' +import { z } from 'zod' import { connectDB } from '@/lib/mongodb' import { Assignment } from '@/models/Assignment' -const ALLOWED_UPDATE_FIELDS = ['title', 'description', 'dueDate', 'deadline', 'subject', 'class', 'status', 'kanbanStatus', 'maxMarks'] +const AssignmentUpdateSchema = z.object({ + title: z.string().min(1).optional(), + description: z.string().optional(), + subject: z.string().min(1).optional(), + class: z.string().min(1).optional(), + deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), { + message: 'Invalid deadline', + }).optional(), + maxMarks: z.number().min(1).optional(), + status: z.enum(['active', 'closed']).optional(), + kanbanStatus: z.enum(['todo', 'in_progress', 'submitted']).optional(), +}) export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { const { userId } = await auth() @@ -27,18 +39,19 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 }) } - // Sanitize: only allow whitelisted fields - const sanitizedBody: Record = {} - for (const key of ALLOWED_UPDATE_FIELDS) { - if (key in body) { - sanitizedBody[key] = body[key] - } + const parsed = AssignmentUpdateSchema.safeParse(body) + if (!parsed.success) { + return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }) + } + + if (Object.keys(parsed.data).length === 0) { + return NextResponse.json({ error: 'No valid fields to update' }, { status: 400 }) } const assignment = await Assignment.findOneAndUpdate( - { _id: id }, - sanitizedBody, - { new: true } + { _id: id, teacherId: userId }, + { $set: parsed.data }, + { new: true, runValidators: true, context: 'query' } ) if (!assignment) return NextResponse.json({ error: 'Not found' }, { status: 404 }) return NextResponse.json(assignment) @@ -63,7 +76,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 }) diff --git a/app/api/assignments/route.ts b/app/api/assignments/route.ts index 19021d8..433caf0 100644 --- a/app/api/assignments/route.ts +++ b/app/api/assignments/route.ts @@ -9,7 +9,9 @@ const AssignmentSchema = z.object({ description: z.string().optional(), subject: z.string().min(1), class: z.string().min(1), - deadline: z.string().min(1), + deadline: z.string().min(1).refine((value) => !Number.isNaN(Date.parse(value)), { + message: 'Invalid deadline', + }), maxMarks: z.number().min(1).optional(), status: z.enum(['active', 'closed']).optional(), kanbanStatus: z.enum(['todo', 'in_progress', 'submitted']).optional(), @@ -52,7 +54,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 a0a8d51900daf3325f9c0019533af8160a3f1408 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 18 Apr 2026 20:46:17 +0530 Subject: [PATCH 3/9] fix: correct attendance record authorization loop --- app/api/attendance/route.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/api/attendance/route.ts b/app/api/attendance/route.ts index 14b6c4d..60981ac 100644 --- a/app/api/attendance/route.ts +++ b/app/api/attendance/route.ts @@ -1,11 +1,16 @@ import { auth } from '@clerk/nextjs/server' import { NextRequest, NextResponse } from 'next/server' +import mongoose from 'mongoose' import { connectDB } from '@/lib/mongodb' import { Attendance } from '@/models/Attendance' import { z } from 'zod' +const StudentIdSchema = z.string().refine((value) => mongoose.Types.ObjectId.isValid(value), { + message: 'Invalid studentId', +}) + const AttendanceSchema = z.object({ - studentId: z.string().min(1), + studentId: StudentIdSchema, studentName: z.string().min(1), class: z.string().min(1), date: z.string().min(1), @@ -30,6 +35,13 @@ export async function GET(req: NextRequest) { const query: Record = { teacherId: userId }; + if (studentId && !mongoose.Types.ObjectId.isValid(studentId)) { + return NextResponse.json( + { error: "Invalid studentId" }, + { status: 400 }, + ); + } + // Helper to validate and normalize date strings to YYYY-MM-DD format const normalizeDate = (dateStr: string): string | null => { try { From 742fb7fc429851204e368c77934be2a38793f013 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 18 Apr 2026 20:46:17 +0530 Subject: [PATCH 4/9] fix: handle empty values safely in grades endpoint --- app/api/grades/[id]/route.ts | 73 ++++++++++++++++++++++++++++++------ app/api/grades/route.ts | 25 ++++++++---- 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/app/api/grades/[id]/route.ts b/app/api/grades/[id]/route.ts index 0141f63..e21ab1a 100644 --- a/app/api/grades/[id]/route.ts +++ b/app/api/grades/[id]/route.ts @@ -3,8 +3,31 @@ import { NextRequest, NextResponse } from 'next/server' import mongoose from 'mongoose' import { connectDB } from '@/lib/mongodb' import { Grade } from '@/models/Grade' +import { z } from 'zod' -const ALLOWED_UPDATE_FIELDS = ['marks', 'maxMarks', 'grade'] +const StudentIdSchema = z.string().refine((value) => mongoose.Types.ObjectId.isValid(value), { + message: 'Invalid studentId', +}) + +const GradeUpdateSchema = z.object({ + studentId: StudentIdSchema.optional(), + studentName: z.string().min(1).optional(), + subject: z.string().min(1).optional(), + marks: z.number().min(0).optional(), + maxMarks: z.number().min(1).optional(), + term: z.string().min(1).optional(), +}) + +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() @@ -15,7 +38,7 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string // Validate ObjectId if (!mongoose.Types.ObjectId.isValid(id)) { - return NextResponse.json({ error: 'Not found' }, { status: 404 }) + return NextResponse.json({ error: 'Invalid id' }, { status: 400 }) } let body @@ -25,19 +48,37 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 }) } - // Sanitize: only allow whitelisted fields - const sanitizedBody: Record = {} - for (const key of ALLOWED_UPDATE_FIELDS) { - if (key in body) { - sanitizedBody[key] = body[key] - } + const parsed = GradeUpdateSchema.safeParse(body) + if (!parsed.success) { + return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }) + } + + if (Object.keys(parsed.data).length === 0) { + return NextResponse.json({ error: 'No valid fields to update' }, { status: 400 }) } await connectDB() + const existing = await Grade.findOne({ _id: id, teacherId: userId }) + if (!existing) return NextResponse.json({ error: 'Not found' }, { status: 404 }) + + const marks = parsed.data.marks ?? existing.marks + const maxMarks = parsed.data.maxMarks ?? existing.maxMarks + if (marks > maxMarks) { + return NextResponse.json( + { error: { fieldErrors: { marks: ['marks must be less than or equal to maxMarks'] } } }, + { status: 400 }, + ) + } + const grade = await Grade.findOneAndUpdate( - { _id: id }, - sanitizedBody, - { new: true } + { _id: id, teacherId: userId }, + { + $set: { + ...parsed.data, + grade: calcGrade(marks, maxMarks), + }, + }, + { new: true, runValidators: true, context: 'query' } ) if (!grade) return NextResponse.json({ error: 'Not found' }, { status: 404 }) return NextResponse.json(grade) @@ -45,6 +86,9 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string if (error instanceof Error) { console.error('PUT /api/grades/[id] error:', error.message) } + if ((error as { code?: number }).code === 11000) { + return NextResponse.json({ error: 'A grade for this student, subject, and term already exists' }, { status: 409 }) + } return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } } @@ -55,8 +99,13 @@ 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 }) + const deleted = await Grade.findOneAndDelete({ _id: id, teacherId: userId }) if (!deleted) { return NextResponse.json({ error: 'Grade not found' }, { status: 404 }) diff --git a/app/api/grades/route.ts b/app/api/grades/route.ts index b9da63d..265d66d 100644 --- a/app/api/grades/route.ts +++ b/app/api/grades/route.ts @@ -1,18 +1,23 @@ import { auth } from '@clerk/nextjs/server' import { NextRequest, NextResponse } from 'next/server' +import mongoose from 'mongoose' import { connectDB } from '@/lib/mongodb' import { Grade } from '@/models/Grade' import { z } from 'zod' +const StudentIdSchema = z.string().refine((value) => mongoose.Types.ObjectId.isValid(value), { + message: 'Invalid studentId', +}) + const GradeSchema = z.object({ - studentId: z.string().min(1), + studentId: StudentIdSchema, studentName: z.string().min(1), subject: z.string().min(1), marks: z.number().min(0), maxMarks: z.number().min(1).optional(), term: z.string().optional(), }).refine( - (data) => !data.maxMarks || data.marks <= data.maxMarks, + (data) => data.marks <= (data.maxMarks ?? 100), { message: 'marks must be less than or equal to maxMarks', path: ['marks'], @@ -40,6 +45,10 @@ export async function GET(req: NextRequest) { const studentId = searchParams.get('studentId') const subject = searchParams.get('subject') + if (studentId && !mongoose.Types.ObjectId.isValid(studentId)) { + return NextResponse.json({ error: 'Invalid studentId' }, { status: 400 }) + } + const query: Record = { teacherId: userId } if (studentId) query.studentId = studentId if (subject) query.subject = subject @@ -48,7 +57,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 }) } } @@ -70,19 +79,19 @@ 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 = 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 } + { $set: { ...data, maxMarks: max, term, teacherId: userId, grade: calcGrade(data.marks, max) } }, + { upsert: true, new: true, runValidators: true, context: 'query', setDefaultsOnInsert: true } ) return NextResponse.json(grade, { status: 201 }) } catch (error) { 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 5ae2d74656f0d44567dc570c9993e0a191d153bc Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 18 Apr 2026 20:46:17 +0530 Subject: [PATCH 5/9] fix: remove unused _req warning in profile route --- app/api/profile/route.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts index a3d98bf..7ca70ad 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 { @@ -20,6 +13,9 @@ export async function GET(req: NextRequest) { if (!teacher) { const clerkUser = await currentUser() + if (!clerkUser) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } const created = await Teacher.create({ clerkId: userId, name: clerkUser?.fullName ?? '', From 243702d9954cc644ce29552de38474bde3a70ab8 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 18 Apr 2026 20:46:17 +0530 Subject: [PATCH 6/9] fix: sanitize user input securely in students API --- app/api/students/[id]/route.ts | 33 ++++++++++++++++++++++----------- app/api/students/route.ts | 9 ++++++--- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/app/api/students/[id]/route.ts b/app/api/students/[id]/route.ts index 2eaaf93..971c8c4 100644 --- a/app/api/students/[id]/route.ts +++ b/app/api/students/[id]/route.ts @@ -1,10 +1,20 @@ import { auth } from '@clerk/nextjs/server' import { NextRequest, NextResponse } from 'next/server' import mongoose from 'mongoose' +import { z } from 'zod' import { connectDB } from '@/lib/mongodb' import { Student } from '@/models/Student' -const ALLOWED_UPDATE_FIELDS = ['name', 'email', 'grade', 'rollNo', 'class', 'phone', 'address', 'parentName', 'parentPhone'] +const StudentUpdateSchema = z.object({ + name: z.string().min(1).optional(), + rollNo: z.string().min(1).optional(), + class: z.string().min(1).optional(), + email: z.string().email().optional().or(z.literal('')), + phone: z.string().optional(), + address: z.string().optional(), + parentName: z.string().optional(), + parentPhone: z.string().optional(), +}) export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string }> }) { const { userId } = await auth() @@ -25,19 +35,20 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string return NextResponse.json({ error: 'Bad Request' }, { status: 400 }) } - // Sanitize: only allow whitelisted fields - const sanitizedBody: Record = {} - for (const key of ALLOWED_UPDATE_FIELDS) { - if (key in body) { - sanitizedBody[key] = body[key] - } + const parsed = StudentUpdateSchema.safeParse(body) + if (!parsed.success) { + return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 }) + } + + if (Object.keys(parsed.data).length === 0) { + return NextResponse.json({ error: 'No valid fields to update' }, { status: 400 }) } await connectDB() const student = await Student.findOneAndUpdate( - { _id: id }, - sanitizedBody, - { new: true } + { _id: id, teacherId: userId }, + { $set: parsed.data }, + { new: true, runValidators: true, context: 'query' } ) if (!student) return NextResponse.json({ error: 'Not found' }, { status: 404 }) return NextResponse.json(student) @@ -65,7 +76,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 }) diff --git a/app/api/students/route.ts b/app/api/students/route.ts index 8f3dcc2..bb7d73a 100644 --- a/app/api/students/route.ts +++ b/app/api/students/route.ts @@ -26,7 +26,7 @@ export async function GET(req: NextRequest) { try { await connectDB(); const { searchParams } = new URL(req.url); - const search = (searchParams.get("search") ?? "").replace(/\s+/g, ' '); + const search = (searchParams.get("search") ?? "").trim().replace(/\s+/g, ' '); const classFilter = searchParams.get("class") ?? ""; // Parse and validate pagination @@ -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 ac9c5d51666484b94b782262e4c385e875be7d03 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 18 Apr 2026 20:46:17 +0530 Subject: [PATCH 7/9] fix: eliminate cascading renders in Overview component --- app/dashboard/OverviewClient.tsx | 80 +++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/app/dashboard/OverviewClient.tsx b/app/dashboard/OverviewClient.tsx index 169795e..f84211b 100644 --- a/app/dashboard/OverviewClient.tsx +++ b/app/dashboard/OverviewClient.tsx @@ -189,27 +189,63 @@ export function OverviewClient() { const [ studentsRes, assignmentsRes, + activeAssignmentsRes, attendanceRes, gradesRes, announcementsRes, ] = await Promise.all([ fetch("/api/students?limit=5"), - fetch("/api/assignments"), + fetch("/api/assignments?limit=100"), + fetch("/api/assignments?status=active&limit=1"), fetch("/api/attendance"), fetch("/api/grades"), fetch("/api/announcements?limit=5"), ]); - const [students, assignmentsData, attendance, grades, announcements] = + const readJson = async (response: Response, label: string) => { + const data = await response.json(); + if (!response.ok) { + const message = + typeof data?.error === "string" + ? data.error + : `${label} request failed`; + throw new Error(message); + } + return data; + }; + + const [ + students, + assignmentsData, + activeAssignmentsData, + attendance, + grades, + announcements, + ] = await Promise.all([ - studentsRes.json(), - assignmentsRes.json(), - attendanceRes.json(), - gradesRes.json(), - announcementsRes.json(), + readJson(studentsRes, "Students"), + readJson(assignmentsRes, "Assignments"), + readJson(activeAssignmentsRes, "Active assignments"), + readJson(attendanceRes, "Attendance"), + readJson(gradesRes, "Grades"), + readJson(announcementsRes, "Announcements"), ]); - const assignments = assignmentsData.assignments ?? assignmentsData; + const studentRows = Array.isArray(students.students) ? students.students : []; + const assignments = Array.isArray(assignmentsData.assignments) + ? assignmentsData.assignments + : Array.isArray(assignmentsData) + ? assignmentsData + : []; + const attendanceRows = Array.isArray(attendance) ? attendance : []; + const gradeRows = Array.isArray(grades) ? grades : []; + const announcementRows = Array.isArray(announcements) ? announcements : []; + const activeAssignmentsTotal = + typeof activeAssignmentsData.total === "number" + ? activeAssignmentsData.total + : Array.isArray(activeAssignmentsData.assignments) + ? activeAssignmentsData.assignments.length + : 0; // ── Attendance ── const dateMap: Record< @@ -219,7 +255,7 @@ export function OverviewClient() { let totalPresent = 0, totalAbsent = 0, totalLate = 0; - for (const rec of attendance) { + for (const rec of attendanceRows) { if (!dateMap[rec.date]) dateMap[rec.date] = { present: 0, absent: 0, late: 0 }; // Validate status before using it @@ -254,11 +290,11 @@ export function OverviewClient() { "B+": 8, B: 7, C: 6, - D: 4, + D: 5, F: 0, }; const termMap: Record = {}; - for (const g of grades) { + for (const g of gradeRows) { (termMap[g.term] ??= []).push(GRADE_POINT[g.grade] ?? 0); } const TERM_ORDER = [ @@ -286,7 +322,7 @@ export function OverviewClient() { // ── Grade distribution ── const gradeCounts: Record = {}; - for (const g of grades) + for (const g of gradeRows) gradeCounts[g.grade || "N/A"] = (gradeCounts[g.grade || "N/A"] || 0) + 1; const gradeDistribution = Object.entries(gradeCounts).map( @@ -316,13 +352,15 @@ export function OverviewClient() { .slice(0, 5); setStats({ - totalStudents: students.students?.length ?? 0, - totalAssignments: Array.isArray(assignments) - ? assignments.length - : (assignments.length ?? 0), - pendingAssignments: assignments.filter( - (a: { status: string }) => a.status === "active", - ).length, + totalStudents: + typeof students.total === "number" + ? students.total + : studentRows.length, + totalAssignments: + typeof assignmentsData.total === "number" + ? assignmentsData.total + : assignments.length, + pendingAssignments: activeAssignmentsTotal, attendancePct, attendanceBreakdown: { present: totalPresent, @@ -333,8 +371,8 @@ export function OverviewClient() { cgpaTrend, gradeDistribution, upcomingDeadlines, - recentAnnouncements: announcements.slice(0, 5), - recentStudents: students.students?.slice(0, 5) ?? [], + recentAnnouncements: announcementRows.slice(0, 5), + recentStudents: studentRows.slice(0, 5), }); setLastRefreshed(new Date()); } catch (err) { From 28b1f11b056d6c7c9980ecefa6fc962aba01dbf6 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 18 Apr 2026 20:46:18 +0530 Subject: [PATCH 8/9] fix: optimize state effects in assignments client --- app/dashboard/assignments/AssignmentsClient.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/dashboard/assignments/AssignmentsClient.tsx b/app/dashboard/assignments/AssignmentsClient.tsx index e0e7f13..d8f8ae5 100644 --- a/app/dashboard/assignments/AssignmentsClient.tsx +++ b/app/dashboard/assignments/AssignmentsClient.tsx @@ -344,7 +344,7 @@ export function AssignmentsClient() { description: a.description, subject: a.subject, class: a.class, - deadline: a.deadline, + deadline: a.deadline.slice(0, 10), maxMarks: a.maxMarks, }); setModalOpen(true); @@ -387,7 +387,7 @@ export function AssignmentsClient() { ), ); try { - await fetch(`/api/assignments/${id}`, { + const res = await fetch(`/api/assignments/${id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -395,6 +395,9 @@ export function AssignmentsClient() { status: col === "submitted" ? "closed" : "active", }), }); + if (!res.ok) { + throw new Error(`HTTP ${res.status}`); + } } catch (error) { fetchAssignments(); toast( From ebcdcb83e4154675eeb0cdd183e126f82a913a14 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Sat, 18 Apr 2026 20:46:18 +0530 Subject: [PATCH 9/9] fix: synchronize corrupted package lock dependencies --- package-lock.json | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 54cfc6a..4d8306b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,6 +73,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1784,6 +1785,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1864,6 +1866,7 @@ "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.2", "@typescript-eslint/types": "8.58.2", @@ -2389,6 +2392,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2749,6 +2753,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -3495,6 +3500,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3680,6 +3686,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5611,6 +5618,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-16.2.3.tgz", "integrity": "sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "16.2.3", "@swc/helpers": "0.5.15", @@ -6068,6 +6076,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -6077,6 +6086,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -6104,13 +6114,15 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-redux": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -6163,7 +6175,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -6900,6 +6913,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -7074,6 +7088,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7401,6 +7416,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }