From 8acf1d2e12c82d8b2c2d157349b8a0667096e363 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 26 Feb 2026 17:57:15 +0100 Subject: [PATCH] Add lesson content editor, quiz test endpoint, and lesson update API - Backend: UpdateLesson handler (PUT /lessons/:id) for editing title, content, quiz questions - Backend: TestQuiz handler (POST /lessons/:id/quiz-test) for quiz evaluation without enrollment - Frontend: Content editor with markdown textarea, save, and approve-for-video workflow - Frontend: Fix quiz endpoint to /lessons/:id/quiz-test Co-Authored-By: Claude Opus 4.6 --- .../app/(sdk)/sdk/academy/[id]/page.tsx | 154 ++++++++++++++++-- admin-compliance/lib/sdk/academy/api.ts | 23 ++- ai-compliance-sdk/cmd/server/main.go | 4 + ai-compliance-sdk/internal/academy/store.go | 17 ++ .../internal/api/handlers/academy_handlers.go | 125 ++++++++++++++ 5 files changed, 311 insertions(+), 12 deletions(-) diff --git a/admin-compliance/app/(sdk)/sdk/academy/[id]/page.tsx b/admin-compliance/app/(sdk)/sdk/academy/[id]/page.tsx index 548ad65..e2d122f 100644 --- a/admin-compliance/app/(sdk)/sdk/academy/[id]/page.tsx +++ b/admin-compliance/app/(sdk)/sdk/academy/[id]/page.tsx @@ -18,6 +18,7 @@ import { fetchEnrollments, deleteCourse, submitQuiz, + updateLesson, generateVideos, getVideoStatus } from '@/lib/sdk/academy/api' @@ -39,6 +40,11 @@ export default function CourseDetailPage() { const [isSubmittingQuiz, setIsSubmittingQuiz] = useState(false) const [videoStatus, setVideoStatus] = useState(null) const [isGeneratingVideos, setIsGeneratingVideos] = useState(false) + const [isEditing, setIsEditing] = useState(false) + const [editContent, setEditContent] = useState('') + const [editTitle, setEditTitle] = useState('') + const [isSaving, setIsSaving] = useState(false) + const [saveMessage, setSaveMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null) useEffect(() => { const loadData = async () => { @@ -89,6 +95,65 @@ export default function CourseDetailPage() { } } + const handleStartEdit = () => { + if (!selectedLesson) return + setEditContent(selectedLesson.contentMarkdown || '') + setEditTitle(selectedLesson.title || '') + setIsEditing(true) + setSaveMessage(null) + } + + const handleCancelEdit = () => { + setIsEditing(false) + setSaveMessage(null) + } + + const handleSaveLesson = async () => { + if (!selectedLesson) return + setIsSaving(true) + setSaveMessage(null) + try { + await updateLesson(selectedLesson.id, { + title: editTitle, + content_url: editContent, + }) + const updatedLesson = { ...selectedLesson, title: editTitle, contentMarkdown: editContent } + setSelectedLesson(updatedLesson) + if (course) { + const updatedLessons = course.lessons.map(l => l.id === updatedLesson.id ? updatedLesson : l) + setCourse({ ...course, lessons: updatedLessons }) + } + setIsEditing(false) + setSaveMessage({ type: 'success', text: 'Lektion gespeichert.' }) + } catch (error: any) { + setSaveMessage({ type: 'error', text: error.message || 'Fehler beim Speichern.' }) + } finally { + setIsSaving(false) + } + } + + const handleApproveLesson = async () => { + if (!selectedLesson) return + if (!confirm('Lektion fuer Video-Verarbeitung freigeben? Der Text wird als final markiert.')) return + setIsSaving(true) + setSaveMessage(null) + try { + await updateLesson(selectedLesson.id, { + description: 'approved_for_video', + }) + const updatedLesson = { ...selectedLesson } + if (course) { + const updatedLessons = course.lessons.map(l => l.id === updatedLesson.id ? updatedLesson : l) + setCourse({ ...course, lessons: updatedLessons }) + } + setSaveMessage({ type: 'success', text: 'Lektion fuer Video-Verarbeitung freigegeben.' }) + } catch (error: any) { + setSaveMessage({ type: 'error', text: error.message || 'Fehler bei der Freigabe.' }) + } finally { + setIsSaving(false) + } + } + const handleGenerateVideos = async () => { setIsGeneratingVideos(true) try { @@ -285,16 +350,70 @@ export default function CourseDetailPage() { {selectedLesson ? (
-

{selectedLesson.title}

- - {selectedLesson.type === 'quiz' ? 'Quiz' : selectedLesson.type === 'video' ? 'Video' : 'Text'} - + {isEditing ? ( + setEditTitle(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 && ( + <> + + + + )} +
+ {/* Save/Approve Message */} + {saveMessage && ( +
+ {saveMessage.text} +
+ )} + {/* Video Player */} {selectedLesson.type === 'video' && selectedLesson.videoUrl && (
@@ -306,8 +425,23 @@ export default function CourseDetailPage() {
)} - {/* Text Content */} - {(selectedLesson.type === 'text' || selectedLesson.type === 'video') && selectedLesson.contentMarkdown && ( + {/* Text Content - Edit Mode */} + {isEditing && (selectedLesson.type === 'text' || selectedLesson.type === 'video') && ( +
+ +