Round 22: Exam Calendar, Tablet Grading, Student Achievements, Notification Center, Announcements, Styling

- Exam Calendar View: Teacher exam planning with calendar integration
  - Teachers create/modify exams, admin full CRUD, students read-only
  - CalendarEvent integration, 2-week warning, AI test availability badge
  - Monthly/weekly calendar view, list view, statistics

- Tablet Grading View: Stylus annotation and AI grading audit
  - HTML5 Canvas with PointerEvent pressure sensitivity
  - Tool palette: pen, highlighter, eraser, text, stamp
  - Quick grade buttons (German 1-6 scale)
  - AI grading audit with detailed feedback and suggestions

- Student Achievements View: Badges, XP, leaderboard
  - Badge collection grid with category filters
  - Level/XP system with progress bars
  - Leaderboard with opt-in
  - Daily/weekly challenges

- Student Study Planner View: Pomodoro timer, weekly schedule
  - Pomodoro timer with focus score
  - Weekly study schedule with drag-and-drop
  - AI study recommendations
  - Session history and statistics

- Notification Center View: Categories, filters, bulk actions
  - 6 categories, 4 priority levels
  - Real-time polling (10 seconds)
  - Preferences with quiet hours
  - Statistics charts

- School Announcements View: CRUD, read receipts
  - 6 announcement types with gradient headers
  - Pin-to-top, read receipts, target audience
  - Admin/teacher create, students/parents view

- Bug fixes: AnnouncementRead model user relation, User API schoolId field
- i18n: 200+ new keys, breadcrumb navigation fixes
- Styling: Gradient headers, animations, hover effects across all new views
This commit is contained in:
Mustafa Cetin committed 2026-07-29 22:56:40 +00:00
1 parent 9e22214470
commit 490bd0650f
30 files changed
+10844 -31

No files matched your search

@@ -0,0 +1,100 @@
// CompetenceTrack — Notification Preferences API
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
export async function GET() {
const session = await getSession();
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const schoolId = session.user.schoolId;
if (!schoolId) {
return NextResponse.json({ error: 'No school assigned' }, { status: 400 });
}
let prefs = await db.notificationPreferences.findUnique({
where: { userId: session.user.id },
});
// Create default preferences if none exist
if (!prefs) {
prefs = await db.notificationPreferences.create({
data: {
userId: session.user.id,
schoolId,
},
});
}
return NextResponse.json(prefs);
}
export async function PUT(request: Request) {
const session = await getSession();
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const schoolId = session.user.schoolId;
if (!schoolId) {
return NextResponse.json({ error: 'No school assigned' }, { status: 400 });
}
try {
const body = await request.json();
const {
academicEnabled,
behavioralEnabled,
administrativeEnabled,
calendarEnabled,
communicationEnabled,
systemEnabled,
quietHoursStart,
quietHoursEnd,
quietHoursEnabled,
emailDigestEnabled,
emailDigestFrequency,
} = body as {
academicEnabled?: boolean;
behavioralEnabled?: boolean;
administrativeEnabled?: boolean;
calendarEnabled?: boolean;
communicationEnabled?: boolean;
systemEnabled?: boolean;
quietHoursStart?: string | null;
quietHoursEnd?: string | null;
quietHoursEnabled?: boolean;
emailDigestEnabled?: boolean;
emailDigestFrequency?: string;
};
const updateData: Record<string, unknown> = {};
if (academicEnabled !== undefined) updateData.academicEnabled = academicEnabled;
if (behavioralEnabled !== undefined) updateData.behavioralEnabled = behavioralEnabled;
if (administrativeEnabled !== undefined) updateData.administrativeEnabled = administrativeEnabled;
if (calendarEnabled !== undefined) updateData.calendarEnabled = calendarEnabled;
if (communicationEnabled !== undefined) updateData.communicationEnabled = communicationEnabled;
if (systemEnabled !== undefined) updateData.systemEnabled = systemEnabled;
if (quietHoursStart !== undefined) updateData.quietHoursStart = quietHoursStart;
if (quietHoursEnd !== undefined) updateData.quietHoursEnd = quietHoursEnd;
if (quietHoursEnabled !== undefined) updateData.quietHoursEnabled = quietHoursEnabled;
if (emailDigestEnabled !== undefined) updateData.emailDigestEnabled = emailDigestEnabled;
if (emailDigestFrequency !== undefined) updateData.emailDigestFrequency = emailDigestFrequency;
const prefs = await db.notificationPreferences.upsert({
where: { userId: session.user.id },
update: updateData,
create: {
userId: session.user.id,
schoolId,
...updateData,
},
});
return NextResponse.json(prefs);
} catch {
return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
}
}