'use client'; import React, { useEffect, useState, useCallback } from 'react'; import { motion } from 'framer-motion'; import { Settings as SettingsIcon, School, Calendar, BookOpen, FileText, Shield, Download, Trash2, Plus, Pencil, CheckCircle2, Clock, AlertTriangle, Filter, RefreshCw, Users, UserPlus, Mail, Key, Search, Database, Activity, GraduationCap, Heart, UserCheck, Zap, BarChart3, TrendingUp, CheckCircle, Leaf, DatabaseIcon, Upload, FileDown, FileUp, Loader2, ClipboardCheck, CalendarDays, HardDrive, Archive, RotateCcw, Volume2, VolumeX, Bell, MapPin, Building2, ArrowRightLeft, TrendingDown, ArrowUpRight, Palette, Eye, Send, Globe, FileJson, FileType, Phone, PhoneCall, AlertCircle, Star, Printer, Trophy, Award, Target, CalendarCheck, Flame, } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Switch } from '@/components/ui/switch'; import { Separator } from '@/components/ui/separator'; import { Skeleton } from '@/components/ui/skeleton'; import { Checkbox } from '@/components/ui/checkbox'; import { Progress } from '@/components/ui/progress'; import { useAppStore } from '@/lib/store'; import { t } from '@/lib/i18n'; import { apiGet, apiPost, apiPut, apiDelete } from '@/lib/api'; import { toast } from 'sonner'; import { getNotificationSoundPref, setNotificationSoundPref, playNotificationSound } from '@/lib/websocket'; import { fetchSchools, updateSchool, fetchSchoolYears, createSchoolYear, fetchSubjects, createSubject, updateSubject, deleteSubject, fetchAuditLog, downloadCsvExport, requestDataErasure, fetchUsers, createUser, updateUser, deleteUser, createStudentUserAccount, bulkCreateStudentAccounts, fetchStudents, type School as SchoolType, type SchoolYear, type Subject, type AuditLogEntry, type UserAccount, exportAuditLogCsv, type SchoolDistrictData, fetchDistricts, createDistrict, updateDistrict, deleteDistrict, fetchDistrictSchools, assignSchoolToDistrict, fetchEmailTemplates, createEmailTemplate, updateEmailTemplate, deleteEmailTemplate, sendTestEmail, fetchEmailLogs, type EmailTemplate, type EmailLog, fetchBadges, createBadge, updateBadge, deleteBadge, fetchStudentBadges, awardBadgeToStudent, type BadgeData, type StudentBadgeData, seedBadges, } from '@/lib/api'; import { RateLimitStatus } from '@/components/offline-indicator'; // ─── Emergency Contact Data Type ───────────────────────────────────── interface EmergencyContactData { id: string; schoolId: string; studentId: string; name: string; relationship: string; phone: string; phoneAlt: string | null; email: string | null; address: string | null; isPrimary: boolean; priority: number; notes: string | null; isDemo: boolean; createdAt: string; updatedAt: string; deletedAt: string | null; student: { id: string; firstName: string; lastName: string }; } // ─── Emergency Contacts Manager Component ──────────────────────────── function EmergencyContactsManager({ schoolId }: { schoolId: string }) { const [contacts, setContacts] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [ecDialogOpen, setEcDialogOpen] = useState(false); const [ecEditId, setEcEditId] = useState(null); const [ecForm, setEcForm] = useState({ studentId: '', name: '', relationship: 'mother', phone: '', phoneAlt: '', email: '', address: '', isPrimary: false, priority: 1, notes: '' }); const loadContacts = useCallback(async () => { if (!schoolId) return; setLoading(true); try { const data = await apiGet(`/api/emergency-contacts?schoolId=${schoolId}`); setContacts(data); } catch { /* ignore */ } setLoading(false); }, [schoolId]); useEffect(() => { loadContacts(); }, [loadContacts]); const filtered = contacts.filter((c) => c.name.toLowerCase().includes(searchTerm.toLowerCase()) || c.student.firstName.toLowerCase().includes(searchTerm.toLowerCase()) || c.student.lastName.toLowerCase().includes(searchTerm.toLowerCase()) || c.phone.includes(searchTerm) ); return (
setSearchTerm(e.target.value)} placeholder={t('emergency.search')} className="pl-10 rounded-xl border-rose-200/50 dark:border-rose-900/30" />
{loading ? (
{[1, 2, 3].map((i) => )}
) : filtered.length === 0 ? (

{t('emergency.no_contacts')}

) : (
{filtered.map((ec) => { const relLabel = t(`emergency.${ec.relationship}`) || ec.relationship; return (
{ec.isPrimary ? : }
{ec.name} {relLabel} {ec.isPrimary && {t('emergency.primary')}}
{t('emergency.student')}: {ec.student.firstName} {ec.student.lastName} {ec.phone}
); })}
)} {ecEditId ? t('emergency.edit') : t('emergency.add')} {t('emergency.title')}
setEcForm((f) => ({ ...f, name: e.target.value }))} className="mt-1 rounded-lg" placeholder={t('emergency.name')} />
setEcForm((f) => ({ ...f, priority: parseInt(e.target.value) || 1 }))} className="mt-1 rounded-lg" />
setEcForm((f) => ({ ...f, phone: e.target.value }))} className="mt-1 rounded-lg" placeholder="+49 123 456789" />
setEcForm((f) => ({ ...f, phoneAlt: e.target.value }))} className="mt-1 rounded-lg" placeholder="+49 987 654321" />
setEcForm((f) => ({ ...f, email: e.target.value }))} className="mt-1 rounded-lg" placeholder="name@example.com" />
setEcForm((f) => ({ ...f, address: e.target.value }))} className="mt-1 rounded-lg" />
setEcForm((f) => ({ ...f, notes: e.target.value }))} className="mt-1 rounded-lg" />
setEcForm((f) => ({ ...f, isPrimary: e.target.checked }))} className="rounded border-gray-300" />
); } const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.06 } }, }; const itemVariants = { hidden: { opacity: 0, y: 12 }, visible: { opacity: 1, y: 0 }, }; const schoolTypeLabels: Record = { ELEMENTARY: 'Grundschule', MIDDLE: 'Mittelschule', GYMNASIUM: 'Gymnasium', OTHER: 'Weitere', }; const actionColors: Record = { CREATE: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300', UPDATE: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300', DELETE: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300', LOGIN: 'bg-teal-100 text-teal-700 dark:bg-teal-900/30 dark:text-teal-300', EXPORT: 'bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300', }; // ─── Notification Sound Setting Component ───────────────────────────── function NotificationSoundSetting() { const [enabled, setEnabled] = useState(getNotificationSoundPref()); const handleToggle = () => { const newValue = !enabled; setEnabled(newValue); setNotificationSoundPref(newValue); if (newValue) { playNotificationSound(); } toast.success(newValue ? t('settings.notification_sound_enabled') : t('settings.notification_sound_disabled')); }; return (
{enabled ? : }

{t('settings.notification_sound')}

{t('settings.notification_sound_desc')}

); } // ─── Badge Management Tab Component ───────────────────────────────────── function BadgeManagementTab() { const currentUser = useAppStore((s) => s.currentUser); const [badges, setBadges] = useState([]); const [studentBadges, setStudentBadges] = useState([]); const [students, setStudentsList] = useState>([]); const [loading, setLoading] = useState(true); const [createBadgeOpen, setCreateBadgeOpen] = useState(false); const [awardBadgeOpen, setAwardBadgeOpen] = useState(false); const [newBadgeForm, setNewBadgeForm] = useState({ name: '', description: '', icon: 'Award', color: '#10b981', category: 'achievement', requirementType: 'custom', requirementValue: 5, isAuto: true, }); const [awardForm, setAwardForm] = useState({ studentId: '', badgeId: '', notes: '' }); const categoryOptions = [ { value: 'competency', label: t('badges.competency') }, { value: 'attendance', label: t('badges.attendance') }, { value: 'behavior', label: t('badges.behavior') }, { value: 'achievement', label: t('badges.achievement') }, { value: 'milestone', label: t('badges.milestone') }, ]; const reqTypeOptions = [ { value: 'mastery_level', label: 'Mastery Level' }, { value: 'attendance_rate', label: 'Attendance Rate' }, { value: 'behavior_count', label: 'Behavior Count' }, { value: 'progress_entries', label: 'Progress Entries' }, { value: 'custom', label: 'Custom' }, ]; const iconOptions = ['Award', 'Star', 'Trophy', 'Target', 'CalendarCheck', 'TrendingUp', 'BookOpen', 'Pencil', 'ClipboardCheck', 'Leaf', 'Heart', 'Zap', 'Rocket', 'Flame']; const loadBadges = useCallback(async () => { if (!currentUser?.schoolId) return; setLoading(true); try { // Seed default badges first await seedBadges(currentUser.schoolId); const data = await fetchBadges(currentUser.schoolId); setBadges(data); const sbData = await fetchStudentBadges(currentUser.schoolId); setStudentBadges(sbData); const stuData = await fetchStudents(currentUser.schoolId); setStudentsList(stuData.map(s => ({ id: s.id, firstName: s.firstName, lastName: s.lastName }))); } catch { /* ignore */ } setLoading(false); }, [currentUser?.schoolId]); useEffect(() => { loadBadges(); }, [loadBadges]); const handleCreateBadge = async () => { if (!currentUser?.schoolId || !newBadgeForm.name) return; try { await createBadge({ schoolId: currentUser.schoolId, ...newBadgeForm }); toast.success(t('badges.create')); setCreateBadgeOpen(false); setNewBadgeForm({ name: '', description: '', icon: 'Award', color: '#10b981', category: 'achievement', requirementType: 'custom', requirementValue: 5, isAuto: true }); loadBadges(); } catch { toast.error(t('error.generic')); } }; const handleAwardBadge = async () => { if (!currentUser?.schoolId || !awardForm.studentId || !awardForm.badgeId) return; try { await awardBadgeToStudent({ schoolId: currentUser.schoolId, studentId: awardForm.studentId, badgeId: awardForm.badgeId, notes: awardForm.notes }); toast.success(t('badges.award')); setAwardBadgeOpen(false); setAwardForm({ studentId: '', badgeId: '', notes: '' }); loadBadges(); } catch { toast.error(t('error.generic')); } }; const handleDeleteBadge = async (id: string) => { try { await deleteBadge(id); toast.success(t('badges.delete')); loadBadges(); } catch { toast.error(t('error.generic')); } }; if (loading) { return
; } return (
{/* Badge Statistics */}
{t('badges.statistics')}

{badges.length}

{t('badges.title')}

{studentBadges.length}

{t('badges.earned')}

{badges.filter(b => b.isAuto).length}

{t('badges.auto')}

{/* Badge List */}
{badges.map((badge) => { const awardedCount = badge._count?.studentBadges ?? 0; return (

{badge.name}

{badge.description}

{t(`badges.${badge.category}`)} {badge.isAuto && {t('badges.auto')}} {awardedCount} {t('badges.earned')}
); })}
{/* Create Badge Dialog */} {t('badges.create')} {t('badges.create')}
setNewBadgeForm(f => ({ ...f, name: e.target.value }))} placeholder={t('badges.name' in {} ? 'badges.name' : 'label.name')} />
setNewBadgeForm(f => ({ ...f, description: e.target.value }))} />
setNewBadgeForm(f => ({ ...f, color: e.target.value }))} className="h-10" />
setNewBadgeForm(f => ({ ...f, requirementValue: parseInt(e.target.value) || 0 }))} />
setNewBadgeForm(f => ({ ...f, isAuto: e.target.checked }))} className="rounded" />
{/* Award Badge Dialog */} {t('badges.award_manually')} {t('badges.award')}
setAwardForm(f => ({ ...f, notes: e.target.value }))} />
); } // ─── Privacy / DSGVO Tab Component ───────────────────────────────────── function PrivacyTab({ currentUser }: { currentUser: { id: string; role: string; email: string; firstName: string; lastName: string } | null }) { const [exporting, setExporting] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deletePassword, setDeletePassword] = useState(''); const [deleting, setDeleting] = useState(false); const [deletionStatus, setDeletionStatus] = useState<{ scheduledForDeletion: boolean; scheduledDeletionDate?: string; canCancel?: boolean } | null>(null); const [cancelling, setCancelling] = useState(false); const loadDeletionStatus = useCallback(async () => { try { const res = await fetch('/api/account-deletion'); if (res.ok) { const data = await res.json(); setDeletionStatus(data); } } catch { // ignore } }, []); useEffect(() => { loadDeletionStatus(); }, [loadDeletionStatus]); const handleExportData = async () => { setExporting(true); try { const res = await fetch('/api/gdpr-export'); if (!res.ok) throw new Error('Export failed'); const data = await res.json(); const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `competencetrack-data-export-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast.success(t('dsgvo.export_data_success')); } catch { toast.error(t('dsgvo.export_data_error')); } finally { setExporting(false); } }; const handleDeleteAccount = async () => { if (!deletePassword) return; setDeleting(true); try { const res = await fetch('/api/account-deletion', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: deletePassword }), }); const data = await res.json(); if (!res.ok) { if (data.error === 'Wrong password') { toast.error(t('dsgvo.delete_wrong_password')); } else { toast.error(data.error || t('dsgvo.delete_error')); } return; } toast.success(t('dsgvo.delete_success')); setDeleteDialogOpen(false); setDeletePassword(''); loadDeletionStatus(); } catch { toast.error(t('dsgvo.delete_error')); } finally { setDeleting(false); } }; const handleCancelDeletion = async () => { setCancelling(true); try { const res = await fetch('/api/account-deletion', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, }); if (!res.ok) { const data = await res.json(); toast.error(data.error || t('dsgvo.delete_error')); return; } toast.success(t('dsgvo.delete_cancel_success')); loadDeletionStatus(); } catch { toast.error(t('dsgvo.delete_error')); } finally { setCancelling(false); } }; const isAdmin = currentUser?.role === 'SCHOOL_ADMIN' || currentUser?.role === 'SUPER_ADMIN'; const userRights = [ { key: 'right_access', icon: Eye, color: 'emerald' }, { key: 'right_rectification', icon: Pencil, color: 'teal' }, { key: 'right_erasure', icon: Trash2, color: 'rose' }, { key: 'right_portability', icon: Download, color: 'amber' }, { key: 'right_restriction', icon: Shield, color: 'violet' }, { key: 'right_objection', icon: AlertTriangle, color: 'orange' }, ]; return (
{/* ── Privacy Policy Card ─────────────────────────────── */}
{t('dsgvo.privacy_policy')}
{t('dsgvo.data_processing')}

