Round 26: Grade Analytics, School Transport, Peer Assessment, Styling

- Grade Analytics & Reporting View: distribution histogram, student trajectory, class comparison, subject analysis, report generation
- School Transport & Bus Routes View: route management, student assignment, transport schedule, stop management
- Peer Assessment View: creation wizard, rating sliders, results radar chart, self vs peer comparison
- 3 new Prisma models: GradeReport, TransportRoute, TransportStop
- Extended PeerAssessment and StudentTransport models
- Enhanced classes-view with gradient header, glassmorphism cards
- Enhanced attendance-view with attendance rate gauge, status indicators
- Fixed duplicate variable name bugs in school-transport-view.tsx
- 12+ new API route files
- 300+ i18n keys added
- All views verified working in browser
This commit is contained in:
Mustafa Cetin committed 2026-07-30 07:20:05 +00:00
1 parent 3d0d518bb9
commit 7f0810c88f
28 files changed
+10264 -127

No files matched your search

@@ -19,6 +19,9 @@ export async function GET(
student: {
select: { id: true, firstName: true, lastName: true },
},
route: {
select: { id: true, routeNumber: true, routeName: true, transportType: true },
},
},
});
@@ -70,6 +73,7 @@ export async function PUT(
driverName,
driverPhone,
distanceKm,
routeId,
notes,
} = body;
@@ -84,12 +88,16 @@ export async function PUT(
...(driverName !== undefined && { driverName: driverName || null }),
...(driverPhone !== undefined && { driverPhone: driverPhone || null }),
...(distanceKm !== undefined && { distanceKm: distanceKm != null ? distanceKm : null }),
...(routeId !== undefined && { routeId: routeId || null }),
...(notes !== undefined && { notes }),
},
include: {
student: {
select: { id: true, firstName: true, lastName: true },
},
route: {
select: { id: true, routeNumber: true, routeName: true, transportType: true },
},
},
});
@@ -0,0 +1,80 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
// Bulk assign transport to students in a class group
export async function POST(request: Request) {
try {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
}
const body = await request.json();
const { schoolId, studentIds, transportType, routeId, pickupTime, dropoffTime, stopName } = body;
if (!schoolId || !studentIds || !Array.isArray(studentIds) || studentIds.length === 0 || !transportType) {
return NextResponse.json(
{ error: 'schoolId, studentIds (array), and transportType are required' },
{ status: 400 }
);
}
const adminRoles = ['SUPER_ADMIN', 'SCHOOL_ADMIN', 'VICE_PRINCIPAL'];
if (!adminRoles.includes(session.user?.role ?? '')) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Create or update transport for each student
const results = [];
for (const studentId of studentIds) {
const existing = await db.studentTransport.findFirst({
where: { schoolId, studentId, deletedAt: null },
});
if (existing) {
const updated = await db.studentTransport.update({
where: { id: existing.id },
data: {
transportType,
routeId: routeId || null,
pickupTime: pickupTime || null,
dropoffTime: dropoffTime || null,
stopName: stopName || null,
},
include: {
student: { select: { id: true, firstName: true, lastName: true } },
route: { select: { id: true, routeNumber: true, routeName: true } },
},
});
results.push(updated);
} else {
const created = await db.studentTransport.create({
data: {
schoolId,
studentId,
transportType,
routeId: routeId || null,
pickupTime: pickupTime || null,
dropoffTime: dropoffTime || null,
stopName: stopName || null,
},
include: {
student: { select: { id: true, firstName: true, lastName: true } },
route: { select: { id: true, routeNumber: true, routeName: true } },
},
});
results.push(created);
}
}
return NextResponse.json({ count: results.length, assignments: results }, { status: 201 });
} catch (error) {
console.error('StudentTransport bulk POST error:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
+8
View File
@@ -35,6 +35,9 @@ export async function GET(request: Request) {
student: {
select: { id: true, firstName: true, lastName: true },
},
route: {
select: { id: true, routeNumber: true, routeName: true, transportType: true },
},
},
});
@@ -64,6 +67,7 @@ export async function POST(request: Request) {
driverName,
driverPhone,
distanceKm,
routeId,
notes,
isDemo,
} = body;
@@ -91,6 +95,7 @@ export async function POST(request: Request) {
driverName: driverName || null,
driverPhone: driverPhone || null,
distanceKm: distanceKm != null ? distanceKm : null,
routeId: routeId || null,
notes: notes || null,
isDemo: isDemo ?? false,
},
@@ -98,6 +103,9 @@ export async function POST(request: Request) {
student: {
select: { id: true, firstName: true, lastName: true },
},
route: {
select: { id: true, routeNumber: true, routeName: true, transportType: true },
},
},
});