(saved_settings.defaultValidityDays ?? defaults.defaultValidityDays)
+
+ const handleSave = () => {
+ localStorage.setItem(SETTINGS_KEY, JSON.stringify({ emailReminders, reminderDays, defaultPassingScore, defaultValidityDays }))
+ onSaved()
+ }
+
+ return (
+
+ {/* Notifications */}
+
+
Benachrichtigungen
+
+
+
+
E-Mail-Erinnerung bei ueberfaelligen Kursen
+
Mitarbeiter per E-Mail an ausstehende Schulungen erinnern
+
+
setEmailReminders(!emailReminders)}
+ className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${emailReminders ? 'bg-purple-600' : 'bg-gray-200'}`}
+ >
+
+
+
+
+ Tage vor Ablauf erinnern
+ setReminderDays(Number(e.target.value))}
+ className="w-32 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
+ />
+
+
+
+
+ {/* Course Defaults */}
+
+
Standard-Einstellungen fuer neue Kurse
+
+
+
+ {/* Info */}
+
+
+
+
+
+
+ Zertifikate werden automatisch nach erfolgreichem Kursabschluss generiert. Die Gueltigkeitsdauer gilt ab dem Ausstellungsdatum.
+
+
+
+
+
+ {saved ? 'Gespeichert ✓' : 'Einstellungen speichern'}
+
+
+ )
+}
diff --git a/admin-compliance/app/sdk/academy/_components/shared.tsx b/admin-compliance/app/sdk/academy/_components/shared.tsx
new file mode 100644
index 0000000..8198009
--- /dev/null
+++ b/admin-compliance/app/sdk/academy/_components/shared.tsx
@@ -0,0 +1,168 @@
+'use client'
+
+import React from 'react'
+import {
+ CourseCategory,
+ EnrollmentStatus,
+ COURSE_CATEGORY_INFO,
+ ENROLLMENT_STATUS_INFO
+} from '@/lib/sdk/academy/types'
+import { Tab, TabId } from '../_types'
+
+// =============================================================================
+// TAB NAVIGATION
+// =============================================================================
+
+export function TabNavigation({
+ tabs,
+ activeTab,
+ onTabChange
+}: {
+ tabs: Tab[]
+ activeTab: TabId
+ onTabChange: (tab: TabId) => void
+}) {
+ return (
+
+
+ {tabs.map(tab => (
+ onTabChange(tab.id)}
+ className={`
+ px-4 py-3 text-sm font-medium border-b-2 transition-colors
+ ${activeTab === tab.id
+ ? 'border-purple-600 text-purple-600'
+ : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
+ }
+ `}
+ >
+
+ {tab.label}
+ {tab.count !== undefined && tab.count > 0 && (
+
+ {tab.count}
+
+ )}
+
+
+ ))}
+
+
+ )
+}
+
+// =============================================================================
+// STAT CARD
+// =============================================================================
+
+export function StatCard({
+ label,
+ value,
+ color = 'gray',
+ icon,
+ trend
+}: {
+ label: string
+ value: number | string
+ color?: 'gray' | 'blue' | 'yellow' | 'red' | 'green' | 'purple'
+ icon?: React.ReactNode
+ trend?: { value: number; label: string }
+}) {
+ const colorClasses = {
+ gray: 'border-gray-200 text-gray-900',
+ blue: 'border-blue-200 text-blue-600',
+ yellow: 'border-yellow-200 text-yellow-600',
+ red: 'border-red-200 text-red-600',
+ green: 'border-green-200 text-green-600',
+ purple: 'border-purple-200 text-purple-600'
+ }
+
+ return (
+
+
+
+
+ {label}
+
+
+ {value}
+
+ {trend && (
+
= 0 ? 'text-green-600' : 'text-red-600'}`}>
+ {trend.value >= 0 ? '+' : ''}{trend.value} {trend.label}
+
+ )}
+
+ {icon && (
+
+ {icon}
+
+ )}
+
+
+ )
+}
+
+// =============================================================================
+// FILTER BAR
+// =============================================================================
+
+export function FilterBar({
+ selectedCategory,
+ selectedStatus,
+ onCategoryChange,
+ onStatusChange,
+ onClear
+}: {
+ selectedCategory: CourseCategory | 'all'
+ selectedStatus: EnrollmentStatus | 'all'
+ onCategoryChange: (category: CourseCategory | 'all') => void
+ onStatusChange: (status: EnrollmentStatus | 'all') => void
+ onClear: () => void
+}) {
+ const hasFilters = selectedCategory !== 'all' || selectedStatus !== 'all'
+
+ return (
+
+ Filter:
+
+ {/* Category Filter */}
+ onCategoryChange(e.target.value as CourseCategory | 'all')}
+ className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
+ >
+ Alle Kategorien
+ {Object.entries(COURSE_CATEGORY_INFO).map(([cat, info]) => (
+ {info.label}
+ ))}
+
+
+ {/* Enrollment Status Filter */}
+ onStatusChange(e.target.value as EnrollmentStatus | 'all')}
+ className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
+ >
+ Alle Status
+ {Object.entries(ENROLLMENT_STATUS_INFO).map(([status, info]) => (
+ {info.label}
+ ))}
+
+
+ {/* Clear Filters */}
+ {hasFilters && (
+
+ Filter zuruecksetzen
+
+ )}
+
+ )
+}
diff --git a/admin-compliance/app/sdk/academy/_types.ts b/admin-compliance/app/sdk/academy/_types.ts
new file mode 100644
index 0000000..312643d
--- /dev/null
+++ b/admin-compliance/app/sdk/academy/_types.ts
@@ -0,0 +1,12 @@
+// =============================================================================
+// TYPES
+// =============================================================================
+
+export type TabId = 'overview' | 'courses' | 'enrollments' | 'certificates' | 'settings'
+
+export interface Tab {
+ id: TabId
+ label: string
+ count?: number
+ countColor?: string
+}
diff --git a/admin-compliance/app/sdk/academy/page.tsx b/admin-compliance/app/sdk/academy/page.tsx
index 9668984..6e97991 100644
--- a/admin-compliance/app/sdk/academy/page.tsx
+++ b/admin-compliance/app/sdk/academy/page.tsx
@@ -1,7 +1,6 @@
'use client'
import React, { useState, useEffect, useMemo } from 'react'
-import Link from 'next/link'
import { useSDK } from '@/lib/sdk'
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
import {
@@ -10,383 +9,31 @@ import {
Enrollment,
EnrollmentStatus,
AcademyStatistics,
- COURSE_CATEGORY_INFO,
- ENROLLMENT_STATUS_INFO,
isEnrollmentOverdue,
getDaysUntilDeadline
} from '@/lib/sdk/academy/types'
import {
fetchSDKAcademyList, generateAllCourses, fetchCertificates,
- deleteEnrollment, updateEnrollment, updateCourse, completeEnrollment
+ deleteEnrollment, completeEnrollment
} from '@/lib/sdk/academy/api'
import type { Certificate } from '@/lib/sdk/academy/types'
-
-// =============================================================================
-// TYPES
-// =============================================================================
-
-type TabId = 'overview' | 'courses' | 'enrollments' | 'certificates' | 'settings'
-
-interface Tab {
- id: TabId
- label: string
- count?: number
- countColor?: string
-}
-
-// =============================================================================
-// COMPONENTS
-// =============================================================================
-
-function TabNavigation({
- tabs,
- activeTab,
- onTabChange
-}: {
- tabs: Tab[]
- activeTab: TabId
- onTabChange: (tab: TabId) => void
-}) {
- return (
-
-
- {tabs.map(tab => (
- onTabChange(tab.id)}
- className={`
- px-4 py-3 text-sm font-medium border-b-2 transition-colors
- ${activeTab === tab.id
- ? 'border-purple-600 text-purple-600'
- : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
- }
- `}
- >
-
- {tab.label}
- {tab.count !== undefined && tab.count > 0 && (
-
- {tab.count}
-
- )}
-
-
- ))}
-
-
- )
-}
-
-function StatCard({
- label,
- value,
- color = 'gray',
- icon,
- trend
-}: {
- label: string
- value: number | string
- color?: 'gray' | 'blue' | 'yellow' | 'red' | 'green' | 'purple'
- icon?: React.ReactNode
- trend?: { value: number; label: string }
-}) {
- const colorClasses = {
- gray: 'border-gray-200 text-gray-900',
- blue: 'border-blue-200 text-blue-600',
- yellow: 'border-yellow-200 text-yellow-600',
- red: 'border-red-200 text-red-600',
- green: 'border-green-200 text-green-600',
- purple: 'border-purple-200 text-purple-600'
- }
-
- return (
-
-
-
-
- {label}
-
-
- {value}
-
- {trend && (
-
= 0 ? 'text-green-600' : 'text-red-600'}`}>
- {trend.value >= 0 ? '+' : ''}{trend.value} {trend.label}
-
- )}
-
- {icon && (
-
- {icon}
-
- )}
-
-
- )
-}
-
-function CourseCard({ course, enrollmentCount, onEdit }: { course: Course; enrollmentCount: number; onEdit?: (course: Course) => void }) {
- const categoryInfo = COURSE_CATEGORY_INFO[course.category] || COURSE_CATEGORY_INFO['custom']
-
- return (
-
-
-
-
-
-
-
- {categoryInfo.label}
-
- {course.status === 'published' && (
- Veroeffentlicht
- )}
-
-
{course.title}
-
{course.description}
-
-
-
-
-
- {course.lessons.length} Lektionen
-
-
-
-
-
- {course.durationMinutes} Min.
-
-
-
-
-
- {enrollmentCount} Teilnehmer
-
-
-
-
-
- Bestehensgrenze: {course.passingScore}%
-
-
-
-
-
- {course.requiredForRoles.includes('all') ? 'Pflicht fuer alle' : `${course.requiredForRoles.length} Rollen`}
-
-
- {new Date(course.updatedAt).toLocaleDateString('de-DE')}
-
-
-
-
-
- Erstellt: {new Date(course.createdAt).toLocaleDateString('de-DE')}
-
-
- Details
-
-
-
-
- {onEdit && (
-
{ e.preventDefault(); onEdit(course) }}
- className="absolute top-3 right-3 p-1.5 bg-white rounded-lg shadow border border-gray-200 text-gray-400 hover:text-purple-600 hover:border-purple-300 opacity-0 group-hover:opacity-100 transition-all z-10"
- title="Kurs bearbeiten"
- >
-
-
-
-
- )}
-
- )
-}
-
-function EnrollmentCard({ enrollment, courseName, onEdit, onComplete, onDelete }: {
- enrollment: Enrollment
- courseName: string
- onEdit?: (enrollment: Enrollment) => void
- onComplete?: (id: string) => void
- onDelete?: (id: string) => void
-}) {
- const statusInfo = ENROLLMENT_STATUS_INFO[enrollment.status]
- const overdue = isEnrollmentOverdue(enrollment)
- const daysUntil = getDaysUntilDeadline(enrollment.deadline)
-
- return (
-
-
-
- {/* Status Badge */}
-
-
- {statusInfo.label}
-
- {overdue && (
-
-
-
-
- Ueberfaellig
-
- )}
-
-
- {/* User Info */}
-
- {enrollment.userName}
-
-
{enrollment.userEmail}
-
{courseName}
-
- {/* Progress Bar */}
-
-
- Fortschritt
- {enrollment.progress}%
-
-
-
-
-
- {/* Right Side - Deadline */}
-
-
- {enrollment.status === 'completed'
- ? 'Abgeschlossen'
- : overdue
- ? `${Math.abs(daysUntil)} Tage ueberfaellig`
- : `${daysUntil} Tage verbleibend`
- }
-
-
- Frist: {new Date(enrollment.deadline).toLocaleDateString('de-DE')}
-
-
-
-
- {/* Footer */}
-
-
- Gestartet: {new Date(enrollment.startedAt).toLocaleDateString('de-DE')}
- {enrollment.completedAt && (
-
- Abgeschlossen: {new Date(enrollment.completedAt).toLocaleDateString('de-DE')}
-
- )}
-
-
- {enrollment.status === 'in_progress' && onComplete && (
- onComplete(enrollment.id)}
- className="px-3 py-1 text-xs bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
- >
- Abschliessen
-
- )}
- {onEdit && (
- onEdit(enrollment)}
- className="px-3 py-1 text-xs bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
- >
- Bearbeiten
-
- )}
- {onDelete && (
- onDelete(enrollment.id)}
- className="px-3 py-1 text-xs bg-red-50 text-red-600 rounded-lg hover:bg-red-100 transition-colors"
- >
- Loeschen
-
- )}
-
-
-
- )
-}
-
-function FilterBar({
- selectedCategory,
- selectedStatus,
- onCategoryChange,
- onStatusChange,
- onClear
-}: {
- selectedCategory: CourseCategory | 'all'
- selectedStatus: EnrollmentStatus | 'all'
- onCategoryChange: (category: CourseCategory | 'all') => void
- onStatusChange: (status: EnrollmentStatus | 'all') => void
- onClear: () => void
-}) {
- const hasFilters = selectedCategory !== 'all' || selectedStatus !== 'all'
-
- return (
-
- Filter:
-
- {/* Category Filter */}
- onCategoryChange(e.target.value as CourseCategory | 'all')}
- className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
- >
- Alle Kategorien
- {Object.entries(COURSE_CATEGORY_INFO).map(([cat, info]) => (
- {info.label}
- ))}
-
-
- {/* Enrollment Status Filter */}
- onStatusChange(e.target.value as EnrollmentStatus | 'all')}
- className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
- >
- Alle Status
- {Object.entries(ENROLLMENT_STATUS_INFO).map(([status, info]) => (
- {info.label}
- ))}
-
-
- {/* Clear Filters */}
- {hasFilters && (
-
- Filter zuruecksetzen
-
- )}
-
- )
-}
+import { Tab, TabId } from './_types'
+import { TabNavigation, StatCard, FilterBar } from './_components/shared'
+import { CourseCard } from './_components/CourseCard'
+import { EnrollmentCard } from './_components/EnrollmentCard'
+import { CertificatesTab } from './_components/CertificatesTab'
+import { EnrollmentEditModal } from './_components/EnrollmentEditModal'
+import { CourseEditModal } from './_components/CourseEditModal'
+import { SettingsTab } from './_components/SettingsTab'
+import {
+ HeaderActions,
+ GenerationResultBar,
+ LoadingSpinner,
+ OverdueAlert,
+ InfoBox,
+ EmptyCourses,
+ EmptyEnrollments
+} from './_components/PageSections'
// =============================================================================
// MAIN PAGE
@@ -554,53 +201,11 @@ export default function AcademyPage() {
explanation={stepInfo?.explanation}
tips={stepInfo?.tips}
>
-
-
- {isGenerating ? (
-
-
-
-
- ) : (
-
-
-
- )}
- {isGenerating ? 'Generiere...' : 'Alle Kurse generieren'}
-
-
-
-
-
- Kurs erstellen
-
-
+
{/* Generation Result */}
- {generateResult && (
- 0 ? 'bg-yellow-50 border-yellow-200' : 'bg-green-50 border-green-200'}`}>
-
- {generateResult.generated} Kurse generiert
- {generateResult.skipped} uebersprungen
- {generateResult.errors.length > 0 && (
- {generateResult.errors.length} Fehler
- )}
-
- {generateResult.errors.length > 0 && (
-
- {generateResult.errors.map((err, i) =>
{err}
)}
-
- )}
-
- )}
+ {generateResult && }
{/* Tab Navigation */}
-
-
-
-
-
+
) : activeTab === 'settings' ? (
- /* Settings Tab — localStorage-basiert */
{ setSettingsSaved(true); setTimeout(() => setSettingsSaved(false), 2000) }} saved={settingsSaved} />
) : activeTab === 'certificates' ? (
- /* Certificates Tab */
-
- {/* Stats */}
-
- {(() => {
- const now = new Date()
- const total = certificates.length
- const valid = certificates.filter(c => new Date(c.validUntil) > now).length
- const expired = certificates.filter(c => new Date(c.validUntil) <= now).length
- return (
- <>
-
-
-
-
{expired}
-
Abgelaufen
-
- >
- )
- })()}
-
-
- {/* Search */}
-
- setCertSearch(e.target.value)}
- className="w-full px-4 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
- />
-
-
- {/* Table */}
- {certificates.length === 0 ? (
-
-
-
Noch keine Zertifikate ausgestellt
-
Zertifikate werden automatisch nach Kursabschluss generiert.
-
- ) : (
-
-
-
-
- Mitarbeiter
- Kurs
- Ausgestellt am
- Gueltig bis
- Status
- Aktionen
-
-
-
- {certificates
- .filter(c =>
- !certSearch ||
- c.userName.toLowerCase().includes(certSearch.toLowerCase()) ||
- c.courseName.toLowerCase().includes(certSearch.toLowerCase())
- )
- .map(cert => )
- }
-
-
-
- )}
-
+
) : (
<>
{/* Statistics (Overview Tab) */}
@@ -728,51 +251,17 @@ export default function AcademyPage() {
{/* Overdue Alert */}
{tabCounts.overdue > 0 && (
-
-
-
-
- Achtung: {tabCounts.overdue} ueberfaellige Schulung(en)
-
-
- Mitarbeiter haben Pflichtschulungen nicht fristgerecht abgeschlossen. Handeln Sie umgehend.
-
-
-
{
- setActiveTab('enrollments')
- setSelectedStatus('all')
- }}
- className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors text-sm font-medium"
- >
- Anzeigen
-
-
+ {
+ setActiveTab('enrollments')
+ setSelectedStatus('all')
+ }}
+ />
)}
{/* Info Box (Overview Tab) */}
- {activeTab === 'overview' && (
-
-
-
-
-
-
-
Schulungspflicht nach Art. 39 DSGVO
-
- Gemaess Art. 39 Abs. 1 lit. b DSGVO gehoert die Sensibilisierung und Schulung
- der an den Verarbeitungsvorgaengen beteiligten Mitarbeiter zu den Aufgaben des
- Datenschutzbeauftragten. Nachweisbare Compliance-Schulungen sind Pflicht und
- sollten mindestens jaehrlich aufgefrischt werden.
-
-
-
-
- )}
+ {activeTab === 'overview' && }
{/* Filters */}
-
- Keine Kurse gefunden
-
- {selectedCategory !== 'all'
- ? 'Passen Sie die Filter an oder'
- : 'Es sind noch keine Kurse vorhanden.'
- }
-
- {selectedCategory !== 'all' ? (
-
- Filter zuruecksetzen
-
- ) : (
-
-
-
-
- Ersten Kurs erstellen
-
- )}
-
+
)}
{activeTab === 'enrollments' && filteredEnrollments.length === 0 && (
-
-
-
Keine Einschreibungen gefunden
-
- {selectedStatus !== 'all'
- ? 'Passen Sie die Filter an.'
- : 'Es sind noch keine Mitarbeiter in Kurse eingeschrieben.'
- }
-
- {selectedStatus !== 'all' && (
-
- Filter zuruecksetzen
-
- )}
-
+
)}
>
)}
@@ -900,358 +337,3 @@ export default function AcademyPage() {
)
}
-
-// =============================================================================
-// CERTIFICATE ROW COMPONENT
-// =============================================================================
-
-function CertificateRow({ cert }: { cert: Certificate }) {
- const now = new Date()
- const validUntil = new Date(cert.validUntil)
- const daysLeft = Math.ceil((validUntil.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
- const isExpired = daysLeft <= 0
- const isExpiringSoon = daysLeft > 0 && daysLeft <= 30
-
- return (
-
- {cert.userName}
- {cert.courseName}
- {new Date(cert.issuedAt).toLocaleDateString('de-DE')}
- {validUntil.toLocaleDateString('de-DE')}
-
- {isExpired ? (
- Abgelaufen
- ) : isExpiringSoon ? (
- Laeuft bald ab
- ) : (
- Gueltig
- )}
-
-
- {cert.pdfUrl ? (
-
- PDF Download
-
- ) : (
-
- Nicht verfuegbar
-
- )}
-
-
- )
-}
-
-// =============================================================================
-// ENROLLMENT EDIT MODAL
-// =============================================================================
-
-function EnrollmentEditModal({ enrollment, onClose, onSaved }: {
- enrollment: Enrollment
- onClose: () => void
- onSaved: () => void
-}) {
- const [deadline, setDeadline] = useState(enrollment.deadline ? enrollment.deadline.split('T')[0] : '')
- const [saving, setSaving] = useState(false)
- const [error, setError] = useState(null)
-
- const handleSave = async () => {
- setSaving(true)
- setError(null)
- try {
- await updateEnrollment(enrollment.id, { deadline: new Date(deadline).toISOString() })
- onSaved()
- } catch (e) {
- setError(e instanceof Error ? e.message : 'Fehler beim Speichern')
- } finally {
- setSaving(false)
- }
- }
-
- return (
-
-
-
-
Einschreibung bearbeiten
-
-
-
-
-
-
-
-
-
Teilnehmer: {enrollment.userName}
-
-
- Deadline
- setDeadline(e.target.value)}
- className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
- />
-
- {error &&
{error}
}
-
-
-
- Abbrechen
-
-
- {saving ? 'Speichern...' : 'Speichern'}
-
-
-
-
- )
-}
-
-// =============================================================================
-// COURSE EDIT MODAL
-// =============================================================================
-
-function CourseEditModal({ course, onClose, onSaved }: {
- course: Course
- onClose: () => void
- onSaved: () => void
-}) {
- const [title, setTitle] = useState(course.title)
- const [description, setDescription] = useState(course.description)
- const [category, setCategory] = useState(course.category)
- const [durationMinutes, setDurationMinutes] = useState(course.durationMinutes)
- const [passingScore, setPassingScore] = useState(course.passingScore ?? 70)
- const [status, setStatus] = useState<'draft' | 'published'>(course.status ?? 'draft')
- const [saving, setSaving] = useState(false)
- const [error, setError] = useState(null)
-
- const handleSave = async () => {
- setSaving(true)
- setError(null)
- try {
- await updateCourse(course.id, { title, description, category, durationMinutes, passingScore, status })
- onSaved()
- } catch (e) {
- setError(e instanceof Error ? e.message : 'Fehler beim Speichern')
- } finally {
- setSaving(false)
- }
- }
-
- return (
-
-
-
-
Kurs bearbeiten
-
-
-
-
-
-
-
-
- Titel
- setTitle(e.target.value)}
- className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
- />
-
-
- Beschreibung
-
-
-
- Kategorie
- setCategory(e.target.value as CourseCategory)}
- className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
- >
- {Object.entries(COURSE_CATEGORY_INFO).map(([key, info]) => (
- {info.label}
- ))}
-
-
-
- Status
- setStatus(e.target.value as 'draft' | 'published')}
- className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
- >
- Entwurf
- Veroeffentlicht
-
-
-
-
- {error &&
{error}
}
-
-
-
- Abbrechen
-
-
- {saving ? 'Speichern...' : 'Aenderungen speichern'}
-
-
-
-
- )
-}
-
-// =============================================================================
-// SETTINGS TAB
-// =============================================================================
-
-function SettingsTab({ onSaved, saved }: { onSaved: () => void; saved: boolean }) {
- const SETTINGS_KEY = 'bp_academy_settings'
-
- const loadSettings = () => {
- try {
- const raw = localStorage.getItem(SETTINGS_KEY)
- if (raw) return JSON.parse(raw)
- } catch { /* ignore */ }
- return {}
- }
-
- const defaults = { emailReminders: true, reminderDays: 7, defaultPassingScore: 70, defaultValidityDays: 365 }
- const saved_settings = loadSettings()
- const [emailReminders, setEmailReminders] = useState(saved_settings.emailReminders ?? defaults.emailReminders)
- const [reminderDays, setReminderDays] = useState(saved_settings.reminderDays ?? defaults.reminderDays)
- const [defaultPassingScore, setDefaultPassingScore] = useState(saved_settings.defaultPassingScore ?? defaults.defaultPassingScore)
- const [defaultValidityDays, setDefaultValidityDays] = useState(saved_settings.defaultValidityDays ?? defaults.defaultValidityDays)
-
- const handleSave = () => {
- localStorage.setItem(SETTINGS_KEY, JSON.stringify({ emailReminders, reminderDays, defaultPassingScore, defaultValidityDays }))
- onSaved()
- }
-
- return (
-
- {/* Notifications */}
-
-
Benachrichtigungen
-
-
-
-
E-Mail-Erinnerung bei ueberfaelligen Kursen
-
Mitarbeiter per E-Mail an ausstehende Schulungen erinnern
-
-
setEmailReminders(!emailReminders)}
- className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${emailReminders ? 'bg-purple-600' : 'bg-gray-200'}`}
- >
-
-
-
-
- Tage vor Ablauf erinnern
- setReminderDays(Number(e.target.value))}
- className="w-32 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
- />
-
-
-
-
- {/* Course Defaults */}
-
-
Standard-Einstellungen fuer neue Kurse
-
-
-
- {/* Info */}
-
-
-
-
-
-
- Zertifikate werden automatisch nach erfolgreichem Kursabschluss generiert. Die Gueltigkeitsdauer gilt ab dem Ausstellungsdatum.
-
-
-
-
-
- {saved ? 'Gespeichert ✓' : 'Einstellungen speichern'}
-
-
- )
-}