{t('dsgvo.privacy_policy_text')}

{/* Data collected */}

{t('dsgvo.data_collected')}

{t('dsgvo.data_collected_list')}

{/* Purpose */}

{t('dsgvo.data_purpose')}

{t('dsgvo.data_purpose_list')}

{/* Retention */}

{t('dsgvo.data_retention')}

{t('dsgvo.data_retention_list')}

{/* Third party */}

{t('dsgvo.data_third_party')}

{t('dsgvo.data_third_party_list')}

{/* DPO Contact */}

{t('dsgvo.contact_dpo')}

datenschutz@competencetrack.org

{/* ── User Rights Card ─────────────────────────────── */}
{t('dsgvo.rights_title')}
{t('dsgvo.rights_desc')}
{userRights.map((right) => { const Icon = right.icon; const colorMap: Record = { emerald: { bg: 'bg-emerald-100 dark:bg-emerald-900/30', text: 'text-emerald-600 dark:text-emerald-400', border: 'border-emerald-200/30 dark:border-emerald-900/20' }, teal: { bg: 'bg-teal-100 dark:bg-teal-900/30', text: 'text-teal-600 dark:text-teal-400', border: 'border-teal-200/30 dark:border-teal-900/20' }, rose: { bg: 'bg-rose-100 dark:bg-rose-900/30', text: 'text-rose-600 dark:text-rose-400', border: 'border-rose-200/30 dark:border-rose-900/20' }, amber: { bg: 'bg-amber-100 dark:bg-amber-900/30', text: 'text-amber-600 dark:text-amber-400', border: 'border-amber-200/30 dark:border-amber-900/20' }, violet: { bg: 'bg-violet-100 dark:bg-violet-900/30', text: 'text-violet-600 dark:text-violet-400', border: 'border-violet-200/30 dark:border-violet-900/20' }, orange: { bg: 'bg-orange-100 dark:bg-orange-900/30', text: 'text-orange-600 dark:text-orange-400', border: 'border-orange-200/30 dark:border-orange-900/20' }, }; const c = colorMap[right.color] || colorMap.emerald; return (
{t(`dsgvo.${right.key}`)}

{t(`dsgvo.${right.key}_desc`)}

); })}
{/* ── Data Export Card ─────────────────────────────── */}
{t('dsgvo.data_portability')}
DSGVO Art. 20

{t('dsgvo.export_data_desc')}

{/* ── Account Deletion Card ─────────────────────────────── */}
{t('dsgvo.delete_account')}
DSGVO Art. 17

{t('dsgvo.delete_account_desc')}

{/* Deletion status */} {deletionStatus?.scheduledForDeletion && (
{t('dsgvo.delete_scheduled')} {new Date(deletionStatus.scheduledDeletionDate!).toLocaleDateString()}
{deletionStatus.canCancel && ( )}
)} {/* Warning */}

{t('dsgvo.delete_warning')}

{!deletionStatus?.scheduledForDeletion && ( )}
{/* ── Jugendschutz Card ─────────────────────────────── */}
{t('dsgvo.jugendschutz')}
Jugendschutzgesetz

{t('dsgvo.jugendschutz_notice')}

{t('dsgvo.parental_consent')}

{t('dsgvo.jugendschutz_notice')}

{t('dsgvo.no_advertising')}

{t('dsgvo.no_tracking')}

{t('dsgvo.jugendschutz_rewards')}

{/* ── DPA / AVV Card (Admin only) ─────────────────────── */} {isAdmin && (
{t('dsgvo.dpa')}
Art. 28 DSGVO

{t('dsgvo.dpa_template_text')}

{/* DPA Status */}
{t('dsgvo.dpa')} {t('dsgvo.dpa_status_pending')}
{/* Data Processing Register */}

{t('dsgvo.data_register')}

{t('dsgvo.data_register_desc')}

)} {/* ── Consent Management Card ─────────────────────────────── */}
{t('dsgvo.consent_management')}
{t('dsgvo.consent_management_desc')}
{[ { key: 'data_processing', icon: Database, color: 'emerald' }, { key: 'communication', icon: Mail, color: 'teal' }, { key: 'analytics', icon: BarChart3, color: 'amber' }, { key: 'third_party', icon: Users, color: 'violet' }, ].map((consent) => { const Icon = consent.icon; const colorMap: Record = { emerald: { bg: 'bg-emerald-100 dark:bg-emerald-900/30', text: 'text-emerald-600 dark:text-emerald-400', border: 'border-emerald-200/30 dark:border-emerald-900/20' }, teal: { bg: 'bg-teal-100 dark:bg-teal-900/30', text: 'text-teal-600 dark:text-teal-400', border: 'border-teal-200/30 dark:border-teal-900/20' }, amber: { bg: 'bg-amber-100 dark:bg-amber-900/30', text: 'text-amber-600 dark:text-amber-400', border: 'border-amber-200/30 dark:border-amber-900/20' }, violet: { bg: 'bg-violet-100 dark:bg-violet-900/30', text: 'text-violet-600 dark:text-violet-400', border: 'border-violet-200/30 dark:border-violet-900/20' }, }; const c = colorMap[consent.color] || colorMap.emerald; return (

{t(`dsgvo.consent_${consent.key}`)}

{t(`dsgvo.consent_${consent.key}_desc`)}

{t('dsgvo.consent_granted')}
); })}
{/* ── Data Retention Settings Card ─────────────────────────────── */}
{t('dsgvo.retention_settings')}
{t('dsgvo.retention_settings_desc')}
{[ { key: 'grades', icon: GraduationCap, color: 'emerald', defaultYears: 3 }, { key: 'attendance', icon: CalendarCheck, color: 'teal', defaultYears: 2 }, { key: 'behavior', icon: Heart, color: 'violet', defaultYears: 1 }, ].map((retention) => { const Icon = retention.icon; const colorMap: Record = { emerald: { bg: 'bg-emerald-50/50 dark:bg-emerald-900/10', text: 'text-emerald-600 dark:text-emerald-400', border: 'border-emerald-200/30 dark:border-emerald-900/20' }, teal: { bg: 'bg-teal-50/50 dark:bg-teal-900/10', text: 'text-teal-600 dark:text-teal-400', border: 'border-teal-200/30 dark:border-teal-900/20' }, violet: { bg: 'bg-violet-50/50 dark:bg-violet-900/10', text: 'text-violet-600 dark:text-violet-400', border: 'border-violet-200/30 dark:border-violet-900/20' }, }; const c = colorMap[retention.color] || colorMap.emerald; return (

{t(`dsgvo.retention_${retention.key}`)}

{retention.defaultYears} {t('dsgvo.retention_years')}
); })}
{/* ── Cookie Consent Management Card ─────────────────────────────── */}
{t('dsgvo.cookie_settings')}
{t('dsgvo.cookie_consent')}

