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:
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 });
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user