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:
1 parent
ecf083b0a7
commit
9ce58565ba
300 files changed
+16902
-121
No files matched your search
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Executable
+33
@@ -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 })
|
||||
}
|
||||
}
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
@@ -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
@@ -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
|
||||
Executable
+232
@@ -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 });
|
||||
}
|
||||
}
|
||||
Executable
+250
@@ -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 });
|
||||
}
|
||||
}
|
||||
Executable
+215
@@ -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 });
|
||||
}
|
||||
}
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+14
@@ -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',
|
||||
})
|
||||
}
|
||||
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+29
@@ -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 })
|
||||
}
|
||||
}
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Regular → Executable
File mode changed.
Executable
+12
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Executable
+11
@@ -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
|
||||
Executable
+12
@@ -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
|
||||
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Regular → Executable
File mode changed.
Loaded 100 of 300 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user