diff --git a/admin-compliance/app/sdk/academy/[id]/_components/CourseHeader.tsx b/admin-compliance/app/sdk/academy/[id]/_components/CourseHeader.tsx new file mode 100644 index 0000000..2a7d230 --- /dev/null +++ b/admin-compliance/app/sdk/academy/[id]/_components/CourseHeader.tsx @@ -0,0 +1,50 @@ +'use client' + +import Link from 'next/link' +import { Course, COURSE_CATEGORY_INFO } from '@/lib/sdk/academy/types' + +interface CourseHeaderProps { + course: Course + onDelete: () => void +} + +export function CourseHeader({ course, onDelete }: CourseHeaderProps) { + const categoryInfo = COURSE_CATEGORY_INFO[course.category] || COURSE_CATEGORY_INFO['custom'] + + return ( +
+
+ + + + + +
+
+ + {categoryInfo.label} + + + {course.status === 'published' ? 'Veroeffentlicht' : 'Entwurf'} + +
+

{course.title}

+

{course.description}

+
+
+
+ +
+
+ ) +} diff --git a/admin-compliance/app/sdk/academy/[id]/_components/CourseStats.tsx b/admin-compliance/app/sdk/academy/[id]/_components/CourseStats.tsx new file mode 100644 index 0000000..3fd4bd0 --- /dev/null +++ b/admin-compliance/app/sdk/academy/[id]/_components/CourseStats.tsx @@ -0,0 +1,33 @@ +'use client' + +import { Course, Lesson, Enrollment } from '@/lib/sdk/academy/types' + +interface CourseStatsProps { + course: Course + sortedLessons: Lesson[] + enrollments: Enrollment[] + completedEnrollments: number +} + +export function CourseStats({ course, sortedLessons, enrollments, completedEnrollments }: CourseStatsProps) { + return ( +
+
+
Lektionen
+
{sortedLessons.length}
+
+
+
Dauer
+
{course.durationMinutes} Min.
+
+
+
Teilnehmer
+
{enrollments.length}
+
+
+
Abgeschlossen
+
{completedEnrollments}
+
+
+ ) +} diff --git a/admin-compliance/app/sdk/academy/[id]/_components/CourseTabs.tsx b/admin-compliance/app/sdk/academy/[id]/_components/CourseTabs.tsx new file mode 100644 index 0000000..d2ede1f --- /dev/null +++ b/admin-compliance/app/sdk/academy/[id]/_components/CourseTabs.tsx @@ -0,0 +1,37 @@ +'use client' + +type TabId = 'overview' | 'lessons' | 'enrollments' | 'videos' + +interface CourseTabsProps { + activeTab: TabId + onTabChange: (tab: TabId) => void +} + +const TAB_LABELS: Record = { + overview: 'Uebersicht', + lessons: 'Lektionen', + enrollments: 'Einschreibungen', + videos: 'Videos', +} + +export function CourseTabs({ activeTab, onTabChange }: CourseTabsProps) { + return ( +
+ +
+ ) +} diff --git a/admin-compliance/app/sdk/academy/[id]/_components/EnrollmentsTab.tsx b/admin-compliance/app/sdk/academy/[id]/_components/EnrollmentsTab.tsx new file mode 100644 index 0000000..85383db --- /dev/null +++ b/admin-compliance/app/sdk/academy/[id]/_components/EnrollmentsTab.tsx @@ -0,0 +1,59 @@ +'use client' + +import { Enrollment, ENROLLMENT_STATUS_INFO, isEnrollmentOverdue, getDaysUntilDeadline } from '@/lib/sdk/academy/types' + +interface EnrollmentsTabProps { + enrollments: Enrollment[] + overdueEnrollments: number +} + +export function EnrollmentsTab({ enrollments, overdueEnrollments }: EnrollmentsTabProps) { + return ( +
+ {overdueEnrollments > 0 && ( +
+ {overdueEnrollments} ueberfaellige Einschreibung(en) +
+ )} + {enrollments.length === 0 ? ( +
+

Noch keine Einschreibungen fuer diesen Kurs.

+
+ ) : ( + enrollments.map(enrollment => { + const statusInfo = ENROLLMENT_STATUS_INFO[enrollment.status] + const overdue = isEnrollmentOverdue(enrollment) + const daysUntil = getDaysUntilDeadline(enrollment.deadline) + return ( +
+
+
+
+ + {statusInfo?.label} + + {overdue && Ueberfaellig} +
+
{enrollment.userName}
+
{enrollment.userEmail}
+
+
+
{enrollment.progress}%
+
+ {enrollment.status === 'completed' ? 'Abgeschlossen' : `${daysUntil > 0 ? daysUntil + ' Tage verbleibend' : Math.abs(daysUntil) + ' Tage ueberfaellig'}`} +
+
+
+
+
+
+
+ ) + }) + )} +
+ ) +} diff --git a/admin-compliance/app/sdk/academy/[id]/_components/LessonsTab.tsx b/admin-compliance/app/sdk/academy/[id]/_components/LessonsTab.tsx new file mode 100644 index 0000000..c511b87 --- /dev/null +++ b/admin-compliance/app/sdk/academy/[id]/_components/LessonsTab.tsx @@ -0,0 +1,239 @@ +'use client' + +import { Lesson, QuizQuestion } from '@/lib/sdk/academy/types' + +interface LessonsTabProps { + sortedLessons: Lesson[] + selectedLesson: Lesson | null + onSelectLesson: (lesson: Lesson) => void + quizAnswers: Record + onQuizAnswer: (answers: Record) => void + quizResult: any + isSubmittingQuiz: boolean + onSubmitQuiz: () => void + onResetQuiz: () => void + isEditing: boolean + editTitle: string + editContent: string + onEditTitle: (v: string) => void + onEditContent: (v: string) => void + isSaving: boolean + saveMessage: { type: 'success' | 'error'; text: string } | null + onStartEdit: () => void + onCancelEdit: () => void + onSaveLesson: () => void + onApproveLesson: () => void +} + +export function LessonsTab({ + sortedLessons, + selectedLesson, + onSelectLesson, + quizAnswers, + onQuizAnswer, + quizResult, + isSubmittingQuiz, + onSubmitQuiz, + onResetQuiz, + isEditing, + editTitle, + editContent, + onEditTitle, + onEditContent, + isSaving, + saveMessage, + onStartEdit, + onCancelEdit, + onSaveLesson, + onApproveLesson, +}: LessonsTabProps) { + return ( +
+ {/* Lesson Navigation */} +
+

Lektionen

+
+ {sortedLessons.map((lesson, i) => ( + + ))} +
+
+ + {/* Lesson Content */} +
+ {selectedLesson ? ( +
+
+ {isEditing ? ( + onEditTitle(e.target.value)} + className="text-xl font-semibold text-gray-900 border border-gray-300 rounded-lg px-3 py-1 flex-1 mr-3" + /> + ) : ( +

{selectedLesson.title}

+ )} +
+ + {selectedLesson.type === 'quiz' ? 'Quiz' : selectedLesson.type === 'video' ? 'Video' : 'Text'} + + {selectedLesson.type !== 'quiz' && !isEditing && ( + <> + + + + )} + {isEditing && ( + <> + + + + )} +
+
+ + {saveMessage && ( +
+ {saveMessage.text} +
+ )} + + {selectedLesson.type === 'video' && selectedLesson.videoUrl && ( +
+
+ )} + + {isEditing && (selectedLesson.type === 'text' || selectedLesson.type === 'video') && ( +
+ +