From 653fa07f57c8feda16e9086e6aad403ef9798326 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Thu, 16 Apr 2026 13:14:50 +0200 Subject: [PATCH] refactor(admin): split academy/[id], iace/hazards, ai-act pages Extracted components and constants into _components/ subdirectories to bring all three pages under the 300 LOC soft target (was 651/628/612, now 255/232/278 LOC respectively). Zero behavior changes. Co-Authored-By: Claude Sonnet 4.6 --- .../academy/[id]/_components/CourseHeader.tsx | 50 ++ .../academy/[id]/_components/CourseStats.tsx | 33 ++ .../academy/[id]/_components/CourseTabs.tsx | 37 ++ .../[id]/_components/EnrollmentsTab.tsx | 59 +++ .../academy/[id]/_components/LessonsTab.tsx | 239 +++++++++ .../academy/[id]/_components/OverviewTab.tsx | 48 ++ .../academy/[id]/_components/VideosTab.tsx | 71 +++ .../app/sdk/academy/[id]/page.tsx | 496 ++---------------- .../sdk/ai-act/_components/AISystemCard.tsx | 116 ++++ .../sdk/ai-act/_components/AddSystemForm.tsx | 102 ++++ .../ai-act/_components/LoadingSkeleton.tsx | 25 + .../sdk/ai-act/_components/RiskPyramid.tsx | 39 ++ .../app/sdk/ai-act/_components/types.ts | 12 + admin-compliance/app/sdk/ai-act/page.tsx | 366 +------------ .../hazards/_components/HazardForm.tsx | 129 +++++ .../hazards/_components/HazardTable.tsx | 68 +++ .../hazards/_components/LibraryModal.tsx | 81 +++ .../hazards/_components/RiskBadge.tsx | 11 + .../[projectId]/hazards/_components/types.ts | 92 ++++ .../app/sdk/iace/[projectId]/hazards/page.tsx | 416 +-------------- 20 files changed, 1288 insertions(+), 1202 deletions(-) create mode 100644 admin-compliance/app/sdk/academy/[id]/_components/CourseHeader.tsx create mode 100644 admin-compliance/app/sdk/academy/[id]/_components/CourseStats.tsx create mode 100644 admin-compliance/app/sdk/academy/[id]/_components/CourseTabs.tsx create mode 100644 admin-compliance/app/sdk/academy/[id]/_components/EnrollmentsTab.tsx create mode 100644 admin-compliance/app/sdk/academy/[id]/_components/LessonsTab.tsx create mode 100644 admin-compliance/app/sdk/academy/[id]/_components/OverviewTab.tsx create mode 100644 admin-compliance/app/sdk/academy/[id]/_components/VideosTab.tsx create mode 100644 admin-compliance/app/sdk/ai-act/_components/AISystemCard.tsx create mode 100644 admin-compliance/app/sdk/ai-act/_components/AddSystemForm.tsx create mode 100644 admin-compliance/app/sdk/ai-act/_components/LoadingSkeleton.tsx create mode 100644 admin-compliance/app/sdk/ai-act/_components/RiskPyramid.tsx create mode 100644 admin-compliance/app/sdk/ai-act/_components/types.ts create mode 100644 admin-compliance/app/sdk/iace/[projectId]/hazards/_components/HazardForm.tsx create mode 100644 admin-compliance/app/sdk/iace/[projectId]/hazards/_components/HazardTable.tsx create mode 100644 admin-compliance/app/sdk/iace/[projectId]/hazards/_components/LibraryModal.tsx create mode 100644 admin-compliance/app/sdk/iace/[projectId]/hazards/_components/RiskBadge.tsx create mode 100644 admin-compliance/app/sdk/iace/[projectId]/hazards/_components/types.ts 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') && ( +
+ +