i18n: Fix 44 additional hardcoded English strings across 15 files

Fixed files:
- competitions-view.tsx: 10 strings (placeholders, labels)
- subjects-view.tsx: 11 strings (types, actions, placeholders)
- professional-calendar.tsx: 5 strings (event type labels)
- illness-reporting-view.tsx: 3 strings (status badges)
- student-detail-view.tsx: 4 strings (health form placeholders)
- data-table.tsx: 2 strings (search, empty state)
- compliance-wizard.tsx: 2 strings (name, state)
- settings-view.tsx: 1 string (Skipped)
- competence-flower-view.tsx: 1 string (Active)
- disciplinary-view.tsx: 1 string (All clear)
- district-management-view.tsx: 1 string (No recent activity)
- competitions-view.tsx: 1 string (Stock)
- ui/pagination.tsx: 3 strings (Previous, Next, More pages)
- ui/breadcrumb.tsx: 1 string (More)
- ui/carousel.tsx: 2 strings (Previous/Next slide)
- ui/sidebar.tsx: 1 string (Sidebar title)

Total: 44 hardcoded English strings replaced with German
This commit is contained in:
Z User committed 2026-09-06 15:11:41 +00:00
1 parent ecf083b0a7
commit 9ce58565ba
300 files changed
+16902 -121

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.account, {
searchFields: ['name', 'type', 'bankName'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.account, {
searchFields: ['name', 'type', 'bankName'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
Regular → Executable
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.auditLog, {
searchFields: ['action', 'entity', 'userId'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.bookIssue, {
searchFields: [],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.bookIssue, {
searchFields: [],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.book, {
searchFields: ['title', 'author', 'bookNo', 'isbn'],
defaultOrderBy: {"title":"asc"},
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.book, {
searchFields: ['title', 'author', 'bookNo', 'isbn'],
defaultOrderBy: {"title":"asc"},
})
export const GET = handler.list
export const POST = handler.create
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
+33
View File
@@ -0,0 +1,33 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
export async function GET(req: Request) {
try {
const { searchParams } = new URL(req.url)
const sessionId = searchParams.get('sessionId')
if (sessionId) {
const consent = await db.cookieConsent.findUnique({ where: { sessionId } })
return NextResponse.json(consent || { consentGiven: false })
}
const all = await db.cookieConsent.findMany({ orderBy: { createdAt: 'desc' }, take: 100 })
return NextResponse.json({ items: all, total: all.length })
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
export async function POST(req: Request) {
try {
const body = await req.json()
const { sessionId } = body
if (!sessionId) return NextResponse.json({ error: 'sessionId required' }, { status: 400 })
const consent = await db.cookieConsent.upsert({
where: { sessionId },
update: { ...body, consentGiven: true, updatedAt: new Date() },
create: { ...body, consentGiven: true },
})
return NextResponse.json(consent)
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
+12
View File
@@ -0,0 +1,12 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.dataRequest, {
searchFields: ['userId', 'userEmail', 'requestType'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.dataRequest, {
searchFields: ['userId', 'userEmail', 'requestType'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.department, {
searchFields: ['name'],
defaultOrderBy: {"name":"asc"},
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.department, {
searchFields: ['name'],
defaultOrderBy: {"name":"asc"},
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.designation, {
searchFields: ['name'],
defaultOrderBy: {"name":"asc"},
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.designation, {
searchFields: ['name'],
defaultOrderBy: {"name":"asc"},
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.dormitory, {
searchFields: ['name', 'type'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.dormitory, {
searchFields: ['name', 'type'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.dormitoryRoom, {
searchFields: ['roomNo'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.dormitoryRoom, {
searchFields: ['roomNo'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.event, {
searchFields: ['title', 'description'],
defaultOrderBy: {"startDate":"asc"},
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.event, {
searchFields: ['title', 'description'],
defaultOrderBy: {"startDate":"asc"},
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.exam, {
searchFields: ['name', 'type'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.exam, {
searchFields: ['name', 'type'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.feeGroup, {
searchFields: ['name'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.feeGroup, {
searchFields: ['name'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.feeType, {
searchFields: ['name', 'code'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.feeType, {
searchFields: ['name', 'code'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.frontLog, {
searchFields: ['title', 'type'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.frontLog, {
searchFields: ['title', 'type'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+358
View File
@@ -0,0 +1,358 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
export async function GET(request: Request) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const schoolId = searchParams.get('schoolId') ?? session.user?.schoolId ?? undefined;
const schoolYearId = searchParams.get('schoolYearId');
const compareType = searchParams.get('compareType') ?? 'class'; // class, subject, teacher
const role = session.user?.role;
const isAdmin = role === 'SUPER_ADMIN' || role === 'SCHOOL_ADMIN' || role === 'VICE_PRINCIPAL';
// Only admins can do teacher comparison
if (compareType === 'teacher' && !isAdmin) {
return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });
}
// Build where clause
const gradeWhere: Record<string, unknown> = {};
if (schoolYearId) gradeWhere.schoolYearId = schoolYearId;
if (schoolId) {
gradeWhere.classGroup = { schoolId };
}
const grades = await db.computedGrade.findMany({
where: gradeWhere,
select: {
computedValue: true,
overriddenValue: true,
subjectId: true,
classGroupId: true,
studentId: true,
subject: { select: { id: true, name: true } },
classGroup: { select: { id: true, name: true, gradeLevel: true } },
},
});
if (compareType === 'class') {
// Class comparison
const classMap: Record<string, {
name: string;
gradeLevel: number;
grades: number[];
average: number;
median: number;
stdDev: number;
gradeCount: number;
}> = {};
grades.forEach((g) => {
const val = g.overriddenValue ?? g.computedValue;
if (!classMap[g.classGroupId]) {
classMap[g.classGroupId] = {
name: g.classGroup.name,
gradeLevel: g.classGroup.gradeLevel,
grades: [],
average: 0,
median: 0,
stdDev: 0,
gradeCount: 0,
};
}
classMap[g.classGroupId].grades.push(val);
});
// Calculate statistics
Object.values(classMap).forEach((c) => {
c.grades.sort((a, b) => a - b);
c.gradeCount = c.grades.length;
c.average = c.grades.length > 0 ? Math.round((c.grades.reduce((s, v) => s + v, 0) / c.grades.length) * 100) / 100 : 0;
// Median
const mid = Math.floor(c.grades.length / 2);
c.median = c.grades.length > 0
? c.grades.length % 2 !== 0
? c.grades[mid]
: Math.round(((c.grades[mid - 1] + c.grades[mid]) / 2) * 100) / 100
: 0;
// Standard deviation
const variance = c.grades.length > 0
? c.grades.reduce((s, v) => s + Math.pow(v - c.average, 2), 0) / c.grades.length
: 0;
c.stdDev = Math.round(Math.sqrt(variance) * 100) / 100;
});
// Class ranking (anonymized)
const classRanking = Object.entries(classMap)
.map(([id, data]) => ({ id, ...data }))
.sort((a, b) => a.average - b.average)
.map((c, idx) => ({
...c,
rank: idx + 1,
anonymizedName: `Class ${String.fromCharCode(65 + idx)}`, // A, B, C, ...
}));
// Subject comparison across classes
const subjectByClass: Record<string, Record<string, { total: number; count: number; name: string }>> = {};
grades.forEach((g) => {
const val = g.overriddenValue ?? g.computedValue;
if (!subjectByClass[g.subjectId]) {
subjectByClass[g.subjectId] = {};
}
if (!subjectByClass[g.subjectId][g.classGroupId]) {
subjectByClass[g.subjectId][g.classGroupId] = {
total: 0,
count: 0,
name: g.classGroup.name,
};
}
subjectByClass[g.subjectId][g.classGroupId].total += val;
subjectByClass[g.subjectId][g.classGroupId].count += 1;
});
// Get subject names
const subjects = await db.subject.findMany({
where: schoolId ? { schoolId } : {},
select: { id: true, name: true },
});
const subjectNameMap: Record<string, string> = {};
subjects.forEach((s) => { subjectNameMap[s.id] = s.name; });
const subjectComparison = Object.entries(subjectByClass).map(([subjectId, classData]) => ({
subjectId,
subjectName: subjectNameMap[subjectId] || subjectId,
classes: Object.entries(classData).map(([classId, data]) => ({
classId,
className: data.name,
average: data.count > 0 ? Math.round((data.total / data.count) * 100) / 100 : 0,
count: data.count,
})),
}));
return NextResponse.json({
type: 'class',
classRanking,
subjectComparison,
total: grades.length,
});
}
if (compareType === 'subject') {
// Subject comparison
const subjectMap: Record<string, {
name: string;
grades: number[];
average: number;
variance: number;
gradeCount: number;
}> = {};
grades.forEach((g) => {
const val = g.overriddenValue ?? g.computedValue;
if (!subjectMap[g.subjectId]) {
subjectMap[g.subjectId] = {
name: g.subject.name,
grades: [],
average: 0,
variance: 0,
gradeCount: 0,
};
}
subjectMap[g.subjectId].grades.push(val);
});
Object.values(subjectMap).forEach((s) => {
s.gradeCount = s.grades.length;
s.average = s.grades.length > 0 ? Math.round((s.grades.reduce((sum, v) => sum + v, 0) / s.grades.length) * 100) / 100 : 0;
const variance = s.grades.length > 0
? s.grades.reduce((sum, v) => sum + Math.pow(v - s.average, 2), 0) / s.grades.length
: 0;
s.variance = Math.round(variance * 100) / 100;
});
// Cross-subject correlation
const subjectIds = Object.keys(subjectMap);
const studentSubjectGrades: Record<string, Record<string, number[]>> = {};
grades.forEach((g) => {
const val = g.overriddenValue ?? g.computedValue;
if (!studentSubjectGrades[g.studentId]) {
studentSubjectGrades[g.studentId] = {};
}
if (!studentSubjectGrades[g.studentId][g.subjectId]) {
studentSubjectGrades[g.studentId][g.subjectId] = [];
}
studentSubjectGrades[g.studentId][g.subjectId].push(val);
});
// Calculate correlation matrix
const correlationMatrix: Array<{
subject1: string;
subject2: string;
name1: string;
name2: string;
correlation: number;
}> = [];
for (let i = 0; i < subjectIds.length; i++) {
for (let j = i; j < subjectIds.length; j++) {
const s1 = subjectIds[i];
const s2 = subjectIds[j];
const pairs: Array<{ x: number; y: number }> = [];
Object.entries(studentSubjectGrades).forEach(([, subjects]) => {
const g1 = subjects[s1];
const g2 = subjects[s2];
if (g1 && g2 && g1.length > 0 && g2.length > 0) {
pairs.push({
x: g1.reduce((a, b) => a + b, 0) / g1.length,
y: g2.reduce((a, b) => a + b, 0) / g2.length,
});
}
});
if (pairs.length >= 3) {
const n = pairs.length;
const sumX = pairs.reduce((s, p) => s + p.x, 0);
const sumY = pairs.reduce((s, p) => s + p.y, 0);
const sumXY = pairs.reduce((s, p) => s + p.x * p.y, 0);
const sumX2 = pairs.reduce((s, p) => s + p.x * p.x, 0);
const sumY2 = pairs.reduce((s, p) => s + p.y * p.y, 0);
const numerator = n * sumXY - sumX * sumY;
const denominator = Math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
const correlation = denominator !== 0 ? Math.round((numerator / denominator) * 100) / 100 : 0;
correlationMatrix.push({
subject1: s1,
subject2: s2,
name1: subjectMap[s1].name,
name2: subjectMap[s2].name,
correlation,
});
}
}
}
// Subject difficulty ranking
const subjectRanking = Object.entries(subjectMap)
.map(([id, data]) => ({
id,
name: data.name,
average: data.average,
variance: data.variance,
gradeCount: data.gradeCount,
difficulty: data.average <= 2 ? 'easy' : data.average <= 3 ? 'moderate' : data.average <= 4 ? 'challenging' : 'hard',
}))
.sort((a, b) => b.average - a.average);
// Subject recommendations
const recommendations = subjectRanking.map((s) => {
const recs: string[] = [];
if (s.average >= 4.5) {
recs.push('Consider additional support materials and tutoring');
recs.push('Review curriculum difficulty and pacing');
} else if (s.average >= 3.5) {
recs.push('Monitor student progress closely');
recs.push('Consider differentiated instruction');
} else {
recs.push('Performance is on track');
recs.push('Continue current teaching approach');
}
if (s.variance > 1.5) {
recs.push('High variance suggests need for differentiated instruction');
}
return { subjectId: s.id, subjectName: s.name, recommendations: recs };
});
return NextResponse.json({
type: 'subject',
subjectRanking,
correlationMatrix,
recommendations,
total: grades.length,
});
}
if (compareType === 'teacher') {
// Teacher comparison (admin only)
if (!isAdmin) {
return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });
}
const teacherClasses = await db.classGroupTeacher.findMany({
where: schoolId ? { classGroup: { schoolId } } : {},
select: {
userId: true,
role: true,
user: { select: { id: true, firstName: true, lastName: true } },
classGroup: { select: { id: true, name: true } },
},
});
const teacherMap: Record<string, {
name: string;
classIds: string[];
grades: number[];
average: number;
classCount: number;
gradeCount: number;
}> = {};
teacherClasses.forEach((tc) => {
if (!teacherMap[tc.userId]) {
teacherMap[tc.userId] = {
name: `${tc.user.firstName} ${tc.user.lastName}`,
classIds: [],
grades: [],
average: 0,
classCount: 0,
gradeCount: 0,
};
}
if (!teacherMap[tc.userId].classIds.includes(tc.classGroup.id)) {
teacherMap[tc.userId].classIds.push(tc.classGroup.id);
}
});
// Assign grades to teachers based on class
grades.forEach((g) => {
Object.entries(teacherMap).forEach(([, teacher]) => {
if (teacher.classIds.includes(g.classGroupId)) {
teacher.grades.push(g.overriddenValue ?? g.computedValue);
}
});
});
// Calculate teacher stats
Object.values(teacherMap).forEach((t) => {
t.gradeCount = t.grades.length;
t.classCount = t.classIds.length;
t.average = t.grades.length > 0 ? Math.round((t.grades.reduce((s, v) => s + v, 0) / t.grades.length) * 100) / 100 : 0;
});
const teacherRanking = Object.entries(teacherMap)
.map(([id, data]) => ({ id, ...data }))
.sort((a, b) => a.average - b.average);
return NextResponse.json({
type: 'teacher',
teacherRanking,
total: grades.length,
});
}
return NextResponse.json({ error: 'Invalid compareType parameter' }, { status: 400 });
} catch (error) {
console.error('Grade comparison error:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
+184
View File
@@ -0,0 +1,184 @@
// @ts-nocheck
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
export async function GET(request: Request) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const schoolId = searchParams.get('schoolId') ?? session.user?.schoolId ?? undefined;
const classGroupId = searchParams.get('classGroupId');
const subjectId = searchParams.get('subjectId');
const schoolYearId = searchParams.get('schoolYearId');
const groupBy = searchParams.get('groupBy') ?? 'overall'; // overall, subject, class
const role = session.user?.role;
const isStudent = role === 'STUDENT';
const isParent = role === 'PARENT';
// Build where clause
const gradeWhere: Record<string, unknown> = {};
if (classGroupId) gradeWhere.classGroupId = classGroupId;
if (subjectId) gradeWhere.subjectId = subjectId;
if (schoolYearId) gradeWhere.schoolYearId = schoolYearId;
if (schoolId) {
gradeWhere.classGroup = { schoolId };
}
// Role-based scoping
if (isStudent && session.user?.id) {
const student = await db.student.findFirst({
where: { userId: session.user.id, deletedAt: null },
});
if (student) gradeWhere.studentId = student.id;
}
if (isParent && session.user?.id) {
const parentLinks = await db.parentStudentLink.findMany({
where: { parentId: session.user.id },
select: { studentId: true },
});
const studentIds = parentLinks.map((l) => l.studentId);
gradeWhere.studentId = { in: studentIds };
}
// Get all grades with subject and class info
const grades = await db.computedGrade.findMany({
where: gradeWhere,
select: {
computedValue: true,
overriddenValue: true,
subjectId: true,
classGroupId: true,
studentId: true,
subject: { select: { id: true, name: true } },
classGroup: { select: { id: true, name: true } },
},
});
// Grade colors for German 1-6 scale
const gradeColors: Record<number, string> = {
1: '#10b981', // emerald
2: '#22c55e', // green
3: '#eab308', // yellow
4: '#f59e0b', // amber
5: '#f97316', // orange
6: '#ef4444', // red
};
if (groupBy === 'overall') {
// Overall distribution
const distribution: Record<number, { count: number; percentage: number; color: string }> = {};
for (let i = 1; i <= 6; i++) {
distribution[i] = { count: 0, percentage: 0, color: gradeColors[i] };
}
grades.forEach((g) => {
const val = g.overriddenValue ?? g.computedValue;
const rounded = Math.round(val);
if (rounded >= 1 && rounded <= 6) {
distribution[rounded].count++;
}
});
const total = grades.length || 1;
Object.values(distribution).forEach((d) => {
d.percentage = Math.round((d.count / total) * 10000) / 100;
});
// Bell curve data (normal distribution overlay)
const mean = grades.reduce((s, g) => s + (g.overriddenValue ?? g.computedValue), 0) / (grades.length || 1);
const variance = grades.reduce((s, g) => {
const val = g.overriddenValue ?? g.computedValue;
return s + Math.pow(val - mean, 2);
}, 0) / (grades.length || 1);
const stdDev = Math.sqrt(variance);
const bellCurveData = [];
for (let x = 0.5; x <= 6.5; x += 0.1) {
const exponent = -Math.pow(x - mean, 2) / (2 * (stdDev * stdDev || 1));
const y = (1 / (stdDev * Math.sqrt(2 * Math.PI) || 1)) * Math.exp(exponent);
bellCurveData.push({ x: Math.round(x * 10) / 10, y: Math.round(y * 1000) / 1000 });
}
return NextResponse.json({
type: 'overall',
distribution: Object.entries(distribution).map(([grade, data]) => ({
grade: parseInt(grade),
...data,
label: grade === '1' ? 'Sehr gut' : grade === '2' ? 'Gut' : grade === '3' ? 'Befriedigend' : grade === '4' ? 'Ausreichend' : grade === '5' ? 'Mangelhaft' : 'Ungenügend',
})),
bellCurve: bellCurveData,
mean: Math.round(mean * 100) / 100,
stdDev: Math.round(stdDev * 100) / 100,
total: grades.length,
});
}
if (groupBy === 'subject') {
// Distribution by subject
const subjectMap: Record<string, { name: string; distribution: Record<number, number> }> = {};
grades.forEach((g) => {
const val = g.overriddenValue ?? g.computedValue;
const rounded = Math.round(val);
if (!subjectMap[g.subjectId]) {
subjectMap[g.subjectId] = { name: g.subject.name, distribution: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 } };
}
if (rounded >= 1 && rounded <= 6) {
subjectMap[g.subjectId].distribution[rounded]++;
}
});
const subjectDistribution = Object.entries(subjectMap).map(([id, data]) => ({
id,
name: data.name,
distribution: data.distribution,
}));
return NextResponse.json({
type: 'subject',
subjects: subjectDistribution,
gradeColors,
total: grades.length,
});
}
if (groupBy === 'class') {
// Distribution by class
const classMap: Record<string, { name: string; distribution: Record<number, number> }> = {};
grades.forEach((g) => {
const val = g.overriddenValue ?? g.computedValue;
const rounded = Math.round(val);
if (!classMap[g.classGroupId]) {
classMap[g.classGroupId] = { name: g.classGroup.name, distribution: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 } };
}
if (rounded >= 1 && rounded <= 6) {
classMap[g.classGroupId].distribution[rounded]++;
}
});
const classDistribution = Object.entries(classMap).map(([id, data]) => ({
id,
name: data.name,
distribution: data.distribution,
}));
return NextResponse.json({
type: 'class',
classes: classDistribution,
gradeColors,
total: grades.length,
});
}
return NextResponse.json({ error: 'Invalid groupBy parameter' }, { status: 400 });
} catch (error) {
console.error('Grade distribution error:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
// @ts-nocheck
+232
View File
@@ -0,0 +1,232 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
// GET: List grade reports
export async function GET(request: Request) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const schoolId = searchParams.get('schoolId') ?? session.user?.schoolId ?? undefined;
const type = searchParams.get('type');
const role = session.user?.role;
const isAdmin = role === 'SUPER_ADMIN' || role === 'SCHOOL_ADMIN' || role === 'VICE_PRINCIPAL';
const isTeacher = role === 'TEACHER';
const where: Record<string, unknown> = {};
if (schoolId) where.schoolId = schoolId;
if (type) where.type = type;
// Non-admin users can only see their own reports
if (!isAdmin && !isTeacher) {
where.generatedBy = session.userId;
}
const reports = await db.gradeReport.findMany({
where,
include: {
generator: {
select: { id: true, firstName: true, lastName: true },
},
},
orderBy: { createdAt: 'desc' },
});
return NextResponse.json({
reports: reports.map((r) => ({
id: r.id,
title: r.title,
type: r.type,
dateRange: r.dateRange,
classIds: r.classIds,
subjectIds: r.subjectIds,
metrics: r.metrics,
status: r.status,
generatedBy: r.generator ? `${r.generator.firstName} ${r.generator.lastName}` : 'Unknown',
createdAt: r.createdAt,
hasFileData: !!r.fileData,
})),
});
} catch (error) {
console.error('Grade reports list error:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
// POST: Generate a new grade report
export async function POST(request: Request) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
}
const role = session.user?.role;
const isAdmin = role === 'SUPER_ADMIN' || role === 'SCHOOL_ADMIN' || role === 'VICE_PRINCIPAL';
const isTeacher = role === 'TEACHER';
if (!isAdmin && !isTeacher) {
return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 });
}
const body = await request.json();
const {
title,
type,
dateRange,
classIds,
subjectIds,
metrics,
} = body;
if (!title || !type || !dateRange) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
const schoolId = body.schoolId ?? session.user?.schoolId;
if (!schoolId) {
return NextResponse.json({ error: 'School ID required' }, { status: 400 });
}
// Build the grade data for the report
const gradeWhere: Record<string, unknown> = {
classGroup: { schoolId },
};
const parsedDateRange = typeof dateRange === 'string' ? JSON.parse(dateRange) : dateRange;
if (parsedDateRange.from || parsedDateRange.to) {
if (parsedDateRange.from) {
gradeWhere.computedAt = { gte: new Date(parsedDateRange.from) };
}
if (parsedDateRange.to) {
gradeWhere.computedAt = { ...(gradeWhere.computedAt as object || {}), lte: new Date(parsedDateRange.to) };
}
}
const parsedClassIds = typeof classIds === 'string' ? JSON.parse(classIds) : classIds;
const parsedSubjectIds = typeof subjectIds === 'string' ? JSON.parse(subjectIds) : subjectIds;
const parsedMetrics = typeof metrics === 'string' ? JSON.parse(metrics) : metrics;
if (parsedClassIds && Array.isArray(parsedClassIds) && parsedClassIds.length > 0) {
gradeWhere.classGroupId = { in: parsedClassIds };
}
if (parsedSubjectIds && Array.isArray(parsedSubjectIds) && parsedSubjectIds.length > 0) {
gradeWhere.subjectId = { in: parsedSubjectIds };
}
// Fetch grades for report
const grades = await db.computedGrade.findMany({
where: gradeWhere,
include: {
student: { select: { id: true, firstName: true, lastName: true } },
subject: { select: { id: true, name: true } },
classGroup: { select: { id: true, name: true } },
},
orderBy: { computedAt: 'asc' },
});
// Generate report data
const reportData: Record<string, unknown> = {
generatedAt: new Date().toISOString(),
generatedBy: `${session.user?.firstName} ${session.user?.lastName}`,
title,
type,
dateRange: parsedDateRange,
totalGrades: grades.length,
};
// Include selected metrics
if (!parsedMetrics || parsedMetrics.includes('distribution')) {
const distribution: Record<number, number> = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 };
grades.forEach((g) => {
const val = g.overriddenValue ?? g.computedValue;
const rounded = Math.round(val);
if (rounded >= 1 && rounded <= 6) distribution[rounded]++;
});
reportData.distribution = distribution;
}
if (!parsedMetrics || parsedMetrics.includes('averages')) {
const avg = grades.length > 0
? Math.round((grades.reduce((s, g) => s + (g.overriddenValue ?? g.computedValue), 0) / grades.length) * 100) / 100
: 0;
reportData.averageGrade = avg;
}
if (!parsedMetrics || parsedMetrics.includes('classComparison')) {
const classAvg: Record<string, { name: string; average: number; count: number }> = {};
grades.forEach((g) => {
const val = g.overriddenValue ?? g.computedValue;
if (!classAvg[g.classGroupId]) {
classAvg[g.classGroupId] = { name: g.classGroup.name, average: 0, count: 0 };
}
classAvg[g.classGroupId].average += val;
classAvg[g.classGroupId].count += 1;
});
Object.values(classAvg).forEach((c) => {
c.average = c.count > 0 ? Math.round((c.average / c.count) * 100) / 100 : 0;
});
reportData.classComparison = classAvg;
}
if (!parsedMetrics || parsedMetrics.includes('subjectAnalysis')) {
const subjectAvg: Record<string, { name: string; average: number; count: number }> = {};
grades.forEach((g) => {
const val = g.overriddenValue ?? g.computedValue;
if (!subjectAvg[g.subjectId]) {
subjectAvg[g.subjectId] = { name: g.subject.name, average: 0, count: 0 };
}
subjectAvg[g.subjectId].average += val;
subjectAvg[g.subjectId].count += 1;
});
Object.values(subjectAvg).forEach((s) => {
s.average = s.count > 0 ? Math.round((s.average / s.count) * 100) / 100 : 0;
});
reportData.subjectAnalysis = subjectAvg;
}
if (!parsedMetrics || parsedMetrics.includes('studentGrades')) {
reportData.studentGrades = grades.map((g) => ({
student: `${g.student.firstName} ${g.student.lastName}`,
subject: g.subject.name,
class: g.classGroup.name,
grade: g.overriddenValue ?? g.computedValue,
isFinalized: g.isFinalized,
period: g.period,
}));
}
// Create the report record
const report = await db.gradeReport.create({
data: {
schoolId,
generatedBy: session.userId,
title,
type,
dateRange: typeof dateRange === 'string' ? dateRange : JSON.stringify(dateRange),
classIds: classIds ? (typeof classIds === 'string' ? classIds : JSON.stringify(classIds)) : null,
subjectIds: subjectIds ? (typeof subjectIds === 'string' ? subjectIds : JSON.stringify(subjectIds)) : null,
metrics: metrics ? (typeof metrics === 'string' ? metrics : JSON.stringify(metrics)) : null,
status: 'completed',
fileData: JSON.stringify(reportData),
},
});
return NextResponse.json({
id: report.id,
title: report.title,
type: report.type,
status: report.status,
createdAt: report.createdAt,
reportData,
});
} catch (error) {
console.error('Grade report generation error:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
+250
View File
@@ -0,0 +1,250 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
export async function GET(request: Request) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const schoolId = searchParams.get('schoolId') ?? session.user?.schoolId ?? undefined;
const classGroupId = searchParams.get('classGroupId');
const subjectId = searchParams.get('subjectId');
const schoolYearId = searchParams.get('schoolYearId');
const role = session.user?.role;
const isStudent = role === 'STUDENT';
const isParent = role === 'PARENT';
// Build where clause for ComputedGrade
const gradeWhere: Record<string, unknown> = {};
if (classGroupId) gradeWhere.classGroupId = classGroupId;
if (subjectId) gradeWhere.subjectId = subjectId;
if (schoolYearId) gradeWhere.schoolYearId = schoolYearId;
// Scope by school
if (schoolId) {
const classWhere: Record<string, unknown> = { schoolId };
gradeWhere.classGroup = { schoolId };
if (Object.keys(classWhere).length > 0) {
gradeWhere.classGroup = classWhere;
}
}
// Role-based scoping
if (isStudent && session.user?.id) {
const student = await db.student.findFirst({
where: { userId: session.user.id, deletedAt: null },
});
if (student) {
gradeWhere.studentId = student.id;
}
}
if (isParent && session.user?.id) {
const parentLinks = await db.parentStudentLink.findMany({
where: { parentId: session.user.id },
select: { studentId: true },
});
const studentIds = parentLinks.map((l) => l.studentId);
gradeWhere.studentId = { in: studentIds };
}
// Get total grades count
const totalGrades = await db.computedGrade.count({ where: gradeWhere });
// Get average grade
const gradeAgg = await db.computedGrade.aggregate({
where: gradeWhere,
_avg: { computedValue: true },
_min: { computedValue: true },
_max: { computedValue: true },
});
// Get grade distribution (1-6 scale)
const allGrades = await db.computedGrade.findMany({
where: gradeWhere,
select: {
computedValue: true,
overriddenValue: true,
subjectId: true,
classGroupId: true,
studentId: true,
period: true,
isFinalized: true,
},
});
// Grade distribution
const distribution: Record<number, number> = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 };
allGrades.forEach((g) => {
const val = g.overriddenValue ?? g.computedValue;
const rounded = Math.round(val);
if (rounded >= 1 && rounded <= 6) {
distribution[rounded]++;
}
});
// Finalized vs draft count
const finalizedCount = allGrades.filter((g) => g.isFinalized).length;
const draftCount = totalGrades - finalizedCount;
// Top performers (lowest grade = best on German scale)
const studentGradeMap: Record<string, { total: number; count: number; name: string }> = {};
for (const g of allGrades) {
const val = g.overriddenValue ?? g.computedValue;
if (!studentGradeMap[g.studentId]) {
studentGradeMap[g.studentId] = { total: 0, count: 0, name: '' };
}
studentGradeMap[g.studentId].total += val;
studentGradeMap[g.studentId].count += 1;
}
// Get student names
const studentIds = Object.keys(studentGradeMap);
const students = await db.student.findMany({
where: { id: { in: studentIds }, deletedAt: null },
select: { id: true, firstName: true, lastName: true },
});
students.forEach((s) => {
if (studentGradeMap[s.id]) {
studentGradeMap[s.id].name = `${s.firstName} ${s.lastName}`;
}
});
// Calculate averages and sort
const studentAverages = studentIds
.map((id) => ({
id,
name: studentGradeMap[id].name,
average: studentGradeMap[id].count > 0 ? studentGradeMap[id].total / studentGradeMap[id].count : 0,
gradeCount: studentGradeMap[id].count,
}))
.sort((a, b) => a.average - b.average); // Lower = better on German scale
const topPerformers = studentAverages.slice(0, 5);
const bottomPerformers = studentAverages.slice(-5).reverse();
// Risk students (average >= 4.5, trending towards 5-6)
const riskStudents = studentAverages.filter((s) => s.average >= 4.5 && s.gradeCount >= 2);
// Get classes for filter
const classes = await db.classGroup.findMany({
where: schoolId ? { schoolId } : {},
select: { id: true, name: true, gradeLevel: true },
orderBy: { gradeLevel: 'asc' },
});
// Get subjects for filter
const subjects = await db.subject.findMany({
where: schoolId ? { schoolId } : {},
select: { id: true, name: true },
orderBy: { name: 'asc' },
});
// Subject difficulty ranking
const subjectGradeMap: Record<string, { total: number; count: number; name: string }> = {};
for (const g of allGrades) {
const val = g.overriddenValue ?? g.computedValue;
if (!subjectGradeMap[g.subjectId]) {
subjectGradeMap[g.subjectId] = { total: 0, count: 0, name: '' };
}
subjectGradeMap[g.subjectId].total += val;
subjectGradeMap[g.subjectId].count += 1;
}
subjects.forEach((s) => {
if (subjectGradeMap[s.id]) {
subjectGradeMap[s.id].name = s.name;
}
});
const subjectDifficulty = Object.entries(subjectGradeMap)
.map(([id, data]) => ({
id,
name: data.name,
average: data.count > 0 ? data.total / data.count : 0,
gradeCount: data.count,
}))
.sort((a, b) => b.average - a.average); // Higher average = harder
// Class average comparison
const classGradeMap: Record<string, { total: number; count: number; name: string }> = {};
for (const g of allGrades) {
const val = g.overriddenValue ?? g.computedValue;
if (!classGradeMap[g.classGroupId]) {
classGradeMap[g.classGroupId] = { total: 0, count: 0, name: '' };
}
classGradeMap[g.classGroupId].total += val;
classGradeMap[g.classGroupId].count += 1;
}
classes.forEach((c) => {
if (classGradeMap[c.id]) {
classGradeMap[c.id].name = c.name;
}
});
const classAverages = Object.entries(classGradeMap)
.map(([id, data]) => ({
id,
name: data.name,
average: data.count > 0 ? data.total / data.count : 0,
gradeCount: data.count,
}))
.sort((a, b) => a.average - b.average);
// Teacher comparison (admin only)
let teacherComparison: Array<{ id: string; name: string; average: number; gradeCount: number }> = [];
if (role === 'SUPER_ADMIN' || role === 'SCHOOL_ADMIN' || role === 'VICE_PRINCIPAL') {
const teacherIds = await db.classGroupTeacher.findMany({
where: schoolId ? { classGroup: { schoolId } } : {},
select: { userId: true, user: { select: { id: true, firstName: true, lastName: true } } },
distinct: ['userId'],
});
for (const t of teacherIds) {
const teacherClasses = await db.classGroupTeacher.findMany({
where: { userId: t.userId },
select: { classGroupId: true },
});
const tClassIds = teacherClasses.map((tc) => tc.classGroupId);
const tGrades = allGrades.filter((g) => tClassIds.includes(g.classGroupId));
if (tGrades.length > 0) {
const avg = tGrades.reduce((sum, g) => sum + (g.overriddenValue ?? g.computedValue), 0) / tGrades.length;
teacherComparison.push({
id: t.user.id,
name: `${t.user.firstName} ${t.user.lastName}`,
average: Math.round(avg * 100) / 100,
gradeCount: tGrades.length,
});
}
}
teacherComparison.sort((a, b) => a.average - b.average);
}
return NextResponse.json({
totalGrades,
averageGrade: gradeAgg._avg.computedValue ? Math.round(gradeAgg._avg.computedValue * 100) / 100 : 0,
minGrade: gradeAgg._min.computedValue ?? 0,
maxGrade: gradeAgg._max.computedValue ?? 0,
finalizedCount,
draftCount,
distribution,
topPerformers,
bottomPerformers,
riskStudents,
classes,
subjects,
subjectDifficulty,
classAverages,
teacherComparison,
});
} catch (error) {
console.error('Grade analytics error:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
+215
View File
@@ -0,0 +1,215 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
export async function GET(request: Request) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const schoolId = searchParams.get('schoolId') ?? session.user?.schoolId ?? undefined;
const classGroupId = searchParams.get('classGroupId');
const subjectId = searchParams.get('subjectId');
const schoolYearId = searchParams.get('schoolYearId');
const studentId = searchParams.get('studentId');
const role = session.user?.role;
const isStudent = role === 'STUDENT';
const isParent = role === 'PARENT';
// Build where clause for assessments
const assessmentWhere: Record<string, unknown> = {};
if (classGroupId) assessmentWhere.classGroupId = classGroupId;
if (subjectId) assessmentWhere.subjectId = subjectId;
if (schoolId) {
assessmentWhere.classGroup = { schoolId };
}
// Get assessments with results over time
const assessments = await db.assessment.findMany({
where: assessmentWhere,
select: {
id: true,
title: true,
date: true,
type: true,
subjectId: true,
classGroupId: true,
maxScore: true,
subject: { select: { id: true, name: true } },
classGroup: { select: { id: true, name: true } },
assessmentResults: {
select: {
studentId: true,
score: true,
masteryLevelValue: true,
student: {
select: { id: true, firstName: true, lastName: true },
},
},
...(studentId ? { where: { studentId } } : {}),
},
},
orderBy: { date: 'asc' },
});
// Role-based scoping for student/parent
let filteredAssessments = assessments;
if (isStudent && session.user?.id) {
const student = await db.student.findFirst({
where: { userId: session.user.id, deletedAt: null },
});
if (student) {
filteredAssessments = assessments.map((a) => ({
...a,
assessmentResults: a.assessmentResults.filter((r) => r.studentId === student.id),
}));
}
}
if (isParent && session.user?.id) {
const parentLinks = await db.parentStudentLink.findMany({
where: { parentId: session.user.id },
select: { studentId: true },
});
const childIds = parentLinks.map((l) => l.studentId);
filteredAssessments = assessments.map((a) => ({
...a,
assessmentResults: a.assessmentResults.filter((r) => childIds.includes(r.studentId)),
}));
}
// Build trend data by month
const monthMap: Record<string, { date: string; average: number; count: number; grade1: number; grade2: number; grade3: number; grade4: number; grade5: number; grade6: number }> = {};
filteredAssessments.forEach((a) => {
const monthKey = a.date.toISOString().substring(0, 7); // YYYY-MM
if (!monthMap[monthKey]) {
monthMap[monthKey] = { date: monthKey, average: 0, count: 0, grade1: 0, grade2: 0, grade3: 0, grade4: 0, grade5: 0, grade6: 0 };
}
a.assessmentResults.forEach((r) => {
if (r.score !== null && a.maxScore) {
// Convert score to German grade (1-6 scale)
const percentage = r.score / a.maxScore;
let grade: number;
if (percentage >= 0.92) grade = 1;
else if (percentage >= 0.81) grade = 2;
else if (percentage >= 0.67) grade = 3;
else if (percentage >= 0.50) grade = 4;
else if (percentage >= 0.30) grade = 5;
else grade = 6;
monthMap[monthKey].count++;
monthMap[monthKey].average += grade;
if (grade === 1) monthMap[monthKey].grade1++;
else if (grade === 2) monthMap[monthKey].grade2++;
else if (grade === 3) monthMap[monthKey].grade3++;
else if (grade === 4) monthMap[monthKey].grade4++;
else if (grade === 5) monthMap[monthKey].grade5++;
else monthMap[monthKey].grade6++;
}
});
});
// Calculate averages
Object.values(monthMap).forEach((m) => {
m.average = m.count > 0 ? Math.round((m.average / m.count) * 100) / 100 : 0;
});
const trendData = Object.values(monthMap).sort((a, b) => a.date.localeCompare(b.date));
// Student trajectory data
const studentTrajectories: Record<string, { name: string; data: Array<{ date: string; grade: number }> }> = {};
filteredAssessments.forEach((a) => {
const monthKey = a.date.toISOString().substring(0, 7);
a.assessmentResults.forEach((r) => {
if (r.score !== null && a.maxScore) {
const percentage = r.score / a.maxScore;
let grade: number;
if (percentage >= 0.92) grade = 1;
else if (percentage >= 0.81) grade = 2;
else if (percentage >= 0.67) grade = 3;
else if (percentage >= 0.50) grade = 4;
else if (percentage >= 0.30) grade = 5;
else grade = 6;
if (!studentTrajectories[r.studentId]) {
studentTrajectories[r.studentId] = {
name: `${r.student.firstName} ${r.student.lastName}`,
data: [],
};
}
// Check if we already have data for this month
const existing = studentTrajectories[r.studentId].data.find((d) => d.date === monthKey);
if (!existing) {
studentTrajectories[r.studentId].data.push({ date: monthKey, grade });
}
}
});
});
// Sort trajectory data by date
Object.values(studentTrajectories).forEach((t) => {
t.data.sort((a, b) => a.date.localeCompare(b.date));
});
// Improvement/regression detection
const studentImprovements: Array<{
studentId: string;
name: string;
trend: 'improving' | 'stable' | 'regressing';
firstGrade: number;
lastGrade: number;
change: number;
}> = [];
Object.entries(studentTrajectories).forEach(([id, data]) => {
if (data.data.length >= 2) {
const firstGrade = data.data[0].grade;
const lastGrade = data.data[data.data.length - 1].grade;
const change = firstGrade - lastGrade; // Negative = regressing (grade went up), Positive = improving
let trend: 'improving' | 'stable' | 'regressing' = 'stable';
if (change >= 0.5) trend = 'improving';
else if (change <= -0.5) trend = 'regressing';
studentImprovements.push({
studentId: id,
name: data.name,
trend,
firstGrade,
lastGrade,
change: Math.round(change * 100) / 100,
});
}
});
// Percentile calculation
const allStudentAvgs = Object.entries(studentTrajectories).map(([id, data]) => {
const avg = data.data.length > 0 ? data.data.reduce((s, d) => s + d.grade, 0) / data.data.length : 0;
return { id, name: data.name, average: avg };
}).sort((a, b) => a.average - b.average);
const percentileData = allStudentAvgs.map((s, idx) => ({
...s,
percentile: Math.round((idx / Math.max(allStudentAvgs.length - 1, 1)) * 100),
}));
return NextResponse.json({
trendData,
studentTrajectories: Object.entries(studentTrajectories).map(([id, data]) => ({
id,
...data,
})),
studentImprovements,
percentileData,
totalAssessments: filteredAssessments.length,
});
} catch (error) {
console.error('Grade trends error:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.inventoryItem, {
searchFields: ['name', 'category'],
defaultOrderBy: {"name":"asc"},
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.inventoryItem, {
searchFields: ['name', 'category'],
defaultOrderBy: {"name":"asc"},
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.invoice, {
searchFields: ['invoiceNo', 'feeType'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.invoice, {
searchFields: ['invoiceNo', 'feeType'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.leave, {
searchFields: ['reason', 'leaveType'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.leave, {
searchFields: ['reason', 'leaveType'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.mark, {
searchFields: [],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.mark, {
searchFields: [],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+14
View File
@@ -0,0 +1,14 @@
import { NextResponse } from 'next/server'
// Mock current user - in a real app this would come from session/auth
// We expose a role switcher so the user can experience different panels
export async function GET() {
return NextResponse.json({
id: 'usr-admin',
name: 'System Administrator',
email: 'admin@school.edu.tr',
role: 'ADMIN',
avatar: null,
phone: '+90 555 000 0001',
})
}
View File
File mode changed.
View File
File mode changed.
+12
View File
@@ -0,0 +1,12 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.notebookPage, {
searchFields: ['title', 'content'],
defaultOrderBy: { pageNumber: 'asc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.notebookPage, {
searchFields: ['title', 'content'],
defaultOrderBy: { pageNumber: 'asc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.notice, {
searchFields: ['title', 'content'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.notice, {
searchFields: ['title', 'content'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.onlineExam, {
searchFields: ['title'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.onlineExam, {
searchFields: ['title'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.payroll, {
searchFields: ['month'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.payroll, {
searchFields: ['month'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.admissionQuery, {
searchFields: ['name', 'phone', 'classApplied'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.admissionQuery, {
searchFields: ['name', 'phone', 'classApplied'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.questionBank, {
searchFields: ['question'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.questionBank, {
searchFields: ['question'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.role, {
searchFields: ['name', 'description'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.role, {
searchFields: ['name', 'description'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.route, {
searchFields: ['name', 'startPoint', 'endPoint'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.route, {
searchFields: ['name', 'startPoint', 'endPoint'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.classRoutine, {
searchFields: [],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.classRoutine, {
searchFields: [],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.section, {
searchFields: ['name'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.section, {
searchFields: ['name'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+29
View File
@@ -0,0 +1,29 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
export async function GET() {
try {
const settings = await db.setting.findMany()
const obj: Record<string, string> = {}
for (const s of settings) obj[s.id] = s.value
return NextResponse.json(obj)
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
export async function POST(req: Request) {
try {
const body = await req.json()
for (const [key, value] of Object.entries(body)) {
await db.setting.upsert({
where: { id: key },
update: { value: String(value) },
create: { id: key, value: String(value) },
})
}
return NextResponse.json({ success: true })
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 })
}
}
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.staffAttendance, {
searchFields: [],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.staffAttendance, {
searchFields: [],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.staff, {
searchFields: ['firstName', 'lastName', 'staffId', 'phone', 'email', 'department'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.staff, {
searchFields: ['firstName', 'lastName', 'staffId', 'phone', 'email', 'department'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
View File
File mode changed.
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.student, {
searchFields: ['firstName', 'lastName', 'admissionNo', 'phone', 'email'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.studyMaterial, {
searchFields: ['title', 'type'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.studyMaterial, {
searchFields: ['title', 'type'],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.subject, {
searchFields: ['name', 'code'],
defaultOrderBy: {"name":"asc"},
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
View File
File mode changed.
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.transaction, {
searchFields: ['category', 'description'],
defaultOrderBy: {"date":"desc"},
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.transaction, {
searchFields: ['category', 'description'],
defaultOrderBy: {"date":"desc"},
})
export const GET = handler.list
export const POST = handler.create
+12
View File
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.transportAssignment, {
searchFields: [],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.get
export const PUT = handler.update
export const DELETE = handler.remove
+11
View File
@@ -0,0 +1,11 @@
import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { createCrudHandler } from '@/lib/api-helpers'
const handler = createCrudHandler(db.transportAssignment, {
searchFields: [],
defaultOrderBy: { createdAt: 'desc' },
})
export const GET = handler.list
export const POST = handler.create
View File
File mode changed.
Loaded 100 of 300 files, more files were not shown because too many files have changed in this diff. Show more