{t('dsgvo.cookie_essential')}

{t('dsgvo.cookie_essential_desc')}

{t('dsgvo.consent_granted')}

{t('dsgvo.cookie_analytics')}

{t('dsgvo.cookie_analytics_desc')}

{t('dsgvo.consent_revoked')}
{/* ── Delete Account Confirmation Dialog ─────────────────────── */} { if (!open) { setDeleteDialogOpen(false); setDeletePassword(''); } }}> {t('dsgvo.delete_confirm_title')} {t('dsgvo.delete_confirm_desc')}

{t('dsgvo.delete_warning')}

setDeletePassword(e.target.value)} className="rounded-xl border-rose-200 dark:border-rose-900/30" placeholder="••••••" />
{t('action.cancel')} {deleting ? : } {t('dsgvo.delete_account')}
); } export default function SettingsView() { const currentUser = useAppStore((s) => s.currentUser); const [activeTab, setActiveTab] = useState('school'); // School info state const [schools, setSchools] = useState([]); const [selectedSchool, setSelectedSchool] = useState(null); const [schoolForm, setSchoolForm] = useState({ name: '', schoolType: '', country: '', timezone: '' }); const [schoolLoading, setSchoolLoading] = useState(true); const [schoolSaving, setSchoolSaving] = useState(false); // School years state const [schoolYears, setSchoolYears] = useState([]); const [yearsLoading, setYearsLoading] = useState(true); const [showCreateYear, setShowCreateYear] = useState(false); const [yearForm, setYearForm] = useState({ label: '', startDate: '', endDate: '' }); // Subjects state const [subjects, setSubjects] = useState([]); const [subjectsLoading, setSubjectsLoading] = useState(true); const [showCreateSubject, setShowCreateSubject] = useState(false); const [subjectForm, setSubjectForm] = useState({ name: '', gradeLevelMin: 1, gradeLevelMax: 13 }); const [editingSubject, setEditingSubject] = useState(null); const [deleteSubjectId, setDeleteSubjectId] = useState(null); // Audit log state const [auditEntries, setAuditEntries] = useState([]); const [auditLoading, setAuditLoading] = useState(true); const [auditActionFilter, setAuditActionFilter] = useState('all'); const [auditEntityTypeFilter, setAuditEntityTypeFilter] = useState('all'); const [auditDateFrom, setAuditDateFrom] = useState(''); const [auditDateTo, setAuditDateTo] = useState(''); const [auditSearch, setAuditSearch] = useState(''); const [auditPage, setAuditPage] = useState(1); const [auditTotalPages, setAuditTotalPages] = useState(1); const [auditDetailEntry, setAuditDetailEntry] = useState(null); // Data erasure state const [showErasureDialog, setShowErasureDialog] = useState(false); const [erasureScope, setErasureScope] = useState('STUDENT'); // Users state const [users, setUsers] = useState([]); const [usersLoading, setUsersLoading] = useState(true); const [showUserDialog, setShowUserDialog] = useState(false); const [editingUser, setEditingUser] = useState(null); const [userForm, setUserForm] = useState({ firstName: '', lastName: '', email: '', password: '', role: 'TEACHER' as 'TEACHER' | 'SCHOOL_ADMIN' | 'SUPER_ADMIN' | 'STUDENT' | 'PARENT', }); const [deleteUserId, setDeleteUserId] = useState(null); // Student account creation state const [showStudentAccountDialog, setShowStudentAccountDialog] = useState(false); const [showBulkStudentDialog, setShowBulkStudentDialog] = useState(false); const [studentAccountForm, setStudentAccountForm] = useState({ firstName: '', lastName: '', email: '', password: 'Schule2025!', studentId: '', }); const [bulkForm, setBulkForm] = useState({ defaultPassword: 'Schule2025!', emailDomain: 'schule.de', selectedStudentIds: [] as string[], }); const [availableStudents, setAvailableStudents] = useState>([]); const [bulkCreating, setBulkCreating] = useState(false); // Demo accounts state const [demoAccounts, setDemoAccounts] = useState>([]); const [demoLoading, setDemoLoading] = useState(true); const [deleteDemoId, setDeleteDemoId] = useState(null); const [deleteAllDemoOpen, setDeleteAllDemoOpen] = useState(false); // Data import state const [importType, setImportType] = useState<'students' | 'assessments' | 'grades'>('students'); const [importFile, setImportFile] = useState(null); const [importing, setImporting] = useState(false); const [importResult, setImportResult] = useState<{ created: number; skipped: number; errorCount: number; errors: string[]; detectedColumns: string[]; } | null>(null); const [dragOver, setDragOver] = useState(false); // Export history const [exportHistory, setExportHistory] = useState>([]); // Branding state const [brandingForm, setBrandingForm] = useState({ logoUrl: '', primaryColor: '#10b981', secondaryColor: '#14b8a6', accentColor: '#059669', fontFamily: 'Inter', customCss: '', motto: '', websiteUrl: '', emailDomain: '', address: '', phone: '', }); const [brandingSaving, setBrandingSaving] = useState(false); // Email templates state const [emailTemplates, setEmailTemplates] = useState([]); const [emailTemplatesLoading, setEmailTemplatesLoading] = useState(true); const [showEmailTemplateDialog, setShowEmailTemplateDialog] = useState(false); const [editingEmailTemplate, setEditingEmailTemplate] = useState(null); const [emailTemplateForm, setEmailTemplateForm] = useState({ name: '', subject: '', body: '' }); const [emailTemplateSaving, setEmailTemplateSaving] = useState(false); const [deleteEmailTemplateId, setDeleteEmailTemplateId] = useState(null); const [showEmailPreview, setShowEmailPreview] = useState(false); const [emailPreviewTemplate, setEmailPreviewTemplate] = useState(null); const [testEmailSending, setTestEmailSending] = useState(false); const [testEmailRecipient, setTestEmailRecipient] = useState(''); // Email log state const [emailLogs, setEmailLogs] = useState([]); const [emailLogCounts, setEmailLogCounts] = useState({ total: 0, sent: 0, failed: 0, pending: 0, bounced: 0 }); const [emailLogsLoading, setEmailLogsLoading] = useState(true); const [emailLogFilter, setEmailLogFilter] = useState('all'); // Email settings state const [emailSettings, setEmailSettings] = useState({ smtpHost: '', smtpPort: '587', smtpUser: '', smtpPassword: '', smtpFrom: '', frequency: 'weekly', autoReports: true, autoBehavior: true, autoAttendance: false, }); // Enhanced export state const [exportFormat, setExportFormat] = useState<'csv' | 'json' | 'pdf'>('csv'); const [exportClassFilter, setExportClassFilter] = useState(''); const [exportDateFrom, setExportDateFrom] = useState(''); const [exportDateTo, setExportDateTo] = useState(''); const [exporting, setExporting] = useState(false); // Backup state const [backups, setBackups] = useState>([]); const [backupLoading, setBackupLoading] = useState(false); const [backupCreating, setBackupCreating] = useState(false); const [deleteBackupId, setDeleteBackupId] = useState(null); const [restoreBackupId, setRestoreBackupId] = useState(null); const [autoBackupEnabled, setAutoBackupEnabled] = useState(false); const [autoBackupFrequency, setAutoBackupFrequency] = useState<'daily' | 'weekly'>('weekly'); // District state const [districts, setDistricts] = useState([]); const [districtsLoading, setDistrictsLoading] = useState(false); const [districtDialogOpen, setDistrictDialogOpen] = useState(false); const [editingDistrict, setEditingDistrict] = useState(null); const [districtForm, setDistrictForm] = useState({ name: '', code: '', region: '', country: 'DE', adminEmail: '' }); const [districtSaving, setDistrictSaving] = useState(false); const [deleteDistrictId, setDeleteDistrictId] = useState(null); const [districtSchools, setDistrictSchools] = useState>([]); const [districtSchoolsLoading, setDistrictSchoolsLoading] = useState(false); const [selectedDistrictId, setSelectedDistrictId] = useState(null); const [assignSchoolId, setAssignSchoolId] = useState(''); const isSuperAdmin = currentUser?.role === 'SUPER_ADMIN'; const isAdmin = currentUser?.role === 'SCHOOL_ADMIN' || currentUser?.role === 'SUPER_ADMIN'; const loadSchools = useCallback(async () => { setSchoolLoading(true); try { const data = await fetchSchools(); setSchools(data); if (currentUser?.schoolId) { const mySchool = data.find((s) => s.id === currentUser.schoolId); if (mySchool) { setSelectedSchool(mySchool); setSchoolForm({ name: mySchool.name, schoolType: mySchool.schoolType, country: mySchool.country, timezone: mySchool.timezone, }); } } else if (data.length > 0) { setSelectedSchool(data[0]); setSchoolForm({ name: data[0].name, schoolType: data[0].schoolType, country: data[0].country, timezone: data[0].timezone, }); } } catch { toast.error(t('error.generic')); } finally { setSchoolLoading(false); } }, [currentUser?.schoolId]); const loadSchoolYears = useCallback(async () => { if (!currentUser?.schoolId) return; setYearsLoading(true); try { const data = await fetchSchoolYears(currentUser.schoolId); setSchoolYears(data); } catch { toast.error(t('error.generic')); } finally { setYearsLoading(false); } }, [currentUser?.schoolId]); const loadSubjects = useCallback(async () => { setSubjectsLoading(true); try { const data = await fetchSubjects(currentUser?.schoolId ?? undefined); setSubjects(data); } catch { toast.error(t('error.generic')); } finally { setSubjectsLoading(false); } }, [currentUser?.schoolId]); const loadAuditLog = useCallback(async () => { setAuditLoading(true); try { const data = await fetchAuditLog({ schoolId: currentUser?.schoolId ?? undefined, action: auditActionFilter !== 'all' ? auditActionFilter : undefined, entityType: auditEntityTypeFilter !== 'all' ? auditEntityTypeFilter : undefined, startDate: auditDateFrom || undefined, endDate: auditDateTo || undefined, page: auditPage, pageSize: 50, }); setAuditEntries(data.entries); setAuditTotalPages(data.pagination.totalPages); } catch { toast.error(t('error.generic')); } finally { setAuditLoading(false); } }, [currentUser?.schoolId, auditActionFilter, auditEntityTypeFilter, auditDateFrom, auditDateTo, auditPage]); const loadUsers = useCallback(async () => { setUsersLoading(true); try { const data = await fetchUsers(currentUser?.schoolId ?? undefined); setUsers(data); } catch { toast.error(t('error.generic')); } finally { setUsersLoading(false); } }, [currentUser?.schoolId]); // Load email templates const loadEmailTemplates = useCallback(async () => { if (!currentUser?.schoolId) return; setEmailTemplatesLoading(true); try { const data = await fetchEmailTemplates(currentUser.schoolId); setEmailTemplates(data); } catch { // ignore } finally { setEmailTemplatesLoading(false); } }, [currentUser?.schoolId]); // Load email logs const loadEmailLogs = useCallback(async () => { if (!currentUser?.schoolId) return; setEmailLogsLoading(true); try { const data = await fetchEmailLogs(currentUser.schoolId, emailLogFilter !== 'all' ? emailLogFilter : undefined); setEmailLogs(data.logs); setEmailLogCounts(data.counts); } catch { // ignore } finally { setEmailLogsLoading(false); } }, [currentUser?.schoolId, emailLogFilter]); const loadDemoAccounts = useCallback(async () => { setDemoLoading(true); try { const response = await fetch('/api/demo-accounts'); if (!response.ok) { throw new Error(`API error ${response.status}`); } const data = await response.json(); setDemoAccounts(Array.isArray(data) ? data : []); } catch (err) { console.error('Failed to load demo accounts:', err); setDemoAccounts([]); } finally { setDemoLoading(false); } }, []); // Backup functions const loadBackups = useCallback(async () => { if (!currentUser?.schoolId) return; setBackupLoading(true); try { const response = await fetch(`/api/backup?schoolId=${currentUser.schoolId}`); if (!response.ok) throw new Error('Failed to load backups'); const data = await response.json(); setBackups(Array.isArray(data) ? data : []); } catch (err) { console.error('Failed to load backups:', err); setBackups([]); } finally { setBackupLoading(false); } }, [currentUser?.schoolId]); // Reload demo accounts when demo tab is selected const handleTabChange = useCallback((tab: string) => { setActiveTab(tab); if (tab === 'demo' && isAdmin) { loadDemoAccounts(); } if (tab === 'backup') { loadBackups(); } if (tab === 'district' && isSuperAdmin) { loadDistricts(); } if (tab === 'email') { loadEmailTemplates(); loadEmailLogs(); } if (tab === 'branding' && selectedSchool) { setBrandingForm({ logoUrl: selectedSchool.logoUrl || '', primaryColor: selectedSchool.primaryColor || '#10b981', secondaryColor: selectedSchool.secondaryColor || '#14b8a6', accentColor: selectedSchool.accentColor || '#059669', fontFamily: selectedSchool.fontFamily || 'Inter', customCss: selectedSchool.customCss || '', motto: selectedSchool.motto || '', websiteUrl: selectedSchool.websiteUrl || '', emailDomain: selectedSchool.emailDomain || '', address: selectedSchool.address || '', phone: selectedSchool.phone || '', }); } }, [isAdmin, loadDemoAccounts, loadBackups, isSuperAdmin, loadEmailTemplates, loadEmailLogs, selectedSchool]); // District handlers const loadDistricts = useCallback(async () => { setDistrictsLoading(true); try { const data = await fetchDistricts(); setDistricts(data); } catch { // ignore } finally { setDistrictsLoading(false); } }, []); const loadDistrictSchoolsData = useCallback(async (districtId: string) => { setSelectedDistrictId(districtId); setDistrictSchoolsLoading(true); try { const data = await fetchDistrictSchools(districtId); setDistrictSchools(data); } catch { // ignore } finally { setDistrictSchoolsLoading(false); } }, []); const handleCreateDistrict = async () => { setDistrictSaving(true); try { await createDistrict({ name: districtForm.name, code: districtForm.code || undefined, region: districtForm.region || undefined, country: districtForm.country, adminEmail: districtForm.adminEmail || undefined, }); toast.success(t('district.create')); setDistrictDialogOpen(false); resetDistrictForm(); loadDistricts(); } catch (err) { toast.error(err instanceof Error ? err.message : t('error.generic')); } finally { setDistrictSaving(false); } }; const handleUpdateDistrict = async () => { if (!editingDistrict) return; setDistrictSaving(true); try { await updateDistrict(editingDistrict.id, { name: districtForm.name, code: districtForm.code || undefined, region: districtForm.region || undefined, country: districtForm.country, adminEmail: districtForm.adminEmail || undefined, }); toast.success(t('district.edit')); setDistrictDialogOpen(false); setEditingDistrict(null); resetDistrictForm(); loadDistricts(); } catch (err) { toast.error(err instanceof Error ? err.message : t('error.generic')); } finally { setDistrictSaving(false); } }; const handleDeleteDistrict = async (id: string) => { try { await deleteDistrict(id); toast.success(t('district.delete')); setDeleteDistrictId(null); if (selectedDistrictId === id) setSelectedDistrictId(null); loadDistricts(); } catch (err) { toast.error(err instanceof Error ? err.message : t('error.generic')); } }; const handleAssignSchool = async () => { if (!selectedDistrictId || !assignSchoolId) return; try { await assignSchoolToDistrict(selectedDistrictId, assignSchoolId); toast.success(t('district.assign_school')); setAssignSchoolId(''); loadDistrictSchoolsData(selectedDistrictId); loadDistricts(); } catch (err) { toast.error(err instanceof Error ? err.message : t('error.generic')); } }; const handleUnassignSchool = async (schoolId: string) => { // Unassign by setting districtId to null on the school try { await apiPut(`/api/schools`, { id: schoolId, districtId: null }); toast.success(t('district.unassign_school')); if (selectedDistrictId) loadDistrictSchoolsData(selectedDistrictId); loadDistricts(); } catch (err) { toast.error(err instanceof Error ? err.message : t('error.generic')); } }; const openEditDistrict = (district: SchoolDistrictData) => { setEditingDistrict(district); setDistrictForm({ name: district.name, code: district.code ?? '', region: district.region ?? '', country: district.country, adminEmail: district.adminEmail ?? '', }); setDistrictDialogOpen(true); }; const resetDistrictForm = () => { setDistrictForm({ name: '', code: '', region: '', country: 'DE', adminEmail: '' }); }; const handleCreateBackup = useCallback(async () => { if (!currentUser?.schoolId) return; setBackupCreating(true); try { const response = await fetch('/api/backup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ schoolId: currentUser.schoolId }), }); if (!response.ok) throw new Error('Failed to create backup'); toast.success(t('backup.created_success')); loadBackups(); } catch (err) { console.error('Failed to create backup:', err); toast.error(t('backup.error_create')); } finally { setBackupCreating(false); } }, [currentUser?.schoolId, loadBackups]); const handleRestoreBackup = useCallback(async () => { if (!restoreBackupId || !currentUser?.schoolId) return; try { const response = await fetch('/api/backup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ schoolId: currentUser.schoolId, action: 'restore', backupId: restoreBackupId }), }); if (!response.ok) throw new Error('Failed to restore backup'); toast.success(t('backup.restored_success')); setRestoreBackupId(null); } catch (err) { console.error('Failed to restore backup:', err); toast.error(t('backup.error_restore')); } }, [restoreBackupId, currentUser?.schoolId]); const handleDeleteBackup = useCallback(async (id: string) => { try { const response = await fetch(`/api/backup?id=${id}`, { method: 'DELETE' }); if (!response.ok) throw new Error('Failed to delete backup'); toast.success(t('backup.deleted_success')); setDeleteBackupId(null); loadBackups(); } catch (err) { console.error('Failed to delete backup:', err); toast.error(t('backup.error_delete')); } }, [loadBackups]); const handleDownloadBackup = useCallback(async (backup: typeof backups[0]) => { if (!currentUser?.schoolId) return; try { const response = await fetch('/api/backup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ schoolId: currentUser.schoolId }), }); if (!response.ok) throw new Error('Failed to download backup'); const data = await response.json(); const blob = new Blob([JSON.stringify(data.data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = backup.filename; a.click(); URL.revokeObjectURL(url); } catch (err) { console.error('Failed to download backup:', err); toast.error(t('backup.error_create')); } }, [currentUser?.schoolId]); useEffect(() => { loadSchools(); loadSchoolYears(); loadSubjects(); loadAuditLog(); loadUsers(); if (isAdmin) loadDemoAccounts(); }, [loadSchools, loadSchoolYears, loadSubjects, loadAuditLog, loadUsers, isAdmin, loadDemoAccounts]); const handleSaveSchool = async () => { if (!selectedSchool) return; setSchoolSaving(true); try { await updateSchool({ id: selectedSchool.id, ...schoolForm, }); toast.success(t('settings.school_saved')); loadSchools(); } catch { toast.error(t('error.generic')); } finally { setSchoolSaving(false); } }; const handleCreateYear = async () => { if (!currentUser?.schoolId) return; try { await createSchoolYear({ schoolId: currentUser.schoolId, ...yearForm, }); toast.success(t('settings.year_created')); setShowCreateYear(false); setYearForm({ label: '', startDate: '', endDate: '' }); loadSchoolYears(); } catch { toast.error(t('error.generic')); } }; const handleCreateSubject = async () => { try { await createSubject({ schoolId: currentUser?.schoolId ?? null, ...subjectForm, }); toast.success(t('settings.subject_created')); setShowCreateSubject(false); setSubjectForm({ name: '', gradeLevelMin: 1, gradeLevelMax: 13 }); loadSubjects(); } catch { toast.error(t('error.generic')); } }; const handleUpdateSubject = async () => { if (!editingSubject) return; try { await updateSubject({ id: editingSubject.id, name: subjectForm.name, gradeLevelMin: subjectForm.gradeLevelMin, gradeLevelMax: subjectForm.gradeLevelMax, }); toast.success(t('settings.subject_updated')); setEditingSubject(null); setSubjectForm({ name: '', gradeLevelMin: 1, gradeLevelMax: 13 }); loadSubjects(); } catch { toast.error(t('error.generic')); } }; const handleDeleteSubject = async () => { if (!deleteSubjectId) return; try { await deleteSubject(deleteSubjectId); toast.success(t('settings.subject_deleted')); setDeleteSubjectId(null); loadSubjects(); } catch { toast.error(t('error.generic')); } }; const handleCsvExport = (type: 'students' | 'progress' | 'assessments' | 'grades' | 'attendance') => { setExporting(true); downloadCsvExport({ type, schoolId: currentUser?.schoolId ?? undefined, schoolYearId: useAppStore.getState().schoolYearId ?? undefined, format: exportFormat, classGroupId: exportClassFilter || undefined, dateFrom: exportDateFrom || undefined, dateTo: exportDateTo || undefined, }); setExportHistory((prev) => [{ type, date: new Date().toLocaleDateString(), format: exportFormat }, ...prev].slice(0, 20)); toast.success(t('settings.data_export_started')); setTimeout(() => setExporting(false), 1500); }; // Branding save handler const handleSaveBranding = async () => { if (!selectedSchool) return; setBrandingSaving(true); try { await updateSchool({ id: selectedSchool.id, logoUrl: brandingForm.logoUrl || null, primaryColor: brandingForm.primaryColor || null, secondaryColor: brandingForm.secondaryColor || null, accentColor: brandingForm.accentColor || null, fontFamily: brandingForm.fontFamily || null, customCss: brandingForm.customCss || null, motto: brandingForm.motto || null, websiteUrl: brandingForm.websiteUrl || null, emailDomain: brandingForm.emailDomain || null, address: brandingForm.address || null, phone: brandingForm.phone || null, }); toast.success(t('branding.saved')); loadSchools(); } catch { toast.error(t('error.generic')); } finally { setBrandingSaving(false); } }; // Email template handlers const handleSaveEmailTemplate = async () => { if (!currentUser?.schoolId) return; setEmailTemplateSaving(true); try { if (editingEmailTemplate) { await updateEmailTemplate(editingEmailTemplate.id, { name: emailTemplateForm.name, subject: emailTemplateForm.subject, body: emailTemplateForm.body, }); toast.success(t('email.template_edit')); } else { await createEmailTemplate({ schoolId: currentUser.schoolId, name: emailTemplateForm.name, subject: emailTemplateForm.subject, body: emailTemplateForm.body, }); toast.success(t('email.template_create')); } setShowEmailTemplateDialog(false); setEditingEmailTemplate(null); setEmailTemplateForm({ name: '', subject: '', body: '' }); loadEmailTemplates(); } catch { toast.error(t('error.generic')); } finally { setEmailTemplateSaving(false); } }; const handleDeleteEmailTemplate = async () => { if (!deleteEmailTemplateId) return; try { await deleteEmailTemplate(deleteEmailTemplateId); toast.success(t('email.template_delete')); setDeleteEmailTemplateId(null); loadEmailTemplates(); } catch { toast.error(t('error.generic')); } }; const handleSendTestEmail = async () => { if (!currentUser?.schoolId || !testEmailRecipient || !emailPreviewTemplate) return; setTestEmailSending(true); try { await sendTestEmail({ schoolId: currentUser.schoolId, templateId: emailPreviewTemplate.id, recipientEmail: testEmailRecipient, recipientName: currentUser.firstName + ' ' + currentUser.lastName, subject: emailPreviewTemplate.subject, body: emailPreviewTemplate.body, }); toast.success(t('email.test_sent')); setShowEmailPreview(false); setTestEmailRecipient(''); loadEmailLogs(); } catch { toast.error(t('error.generic')); } finally { setTestEmailSending(false); } }; const handleImport = async () => { if (!importFile || !currentUser?.schoolId) return; setImporting(true); setImportResult(null); try { const formData = new FormData(); formData.append('file', importFile); formData.append('type', importType); formData.append('schoolId', currentUser.schoolId); const res = await fetch('/api/data-import', { method: 'POST', body: formData, }); if (!res.ok) { const err = await res.json().catch(() => ({ error: 'Import failed' })); throw new Error(err.error || 'Import failed'); } const result = await res.json(); setImportResult(result); toast.success(t('import.success')); setImportFile(null); } catch (err) { toast.error(err instanceof Error ? err.message : t('import.errors')); } finally { setImporting(false); } }; const handleDownloadSample = (type: 'students' | 'assessments' | 'grades') => { window.open(`/api/data-import?type=${type}`, '_blank'); }; const handleErasureRequest = async () => { try { await requestDataErasure({ scope: erasureScope, }); toast.success(t('settings.data_erasure_requested')); setShowErasureDialog(false); } catch { toast.error(t('error.generic')); } }; // ── Demo account handlers ── const handleToggleDemoAccount = async (id: string, currentDeletedAt: string | null) => { try { const action = currentDeletedAt ? 'enable' : 'disable'; await apiPut(`/api/demo-accounts/${id}`, { action }); toast.success(t('settings.demo_account_toggled')); loadDemoAccounts(); } catch { toast.error(t('error.generic')); } }; const handleDeleteDemoAccount = async (id: string) => { try { await apiDelete(`/api/demo-accounts/${id}`); toast.success(t('settings.demo_account_deleted')); setDeleteDemoId(null); loadDemoAccounts(); } catch { toast.error(t('error.generic')); } }; const handleDisableAllDemo = async () => { try { await apiPut('/api/demo-accounts', { action: 'disable' }); toast.success(t('settings.demo_disabled')); loadDemoAccounts(); } catch { toast.error(t('error.generic')); } }; const handleEnableAllDemo = async () => { try { await apiPut('/api/demo-accounts', { action: 'enable' }); toast.success(t('settings.demo_enabled')); loadDemoAccounts(); } catch { toast.error(t('error.generic')); } }; const handleDeleteAllDemo = async () => { try { await apiDelete('/api/demo-accounts'); toast.success(t('settings.demo_deleted')); setDeleteAllDemoOpen(false); loadDemoAccounts(); } catch { toast.error(t('error.generic')); } }; const getRoleBadgeColor = (role: string) => { switch (role) { case 'SCHOOL_ADMIN': return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300'; case 'TEACHER': return 'bg-teal-100 text-teal-700 dark:bg-teal-900/30 dark:text-teal-300'; case 'STUDENT': return 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300'; case 'PARENT': return 'bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300'; case 'SUPER_ADMIN': return 'bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-300'; default: return 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-300'; } }; const getRoleIcon = (role: string) => { switch (role) { case 'SCHOOL_ADMIN': return Shield; case 'TEACHER': return GraduationCap; case 'STUDENT': return UserCheck; case 'PARENT': return Heart; default: return Users; } }; const openCreateUserDialog = () => { setEditingUser(null); setUserForm({ firstName: '', lastName: '', email: '', password: '', role: 'TEACHER', }); setShowUserDialog(true); }; const openCreateStudentAccountDialog = async () => { setStudentAccountForm({ firstName: '', lastName: '', email: '', password: 'Schule2025!', studentId: '' }); try { const students = await fetchStudents(currentUser?.schoolId ?? undefined); setAvailableStudents(students.map((s) => ({ id: s.id, firstName: s.firstName, lastName: s.lastName }))); } catch { // ignore } setShowStudentAccountDialog(true); }; const openBulkStudentDialog = async () => { setBulkForm({ defaultPassword: 'Schule2025!', emailDomain: 'schule.de', selectedStudentIds: [] }); try { const students = await fetchStudents(currentUser?.schoolId ?? undefined); setAvailableStudents(students.map((s) => ({ id: s.id, firstName: s.firstName, lastName: s.lastName }))); } catch { // ignore } setShowBulkStudentDialog(true); }; const handleCreateStudentAccount = async () => { try { await createStudentUserAccount({ schoolId: currentUser?.schoolId ?? '', email: studentAccountForm.email, password: studentAccountForm.password, firstName: studentAccountForm.firstName, lastName: studentAccountForm.lastName, role: 'STUDENT', studentId: studentAccountForm.studentId || undefined, }); toast.success(t('settings.student_account_created')); setShowStudentAccountDialog(false); loadUsers(); } catch (err) { const message = err instanceof Error ? err.message : t('error.generic'); toast.error(message); } }; const handleBulkCreateStudents = async () => { if (bulkForm.selectedStudentIds.length === 0) { toast.error(t('error.generic')); return; } setBulkCreating(true); try { const result = await bulkCreateStudentAccounts({ schoolId: currentUser?.schoolId ?? '', defaultPassword: bulkForm.defaultPassword, studentIds: bulkForm.selectedStudentIds, emailDomain: bulkForm.emailDomain, }); toast.success(t('settings.bulk_accounts_created') + ` (${result.count})`); setShowBulkStudentDialog(false); loadUsers(); } catch (err) { const message = err instanceof Error ? err.message : t('error.generic'); toast.error(message); } finally { setBulkCreating(false); } }; const openEditUserDialog = (u: UserAccount) => { setEditingUser(u); setUserForm({ firstName: u.firstName, lastName: u.lastName, email: u.email, password: '', role: u.role as 'TEACHER' | 'SCHOOL_ADMIN' | 'SUPER_ADMIN' | 'STUDENT' | 'PARENT', }); setShowUserDialog(true); }; const handleSaveUser = async () => { try { if (editingUser) { await updateUser({ id: editingUser.id, firstName: userForm.firstName, lastName: userForm.lastName, email: userForm.email, role: userForm.role, }); toast.success(t('users.updated')); } else { if (userForm.password.length < 8) { toast.error(t('users.password_hint')); return; } await createUser({ schoolId: currentUser?.schoolId ?? null, email: userForm.email, password: userForm.password, firstName: userForm.firstName, lastName: userForm.lastName, role: userForm.role, }); toast.success(t('users.created')); } setShowUserDialog(false); setUserForm({ firstName: '', lastName: '', email: '', password: '', role: 'TEACHER' }); loadUsers(); } catch (err) { toast.error(err instanceof Error ? err.message : t('error.generic')); } }; const handleDeleteUser = async () => { if (!deleteUserId) return; try { await deleteUser(deleteUserId); toast.success(t('users.deleted')); setDeleteUserId(null); loadUsers(); } catch (err) { toast.error(err instanceof Error ? err.message : t('error.generic')); } }; const roleBadge = (role: string) => { if (role === 'SUPER_ADMIN') return 'bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-300'; if (role === 'SCHOOL_ADMIN') return 'bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300'; return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300'; }; const roleLabel = (role: string) => { if (role === 'SUPER_ADMIN') return t('role.super_admin'); if (role === 'SCHOOL_ADMIN') return t('role.school_admin'); return t('role.teacher'); }; if (!isAdmin) { return (

{t('settings.title')} — {t('role.teacher')}

{t('error.forbidden')}

); } return ( {/* Header */}

{t('settings.title')}

{t('app.subtitle')}

{/* Quick stats banner */}
{[ { icon: School, label: t('polish.quick_stats'), value: schools.length, color: 'from-violet-50 to-violet-100/50 dark:from-violet-900/20 dark:to-violet-800/10', text: 'text-violet-700 dark:text-violet-300', iconBg: 'bg-gradient-to-br from-violet-400 to-violet-500', border: 'border-violet-200/40 dark:border-violet-900/30', tab: 'school' }, { icon: Users, label: t('polish.total_users'), value: users.length, color: 'from-emerald-50 to-emerald-100/50 dark:from-emerald-900/20 dark:to-emerald-800/10', text: 'text-emerald-700 dark:text-emerald-300', iconBg: 'bg-gradient-to-br from-emerald-400 to-teal-500', border: 'border-emerald-200/40 dark:border-emerald-900/30', tab: 'users' }, { icon: BookOpen, label: t('polish.total_subjects'), value: subjects.length, color: 'from-amber-50 to-amber-100/50 dark:from-amber-900/20 dark:to-amber-800/10', text: 'text-amber-700 dark:text-amber-300', iconBg: 'bg-gradient-to-br from-amber-400 to-amber-500', border: 'border-amber-200/40 dark:border-amber-900/30', tab: 'subjects' }, { icon: Calendar, label: t('polish.total_years'), value: schoolYears.length, color: 'from-teal-50 to-teal-100/50 dark:from-teal-900/20 dark:to-teal-800/10', text: 'text-teal-700 dark:text-teal-300', iconBg: 'bg-gradient-to-br from-teal-400 to-emerald-500', border: 'border-teal-200/40 dark:border-teal-900/30', tab: 'years' }, { icon: Activity, label: t('settings.tab_audit'), value: auditEntries.length, color: 'from-slate-50 to-slate-100/50 dark:from-slate-800/40 dark:to-slate-700/10', text: 'text-slate-700 dark:text-slate-300', iconBg: 'bg-gradient-to-br from-slate-400 to-slate-500', border: 'border-slate-200/40 dark:border-slate-700/30', tab: 'audit' }, ].map((stat) => (
setActiveTab(stat.tab)}>

{stat.label}

{stat.value}

))}
{/* Tabs */} {t('settings.tab_school')} {t('settings.tab_years')} {t('settings.tab_subjects')} {t('settings.tab_users')} {t('settings.tab_audit')} {t('settings.tab_data')} {t('branding.tab')} {t('email.tab')} {isAdmin && ( {t('settings.demo_accounts')} )} {t('backup.title')} {isSuperAdmin && ( {t('district.title')} )} {t('rate_limit.status_title')} {t('badges.manage')} {t('dsgvo.tab_privacy')} {/* ── School Info Tab ─────────────────────────────────── */}
{t('settings.tab_school')}
{t('settings.school_name')} & {t('settings.school_type')}
{schoolLoading ? (
) : selectedSchool ? (
setSchoolForm({ ...schoolForm, name: e.target.value })} className="border-emerald-200 dark:border-emerald-900/30 focus:border-emerald-500 rounded-xl" />
setSchoolForm({ ...schoolForm, country: e.target.value })} className="border-emerald-200 dark:border-emerald-900/30 focus:border-emerald-500 rounded-xl" />
setSchoolForm({ ...schoolForm, timezone: e.target.value })} className="border-emerald-200 dark:border-emerald-900/30 focus:border-emerald-500 rounded-xl" />
{selectedSchool._count && (
{selectedSchool._count.users} {t('label.teacher') || 'Lehrkräfte'} {selectedSchool._count.classGroups} {t('label.class')} {selectedSchool._count.students} {t('label.student') || 'Schüler'}
)}
) : (

{t('dashboard.no_classes')}

)}
{/* Notification Sound Setting */}
{/* ── School Years Tab ────────────────────────────────── */}
{t('settings.tab_years')}
{t('label.school_year')}
{yearsLoading ? (
{Array.from({ length: 3 }).map((_, i) => )}
) : schoolYears.length === 0 ? (

{t('settings.audit_no_entries')}

) : (
{schoolYears.map((yr) => { const yearWithCount = yr as SchoolYear & { _count?: { classGroups: number; enrollments: number } }; return (

{yr.label}

{new Date(yr.startDate).toLocaleDateString()} — {new Date(yr.endDate).toLocaleDateString()}

{yearWithCount._count && ( <> {yearWithCount._count.classGroups} {t('settings.year_classes')} {yearWithCount._count.enrollments} {t('settings.year_enrollments')} )}
); })}
)} {/* Create Year Dialog */} {t('action.create')} {t('label.school_year')} {t('settings.year_label')}
setYearForm({ ...yearForm, label: e.target.value })} placeholder="2025/2026" className="rounded-xl" />
setYearForm({ ...yearForm, startDate: e.target.value })} className="rounded-xl" />
setYearForm({ ...yearForm, endDate: e.target.value })} className="rounded-xl" />
{/* ── Subjects Tab ────────────────────────────────────── */}
{t('settings.tab_subjects')}
{t('label.subject')}
{subjectsLoading ? (
{Array.from({ length: 3 }).map((_, i) => )}
) : subjects.length === 0 ? (

{t('settings.audit_no_entries')}

) : (
{subjects.map((sub) => (
{sub.name.slice(0, 2).toUpperCase()}

{sub.name}

{t('label.grade')} {sub.gradeLevelMin}–{sub.gradeLevelMax} · {sub.schoolId ? t('settings.subject_school') : t('settings.subject_global')}

))}
)} {/* Create/Edit Subject Dialog */} { if (!open) { setShowCreateSubject(false); setEditingSubject(null); setSubjectForm({ name: '', gradeLevelMin: 1, gradeLevelMax: 13 }); } }}> {editingSubject ? t('action.edit') : t('action.create')} {t('label.subject')}
setSubjectForm({ ...subjectForm, name: e.target.value })} className="rounded-xl" />
setSubjectForm({ ...subjectForm, gradeLevelMin: parseInt(e.target.value) || 1 })} className="rounded-xl" />
setSubjectForm({ ...subjectForm, gradeLevelMax: parseInt(e.target.value) || 13 })} className="rounded-xl" />
{/* Delete Subject Alert */} { if (!open) setDeleteSubjectId(null); }}> {t('action.delete')} {t('label.subject')} {t('settings.subject_deleted')} setDeleteSubjectId(null)}>{t('action.cancel')} {t('action.confirm')}
{/* ── Users Tab ──────────────────────────────────────── */}
{t('users.title')}
{t('users.subtitle')}
{t('users.count', { count: users.length })}
{usersLoading ? (
{Array.from({ length: 4 }).map((_, i) => )}
) : users.length === 0 ? (

{t('users.no_users')}

) : (
{users.map((u) => (
{u.firstName[0]}{u.lastName[0]}

{u.firstName} {u.lastName}

{roleLabel(u.role)} {u.deletedAt && ( {t('users.inactive')} )}
{u.email}
{/* Assigned classes */}
{u.classGroupTeachers && u.classGroupTeachers.length > 0 ? ( u.classGroupTeachers.map((ct) => ( {ct.classGroup.name} ({ct.role === 'HOMEROOM_TEACHER' ? t('classes.homeroom_teacher') : t('classes.subject_teacher')}) )) ) : ( {t('users.no_classes_assigned')} )}
))}
)} {/* Create/Edit User Dialog */} { if (!open) { setShowUserDialog(false); setEditingUser(null); setUserForm({ firstName: '', lastName: '', email: '', password: '', role: 'TEACHER' }); } }}> {editingUser ? t('users.edit_user') : t('users.create_user')} {t('users.subtitle')}
setUserForm({ ...userForm, firstName: e.target.value })} className="rounded-xl border-rose-200/50 dark:border-rose-900/30" />
setUserForm({ ...userForm, lastName: e.target.value })} className="rounded-xl border-rose-200/50 dark:border-rose-900/30" />
setUserForm({ ...userForm, email: e.target.value })} className="rounded-xl border-rose-200/50 dark:border-rose-900/30" />
{!editingUser && (
setUserForm({ ...userForm, password: e.target.value })} placeholder="••••••••" className="rounded-xl border-rose-200/50 dark:border-rose-900/30" />

{t('users.password_hint')}

)}
{/* Delete User Alert */} { if (!open) setDeleteUserId(null); }}> {t('users.delete_user')} {t('users.delete_confirm')} setDeleteUserId(null)}>{t('action.cancel')} {t('action.confirm')} {/* Create Student Account Dialog */} { if (!open) setShowStudentAccountDialog(false); }}> {t('settings.create_student_account')} {t('auth.student_login_desc')}
setStudentAccountForm({ ...studentAccountForm, firstName: e.target.value })} className="rounded-xl" />
setStudentAccountForm({ ...studentAccountForm, lastName: e.target.value })} className="rounded-xl" />
setStudentAccountForm({ ...studentAccountForm, email: e.target.value })} className="rounded-xl" placeholder="name@schule.de" />
setStudentAccountForm({ ...studentAccountForm, password: e.target.value })} className="rounded-xl" />
{availableStudents.length > 0 && (
)}
{/* Bulk Create Student Accounts Dialog */} { if (!open) setShowBulkStudentDialog(false); }}> {t('settings.bulk_create_students')} {t('auth.bulk_create')}
setBulkForm({ ...bulkForm, defaultPassword: e.target.value })} className="rounded-xl" />
setBulkForm({ ...bulkForm, emailDomain: e.target.value })} className="rounded-xl" placeholder="schule.de" />
{availableStudents.map((s) => ( ))}
{/* ── Audit Log Tab ───────────────────────────────────── */}
{t('settings.audit_title')}
{t('settings.audit_action')} & {t('settings.audit_timestamp')}
{/* Search input */}
setAuditSearch(e.target.value)} placeholder={t('polish.audit_search')} className="pl-9 h-9 rounded-lg border-violet-200/50 dark:border-violet-900/30 bg-violet-50/20 dark:bg-violet-900/10" />
{/* Color-coded action filter chips */}
{[ { value: 'all', label: t('polish.all_actions'), color: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300 ring-gray-300 dark:ring-gray-700' }, { value: 'CREATE', label: t('polish.create_action'), color: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300 ring-emerald-300 dark:ring-emerald-700' }, { value: 'UPDATE', label: t('polish.update_action'), color: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300 ring-amber-300 dark:ring-amber-700' }, { value: 'DELETE', label: t('polish.delete_action'), color: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300 ring-red-300 dark:ring-red-700' }, { value: 'LOGIN', label: t('polish.login_action'), color: 'bg-teal-100 text-teal-700 dark:bg-teal-900/30 dark:text-teal-300 ring-teal-300 dark:ring-teal-700' }, { value: 'EXPORT', label: t('action.export'), color: 'bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300 ring-violet-300 dark:ring-violet-700' }, ].map((chip) => ( ))}
{/* Entity type filter */}
{(() => { const entityTypes = Array.from(new Set(auditEntries.map((e) => e.entityType))).sort(); return [ { value: 'all', label: t('audit.all_entity_types') }, ...entityTypes.map((et) => ({ value: et, label: et })), ].map((chip) => ( )); })()}
{/* Date filters */}
{ setAuditDateFrom(e.target.value); setAuditPage(1); }} className="h-8 w-36 rounded-lg text-xs border-violet-200 dark:border-violet-900/30" />
{ setAuditDateTo(e.target.value); setAuditPage(1); }} className="h-8 w-36 rounded-lg text-xs border-violet-200 dark:border-violet-900/30" />
{/* Timeline */} {auditLoading ? (
{Array.from({ length: 5 }).map((_, i) => )}
) : auditEntries.length === 0 ? (

{t('settings.audit_no_entries')}

) : (() => { const searchLower = auditSearch.trim().toLowerCase(); const filtered = searchLower ? auditEntries.filter((e) => { const actorName = e.user ? `${e.user.firstName} ${e.user.lastName}`.toLowerCase() : 'system'; return ( e.entityType.toLowerCase().includes(searchLower) || e.action.toLowerCase().includes(searchLower) || actorName.includes(searchLower) || (e.entityId ?? '').toLowerCase().includes(searchLower) ); }) : auditEntries; if (filtered.length === 0) { return (

{t('polish.no_results')}

); } return (
{/* Vertical timeline line */}
{filtered.map((entry, i) => { const colorClass = actionColors[entry.action] || 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-300'; const actorName = entry.user ? `${entry.user.firstName} ${entry.user.lastName}` : 'System'; const dotColor = entry.action === 'CREATE' ? 'bg-emerald-500' : entry.action === 'UPDATE' ? 'bg-amber-500' : entry.action === 'DELETE' ? 'bg-red-500' : entry.action === 'LOGIN' ? 'bg-teal-500' : entry.action === 'EXPORT' ? 'bg-violet-500' : 'bg-gray-400'; const iconBg = entry.action === 'CREATE' ? 'bg-emerald-100 dark:bg-emerald-900/30 text-emerald-600 dark:text-emerald-300' : entry.action === 'UPDATE' ? 'bg-amber-100 dark:bg-amber-900/30 text-amber-600 dark:text-amber-300' : entry.action === 'DELETE' ? 'bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-300' : entry.action === 'LOGIN' ? 'bg-teal-100 dark:bg-teal-900/30 text-teal-600 dark:text-teal-300' : 'bg-violet-100 dark:bg-violet-900/30 text-violet-600 dark:text-violet-300'; return ( setAuditDetailEntry(entry)} > {/* Timeline dot */}
{entry.action === 'CREATE' ? : entry.action === 'UPDATE' ? : entry.action === 'DELETE' ? : entry.action === 'LOGIN' ? : entry.action === 'EXPORT' ? : }
{entry.action} {entry.entityType} {entry.entityId && ( {entry.entityId.slice(0, 8)}… )}
{new Date(entry.timestamp).toLocaleString()}
{actorName === 'System' ? 'S' : actorName.split(' ').map((n) => n[0]).join('').slice(0, 2)}
{actorName}
{entry.ipAddress && ( IP: {entry.ipAddress} )}
); })}
); })()} {/* Pagination */} {auditTotalPages > 1 && (
{t('audit.page')} {auditPage} {t('audit.of')} {auditTotalPages}
)} {/* Audit Detail Dialog */} { if (!o) setAuditDetailEntry(null); }}> {auditDetailEntry && ( <> {t('audit.detail')} {auditDetailEntry.action} - {auditDetailEntry.entityType}

{auditDetailEntry.action}

{auditDetailEntry.entityType}

{new Date(auditDetailEntry.timestamp).toLocaleString()}

{auditDetailEntry.entityId ?? '-'}

{auditDetailEntry.ipAddress && (

{auditDetailEntry.ipAddress}

)} {auditDetailEntry.userAgent && (

{auditDetailEntry.userAgent}

)}
{/* Changes before/after */} {auditDetailEntry.changes && (() => { try { const parsed = JSON.parse(auditDetailEntry.changes); return (
{parsed.before && (
                                    {JSON.stringify(parsed.before, null, 2)}
                                  
)} {parsed.after && (
                                    {JSON.stringify(parsed.after, null, 2)}
                                  
)}
); } catch { return

{t('audit.no_changes')}

; } })()}
)}
{/* ── Data Management Tab ─────────────────────────────── */}
{/* Data Import */}
{t('import.title')}
{t('import.csv_upload')}
{/* Import type selector */}
{(['students', 'assessments', 'grades'] as const).map((impType) => { const icons = { students: GraduationCap, assessments: ClipboardCheck, grades: BarChart3 }; const Icon = icons[impType]; return ( ); })}
{/* Drag and drop zone */}
{ e.preventDefault(); setDragOver(true); }} onDragLeave={() => setDragOver(false)} onDrop={(e) => { e.preventDefault(); setDragOver(false); const file = e.dataTransfer.files[0]; if (file && file.name.endsWith('.csv')) { setImportFile(file); setImportResult(null); } else { toast.error('Please upload a CSV file'); } }} >

