Round 23: Report Card Generator, Seating Chart, School Library, Parent Portal Enhancement
- Report Card Generator: Professional report cards with PDF export - Subject grades table with competency levels - Competency radar chart, teacher comments - Template management, batch generation - Status workflow: draft → review → published → archived - Seating Chart View: Visual classroom layout - Grid-based classroom with desk positions - Multiple layout templates (rows, groups, U-shape, circle) - Click-to-assign student placement - Randomize and smart arrange features - Student info popover - School Library View: Book lending system - Book catalog with search and filter - Checkout system with due dates and renewals - Overdue tracking with notifications - Reservation queue system - Statistics dashboard with charts - Enhanced Parent Portal: Comprehensive dashboard - Child overview with multiple children selector - Academic progress with competency radar chart - Attendance & illness with approval - Communication, schedule, and reports tabs - Bug fixes: i18n breadcrumb keys for new views - Styling: Gradient headers, animations, hover effects across all new views - 200+ i18n keys added, 15+ API routes, 3+ Prisma models
This commit is contained in:
1 parent
490bd0650f
commit
6e5af13778
28 files changed
+10252
-494
No files matched your search
@@ -0,0 +1,233 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { getSession } from '@/lib/auth';
|
||||
|
||||
// ── GET: Get a single seating chart ──────────────────────────────────────────
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const chart = await db.seatingChart.findFirst({
|
||||
where: { id },
|
||||
include: {
|
||||
classGroup: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
gradeLevel: true,
|
||||
schoolId: true,
|
||||
},
|
||||
},
|
||||
teacher: { select: { id: true, firstName: true, lastName: true } },
|
||||
},
|
||||
});
|
||||
|
||||
if (!chart) {
|
||||
return NextResponse.json({ error: 'Seating chart not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Access check
|
||||
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== chart.schoolId) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
|
||||
// Get enrolled students for the class
|
||||
const enrollments = await db.enrollment.findMany({
|
||||
where: { classGroupId: chart.classGroupId, endDate: null },
|
||||
include: {
|
||||
student: {
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
avatarUrl: true,
|
||||
avatarInitials: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const students = enrollments.map((e) => e.student);
|
||||
|
||||
// Get behavior data for smart arrange
|
||||
const behaviorIncidents = await db.behaviorIncident.findMany({
|
||||
where: {
|
||||
studentId: { in: students.map((s) => s.id) },
|
||||
severity: { in: ['major', 'severe'] },
|
||||
},
|
||||
select: { studentId: true, severity: true },
|
||||
});
|
||||
|
||||
const behaviorMap = new Map<string, number>();
|
||||
for (const incident of behaviorIncidents) {
|
||||
const weight = incident.severity === 'severe' ? 3 : 1;
|
||||
behaviorMap.set(incident.studentId, (behaviorMap.get(incident.studentId) || 0) + weight);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
...chart,
|
||||
arrangement: chart.arrangement ? JSON.parse(chart.arrangement) : [],
|
||||
students: students.map((s) => ({
|
||||
...s,
|
||||
behaviorScore: behaviorMap.get(s.id) || 0,
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('SeatingChart GET error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ── PUT: Update a seating chart ──────────────────────────────────────────
|
||||
export async function PUT(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const userRole = session.user?.role;
|
||||
if (
|
||||
userRole !== 'TEACHER' &&
|
||||
userRole !== 'SCHOOL_ADMIN' &&
|
||||
userRole !== 'VICE_PRINCIPAL' &&
|
||||
userRole !== 'SUPER_ADMIN'
|
||||
) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const chart = await db.seatingChart.findFirst({ where: { id } });
|
||||
|
||||
if (!chart) {
|
||||
return NextResponse.json({ error: 'Seating chart not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Only the creator or admin can edit
|
||||
if (
|
||||
userRole === 'TEACHER' &&
|
||||
chart.teacherId !== session.userId
|
||||
) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const {
|
||||
name,
|
||||
layoutType,
|
||||
rows,
|
||||
columns,
|
||||
gap,
|
||||
arrangement,
|
||||
showDoor,
|
||||
showWindows,
|
||||
doorPosition,
|
||||
windowPosition,
|
||||
isTemplate,
|
||||
isDefault,
|
||||
notes,
|
||||
} = body;
|
||||
|
||||
// If isDefault is being set to true, unset any existing default for this class
|
||||
if (isDefault) {
|
||||
await db.seatingChart.updateMany({
|
||||
where: { classGroupId: chart.classGroupId, isDefault: true },
|
||||
data: { isDefault: false },
|
||||
});
|
||||
}
|
||||
|
||||
const updated = await db.seatingChart.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...(name !== undefined && { name }),
|
||||
...(layoutType !== undefined && { layoutType }),
|
||||
...(rows !== undefined && { rows }),
|
||||
...(columns !== undefined && { columns }),
|
||||
...(gap !== undefined && { gap }),
|
||||
...(arrangement !== undefined && { arrangement: JSON.stringify(arrangement) }),
|
||||
...(showDoor !== undefined && { showDoor }),
|
||||
...(showWindows !== undefined && { showWindows }),
|
||||
...(doorPosition !== undefined && { doorPosition }),
|
||||
...(windowPosition !== undefined && { windowPosition }),
|
||||
...(isTemplate !== undefined && { isTemplate }),
|
||||
...(isDefault !== undefined && { isDefault }),
|
||||
...(notes !== undefined && { notes }),
|
||||
},
|
||||
include: {
|
||||
classGroup: { select: { id: true, name: true, gradeLevel: true } },
|
||||
teacher: { select: { id: true, firstName: true, lastName: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// Also update the ClassGroup's seatingOrder for backward compatibility
|
||||
if (arrangement && Array.isArray(arrangement)) {
|
||||
await db.classGroup.update({
|
||||
where: { id: chart.classGroupId },
|
||||
data: { seatingOrder: JSON.stringify(arrangement) },
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
...updated,
|
||||
arrangement: updated.arrangement ? JSON.parse(updated.arrangement) : [],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('SeatingChart PUT error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ── DELETE: Delete a seating chart ──────────────────────────────────────────
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const userRole = session.user?.role;
|
||||
if (
|
||||
userRole !== 'TEACHER' &&
|
||||
userRole !== 'SCHOOL_ADMIN' &&
|
||||
userRole !== 'VICE_PRINCIPAL' &&
|
||||
userRole !== 'SUPER_ADMIN'
|
||||
) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const chart = await db.seatingChart.findFirst({ where: { id } });
|
||||
|
||||
if (!chart) {
|
||||
return NextResponse.json({ error: 'Seating chart not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Only the creator or admin can delete
|
||||
if (
|
||||
userRole === 'TEACHER' &&
|
||||
chart.teacherId !== session.userId
|
||||
) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
|
||||
await db.seatingChart.delete({ where: { id } });
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('SeatingChart DELETE error:', error);
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user