Round 20: Student Portal, District Management, userId field, styling enhancements, bug fixes
- Added userId field to Student model with User relation - Created Student Portal view with 6 tabs (competencies, goals, achievements, schedule, homework, feedback) - Created StudentGoal model + CRUD API with role-based access - Created District Management view with CRUD, analytics, school assignment - Enhanced auth page: animated gradient, floating particles, glassmorphism, staggered entrance - Enhanced dashboard: time-based greeting, animated stat cards, class overview, school branding - Enhanced student portal: hero section, circular progress, streak counter, celebration particles - Fixed student-goals API 403 for STUDENT role (name-matching fallback for legacy data) - Added userId filter to students API with OR fallback - Added nav.student-portal i18n key - 100+ new i18n keys in DE and EN - All lint checks pass
This commit is contained in:
1 parent
9680cc9244
commit
04ae7aa80c
23 files changed
+4624
-2868
No files matched your search
@@ -58,3 +58,38 @@ export async function POST(
|
||||
return NextResponse.json({ error: 'Failed to assign school to district' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/districts/[id]/schools — remove school from district
|
||||
export async function DELETE(
|
||||
_req: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
try {
|
||||
const { id } = await params;
|
||||
const { searchParams } = new URL(_req.url);
|
||||
const schoolId = searchParams.get('schoolId');
|
||||
|
||||
if (!schoolId) {
|
||||
return NextResponse.json({ error: 'schoolId is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Verify the school belongs to this district
|
||||
const school = await db.school.findFirst({
|
||||
where: { id: schoolId, districtId: id },
|
||||
});
|
||||
|
||||
if (!school) {
|
||||
return NextResponse.json({ error: 'School not found in this district' }, { status: 404 });
|
||||
}
|
||||
|
||||
await db.school.update({
|
||||
where: { id: schoolId },
|
||||
data: { districtId: null },
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('[districts/[id]/schools] DELETE error:', error);
|
||||
return NextResponse.json({ error: 'Failed to remove school from district' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,15 @@ export async function GET() {
|
||||
where: { deletedAt: null },
|
||||
include: {
|
||||
schools: {
|
||||
select: { id: true, name: true, schoolType: true, country: true },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
schoolType: true,
|
||||
country: true,
|
||||
_count: {
|
||||
select: { students: true, classGroups: true, users: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { name: 'asc' },
|
||||
@@ -18,7 +26,9 @@ export async function GET() {
|
||||
const result = districts.map((d) => ({
|
||||
...d,
|
||||
schoolCount: d.schools.length,
|
||||
totalStudents: 0, // Would need aggregation query in production
|
||||
totalStudents: d.schools.reduce((acc, s) => acc + s._count.students, 0),
|
||||
totalTeachers: d.schools.reduce((acc, s) => acc + s._count.users, 0),
|
||||
totalClasses: d.schools.reduce((acc, s) => acc + s._count.classGroups, 0),
|
||||
}));
|
||||
|
||||
return NextResponse.json(result);
|
||||
|
||||
Reference in new issue
Block a user