{importFile ? importFile.name : t('import.csv_upload')}

Drag & drop or click to select

{ const file = e.target.files?.[0]; if (file) { setImportFile(file); setImportResult(null); } }} />
{/* Import button */}
{/* Import result */} {importResult && (

{t('import.summary')}

{importResult.created}

{t('import.success')}

{importResult.skipped}

Skipped

{importResult.errorCount}

{t('import.errors')}

{importResult.detectedColumns.length > 0 && (
{importResult.detectedColumns.map((col) => ( {col} ))}
)} {importResult.errors.length > 0 && (
{importResult.errors.slice(0, 5).map((err, i) => (

{err}

))}
)}
)}
{/* Enhanced Data Export */}
{t('export.enhanced_title')}
{t('export.enhanced_desc')}
{/* Export format & filters */}
setExportDateFrom(e.target.value)} className="border-emerald-200 dark:border-emerald-900/30 rounded-xl" />
setExportDateTo(e.target.value)} className="border-emerald-200 dark:border-emerald-900/30 rounded-xl" />
{/* Export cards */}
{[ { type: 'students' as const, iconComponent: GraduationCap, label: t('export.students'), desc: t('export.grade_report'), color: 'from-emerald-50/60 to-emerald-100/0 dark:from-emerald-900/15 dark:to-emerald-900/0', border: 'border-emerald-200/40 dark:border-emerald-900/30', iconBg: 'bg-gradient-to-br from-emerald-400 to-emerald-500' }, { type: 'progress' as const, iconComponent: TrendingUp, label: t('export.progress'), desc: t('export.competency_report'), color: 'from-amber-50/60 to-amber-100/0 dark:from-amber-900/15 dark:to-amber-900/0', border: 'border-amber-200/40 dark:border-amber-900/30', iconBg: 'bg-gradient-to-br from-amber-400 to-amber-500' }, { type: 'assessments' as const, iconComponent: ClipboardCheck, label: t('export.assessments'), desc: t('export.attendance_report'), color: 'from-teal-50/60 to-teal-100/0 dark:from-teal-900/15 dark:to-teal-900/0', border: 'border-teal-200/40 dark:border-teal-900/30', iconBg: 'bg-gradient-to-br from-teal-400 to-teal-500' }, { type: 'grades' as const, iconComponent: BarChart3, label: t('export.grades'), desc: t('export.grade_report'), color: 'from-violet-50/60 to-violet-100/0 dark:from-violet-900/15 dark:to-violet-900/0', border: 'border-violet-200/40 dark:border-violet-900/30', iconBg: 'bg-gradient-to-br from-violet-400 to-violet-500' }, { type: 'attendance' as const, iconComponent: CalendarDays, label: t('export.attendance'), desc: t('export.attendance_report'), color: 'from-rose-50/60 to-rose-100/0 dark:from-rose-900/15 dark:to-rose-900/0', border: 'border-rose-200/40 dark:border-rose-900/30', iconBg: 'bg-gradient-to-br from-rose-400 to-rose-500' }, ].map((item) => ( handleCsvExport(item.type)} >

{item.label}

{item.desc}

{exportFormat.toUpperCase()}
))}
{/* Progress indicator */} {exporting && (

{t('export.progress_indicator')}

)} {/* Export history */} {exportHistory.length > 0 && (

{t('export.download_history')}

{exportHistory.map((item, idx) => (
{item.type} {item.format?.toUpperCase() || 'CSV'}
{item.date}
))}
)}
{/* GDPR Data Erasure — Danger Zone */}

