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:
Mustafa Cetin committed 2026-07-29 18:45:41 +00:00
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 });
}
}
+12 -2
View File
@@ -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);