{t('settings.data_erasure')}

{t('settings.data_erasure_desc')}

{t('settings.data_erasure_desc')}

{/* Erasure Dialog */} {t('settings.data_erasure')} {t('settings.data_erasure_desc')}
{t('action.cancel')} {t('settings.data_erasure_confirm')}
{/* ── Demo Accounts Tab ───────────────────────────────── */} {isAdmin && ( {/* Demo Data Danger Zone Card */}
{t('settings.demo_data_title')}
{t('settings.demo_data_desc')}
{/* Demo record count */}

{t('settings.demo_record_count')}

{demoAccounts.length} {t('settings.demo_record_count').toLowerCase()}

{/* Big red Delete All Demo Data button */} {/* Environmental-friendly messaging */}

{t('settings.env_tip')}

{/* Demo Accounts Card */}
{t('settings.demo_accounts')}
{t('settings.demo_accounts_desc')}
{/* Info hint */}

{t('settings.demo_toggle_hint')}

{/* Action buttons */}
{/* Demo accounts list */} {demoLoading ? (
{Array.from({ length: 3 }).map((_, i) => )}
) : demoAccounts.length === 0 ? (

{t('settings.demo_no_accounts')}

) : (
{(demoAccounts ?? []).map((account) => { const isActive = !account.deletedAt; const RoleIcon = getRoleIcon(account.role); return (

{account.firstName} {account.lastName}

{account.email}

{account.role} {isActive ? t('settings.demo_active') : t('settings.demo_inactive')}
handleToggleDemoAccount(account.id, account.deletedAt)} className="data-[state=checked]:bg-emerald-500 data-[state=unchecked]:bg-gray-300 dark:data-[state=unchecked]:bg-gray-600" />
); })}
)} {/* Delete single demo account confirmation */} { if (!open) setDeleteDemoId(null); }}> {t('action.delete')} {t('settings.demo_delete_one_confirm')} {t('action.cancel')} deleteDemoId && handleDeleteDemoAccount(deleteDemoId)} className="bg-rose-600 hover:bg-rose-700 text-white"> {t('action.delete')} {/* Delete all demo accounts confirmation */} {t('settings.demo_delete_all')} {t('settings.demo_delete_confirm')} {t('action.cancel')} {t('action.delete')}
)} {/* ── Backup Tab ─────────────────────────────────────────── */}
{t('backup.title')}
{t('backup.auto')}
{/* Create backup + Auto-backup toggle */}
{autoBackupEnabled && ( )}
{/* Last backup info */} {backups.length > 0 && backups[0].status === 'completed' && (
{t('backup.last')}: {new Date(backups[0].createdAt).toLocaleString()}
)} {/* Backup list */} {backupLoading ? (
{Array.from({ length: 3 }).map((_, i) => )}
) : backups.length === 0 ? (

{t('backup.no_backups')}

{t('backup.no_backups_desc')}

) : (
{backups.map((backup) => { const sizeKB = Math.round(backup.size / 1024); const sizeMB = (backup.size / (1024 * 1024)).toFixed(1); const sizeDisplay = backup.size > 1024 * 1024 ? `${sizeMB} MB` : `${sizeKB} KB`; const statusColor = backup.status === 'completed' ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300' : backup.status === 'failed' ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300' : 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300'; const statusText = backup.status === 'completed' ? t('backup.completed') : backup.status === 'failed' ? t('backup.failed') : t('backup.pending'); return (

{backup.filename}

{new Date(backup.createdAt).toLocaleString()} {statusText} {backup.type === 'full' ? t('backup.type_full') : t('backup.type_incremental')} {t('backup.size')}: {sizeDisplay}
{backup.notes && (

{backup.notes}

)}
); })}
)}
{/* ── Backup Restore Confirmation ──────────────────────────── */} { if (!open) setRestoreBackupId(null); }}> {t('backup.restore_confirm')} {t('backup.restore_confirm_desc')} {t('action.cancel')} {t('backup.restore')} {/* ── Backup Delete Confirmation ───────────────────────────── */} { if (!open) setDeleteBackupId(null); }}> {t('backup.delete_confirm')} {t('backup.delete_confirm_desc')} {t('action.cancel')} deleteBackupId && handleDeleteBackup(deleteBackupId)} className="bg-rose-600 hover:bg-rose-700 text-white"> {t('action.delete')} {/* ── District Tab (SUPER_ADMIN only) ─────────────────────────── */} {isSuperAdmin && (
{/* District Management Card */}
{t('district.title')}
{districtsLoading ? (
{Array.from({ length: 3 }).map((_, i) => )}
) : districts.length === 0 ? (

{t('district.no_districts')}

{t('district.no_districts_desc')}

) : (
{districts.map((district) => ( loadDistrictSchoolsData(district.id)} >

{district.name}

{district.code && ( {district.code} )} {district.region && ( {district.region} )}
{t('district.school_count')}: {district.schools.length} {district.isActive ? ( {t('schedules.active')} ) : ( {t('schedules.inactive')} )}
))}
)}
{/* District Schools Detail */} {selectedDistrictId && (
{t('district.schools')} {districts.find((d) => d.id === selectedDistrictId) && ( · {districts.find((d) => d.id === selectedDistrictId)!.name} )}
{/* Assign school */}
{/* Schools list */} {districtSchoolsLoading ? (
{Array.from({ length: 2 }).map((_, i) => )}
) : districtSchools.length === 0 ? (

{t('district.no_schools_assigned')}

) : (
{districtSchools.map((school) => (

{school.name}

{t('district.total_students')}: {school._count.students} {t('district.total_classes')}: {school._count.classGroups} {t('district.total_teachers')}: {school._count.users}
))}
)}
)} {/* Cross-School Comparison */} {districts.length > 0 && (
{t('district.cross_school_comparison')}
{districts.map((district) => { const totalStudents = district.schools.length; const maxSchools = Math.max(...districts.map((d) => d.schools.length), 1); const pct = Math.round((totalStudents / maxSchools) * 100); return (

{district.name}

{district.schools.length} {t('district.schools')} {totalStudents === maxSchools && districts.length > 1 && ( {t('district.top_performing')} )}
); })}
)}
)} {/* ── District Create/Edit Dialog ─────────────────────────────── */} { if (!open) { setDistrictDialogOpen(false); setEditingDistrict(null); } }}> {editingDistrict ? t('district.edit') : t('district.create')}
setDistrictForm({ ...districtForm, name: e.target.value })} className="rounded-xl border-violet-200/50 dark:border-violet-900/30" placeholder={t('district.name')} />
setDistrictForm({ ...districtForm, code: e.target.value })} className="rounded-xl border-violet-200/50 dark:border-violet-900/30" placeholder="DE-NW-001" />
setDistrictForm({ ...districtForm, region: e.target.value })} className="rounded-xl border-violet-200/50 dark:border-violet-900/30" placeholder="Nordrhein-Westfalen" />
setDistrictForm({ ...districtForm, country: e.target.value })} className="rounded-xl border-violet-200/50 dark:border-violet-900/30" />
setDistrictForm({ ...districtForm, adminEmail: e.target.value })} className="rounded-xl border-violet-200/50 dark:border-violet-900/30" placeholder="admin@bezirk.de" />
{/* ── District Delete Confirmation ────────────────────────────── */} { if (!open) setDeleteDistrictId(null); }}> {t('district.confirm_delete')} {t('district.confirm_delete_desc')} {t('action.cancel')} deleteDistrictId && handleDeleteDistrict(deleteDistrictId)} className="bg-rose-600 hover:bg-rose-700 text-white"> {t('action.delete')} {/* ── Rate Limit Tab ─────────────────────────────────────────── */}
{t('rate_limit.status_title')}
{t('rate_limit.auth_limit')} · {t('rate_limit.data_limit')} · {t('rate_limit.write_limit')} · {t('rate_limit.heavy_limit')}
{/* ── Branding Tab ───────────────────────────────────────────── */}
{t('branding.title')}
{t('branding.colors')} · {t('branding.typography')} · {t('branding.contact')}
{/* Logo */}
setBrandingForm({ ...brandingForm, logoUrl: e.target.value })} placeholder={t('branding.logo_placeholder')} className="border-teal-200 dark:border-teal-900/30 rounded-xl" /> {brandingForm.logoUrl && (
Logo preview { (e.target as HTMLImageElement).style.display = 'none'; }} /> {t('branding.preview')}
)}
{/* Colors */}

{t('branding.colors')}

setBrandingForm({ ...brandingForm, primaryColor: e.target.value })} className="w-10 h-10 rounded-lg border border-teal-200 dark:border-teal-900/30 cursor-pointer min-h-[44px]" /> setBrandingForm({ ...brandingForm, primaryColor: e.target.value })} className="border-teal-200 dark:border-teal-900/30 rounded-xl text-sm" />
setBrandingForm({ ...brandingForm, secondaryColor: e.target.value })} className="w-10 h-10 rounded-lg border border-teal-200 dark:border-teal-900/30 cursor-pointer min-h-[44px]" /> setBrandingForm({ ...brandingForm, secondaryColor: e.target.value })} className="border-teal-200 dark:border-teal-900/30 rounded-xl text-sm" />
setBrandingForm({ ...brandingForm, accentColor: e.target.value })} className="w-10 h-10 rounded-lg border border-teal-200 dark:border-teal-900/30 cursor-pointer min-h-[44px]" /> setBrandingForm({ ...brandingForm, accentColor: e.target.value })} className="border-teal-200 dark:border-teal-900/30 rounded-xl text-sm" />
{/* Typography */}
{/* Motto */}
setBrandingForm({ ...brandingForm, motto: e.target.value })} placeholder={t('branding.motto_placeholder')} className="border-teal-200 dark:border-teal-900/30 rounded-xl" />
{/* Contact info */}

{t('branding.contact')}

setBrandingForm({ ...brandingForm, websiteUrl: e.target.value })} placeholder={t('branding.website_placeholder')} className="border-teal-200 dark:border-teal-900/30 rounded-xl" />
setBrandingForm({ ...brandingForm, emailDomain: e.target.value })} placeholder={t('branding.email_domain_placeholder')} className="border-teal-200 dark:border-teal-900/30 rounded-xl" />
setBrandingForm({ ...brandingForm, address: e.target.value })} className="border-teal-200 dark:border-teal-900/30 rounded-xl" />
setBrandingForm({ ...brandingForm, phone: e.target.value })} className="border-teal-200 dark:border-teal-900/30 rounded-xl" />
{/* Custom CSS */}