"use client"; import { useEffect, useState } from "react"; import { lmsApi, Lesson, Course, Module, UserGrade } from "@/lib/api"; import Link from "next/link"; import { ChevronLeft, ChevronRight, Menu, CheckCircle2 } from "lucide-react"; import { useAuth } from "@/context/AuthContext"; import DescriptionPlayer from "@/components/blocks/DescriptionPlayer"; import MediaPlayer from "@/components/blocks/MediaPlayer"; import QuizPlayer from "@/components/blocks/QuizPlayer"; import FillInTheBlanksPlayer from "@/components/blocks/FillInTheBlanksPlayer"; import MatchingPlayer from "@/components/blocks/MatchingPlayer"; import OrderingPlayer from "@/components/blocks/OrderingPlayer"; import ShortAnswerPlayer from "@/components/blocks/ShortAnswerPlayer"; import CodeExercisePlayer from "@/components/blocks/CodeExercisePlayer"; import HotspotPlayer from "@/components/blocks/HotspotPlayer"; import MemoryPlayer from "@/components/blocks/MemoryPlayer"; import DocumentPlayer from "@/components/blocks/DocumentPlayer"; import AudioResponsePlayer from "@/components/blocks/AudioResponsePlayer"; import PeerReviewPlayer from "@/components/blocks/PeerReviewPlayer"; import InteractiveTranscript from "@/components/InteractiveTranscript"; import AITutor from "@/components/AITutor"; import LessonLockedView from "@/components/LessonLockedView"; import StudentNotes from "@/components/StudentNotes"; import { ListMusic, StickyNote } from "lucide-react"; export default function LessonPlayerPage({ params }: { params: { id: string, lessonId: string } }) { const [lesson, setLesson] = useState(null); const [course, setCourse] = useState<(Course & { modules: Module[] }) | null>(null); const [loading, setLoading] = useState(true); const [sidebarOpen, setSidebarOpen] = useState(true); const [transcriptOpen, setTranscriptOpen] = useState(true); const [notesOpen, setNotesOpen] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [userGrade, setUserGrade] = useState(null); const [allGrades, setAllGrades] = useState([]); const { user } = useAuth(); useEffect(() => { const fetchAll = async () => { try { const [lessonData, outlineData] = await Promise.all([ lmsApi.getLesson(params.lessonId), lmsApi.getCourseOutline(params.id) ]); setLesson(lessonData); setCourse({ ...outlineData.course, modules: outlineData.modules }); if (user) { const grades = await lmsApi.getUserGrades(user.id, params.id); setAllGrades(grades); const currentGrade = grades.find((g: UserGrade) => g.lesson_id === params.lessonId); setUserGrade(currentGrade || null); } } catch (err) { console.error("Error al cargar los datos de la lección", err); } finally { setLoading(false); } }; fetchAll(); }, [params.id, params.lessonId, user]); if (loading) return
Cargando Experiencia...
; if (!lesson || !course) return
Contenido no encontrado.
; const allLessons = course.modules.flatMap(m => m.lessons); const currentIndex = allLessons.findIndex(l => l.id === params.lessonId); const prevLesson = allLessons[currentIndex - 1]; const nextLesson = allLessons[currentIndex + 1]; const hasTranscription = lesson.transcription && lesson.transcription.cues && lesson.transcription.cues.length > 0 && !(lesson.metadata?.blocks || []).some(b => b.type === 'media' && b.config?.show_transcript === false); const handleSeek = (time: number) => { const videoElement = document.querySelector('video'); if (videoElement) { videoElement.currentTime = time; videoElement.play(); } }; const handleBlockComplete = async (blockId: string, score: number) => { if (user) { try { // Update the score for the specific block in metadata const currentBlockScores = (userGrade?.metadata?.block_scores as Record) || {}; const newBlockScores = { ...currentBlockScores, [blockId]: score }; // Calculate overall lesson score as average of block scores const blocks = lesson.metadata?.blocks || []; const interactiveBlocks = blocks.filter((b: any) => !['description', 'media', 'document'].includes(b.type) ); const scores = interactiveBlocks.map((b: any) => b.id === blockId ? score : currentBlockScores[b.id] || 0 ); const newOverallScore = scores.length > 0 ? (scores.reduce((a: number, b: number) => a + b, 0) / scores.length) * 100 : 100; const res = await lmsApi.submitScore( user.id, params.id, params.lessonId, newOverallScore, { ...userGrade?.metadata, block_scores: newBlockScores } ); setUserGrade(res); setAllGrades(prev => { const idx = prev.findIndex(g => g.lesson_id === params.lessonId); if (idx >= 0) { const newGrades = [...prev]; newGrades[idx] = res; return newGrades; } return [...prev, res]; }); console.log(`Score for block ${blockId} submitted: ${score}`); } catch (err) { console.error(`Failed to submit score for block ${blockId}`, err); } } }; const getLessonStatus = (l: Lesson) => { const grade = allGrades.find(g => g.lesson_id === l.id); const isCurrent = l.id === params.lessonId; // Condition for Completed (Green) // 1. Quizzes/Tests answered completely (score exists) // 2. Non-repeatable lessons finished if (grade && grade.attempts_count > 0) { if (l.content_type === 'quiz' || l.content_type === 'activity' || !l.allow_retry) { return 'completed'; // Green } if (l.allow_retry) { return 'repeatable'; // Red } } if (isCurrent || (grade && grade.attempts_count > 0)) { return 'in-progress'; // Yellow } return 'not-started'; }; const getModuleStatus = (m: Module) => { const statuses = m.lessons.map(l => getLessonStatus(l)); if (statuses.every(s => s === 'completed')) return 'completed'; if (statuses.some(s => s === 'in-progress' || s === 'completed' || s === 'repeatable')) return 'in-progress'; return 'not-started'; }; const getStatusColor = (status: string) => { switch (status) { case 'completed': return 'bg-green-500 shadow-[0_0_10px_rgba(34,197,94,0.5)]'; case 'in-progress': return 'bg-yellow-500 shadow-[0_0_10px_rgba(234,179,8,0.5)]'; case 'repeatable': return 'bg-red-500 shadow-[0_0_10px_rgba(239,68,68,0.5)]'; default: return 'bg-white/10'; } }; return (
{/* Navigation Sidebar */} {/* Main Content Area */}
{hasTranscription && ( )}
{lesson.content_type === 'activity' ? 'Actividad Interactiva' : 'Lección en Video'}
{getLessonStatus(lesson) === 'completed' && "Completada"} {getLessonStatus(lesson) === 'in-progress' && "En Curso"} {getLessonStatus(lesson) === 'repeatable' && "Repetible"} {getLessonStatus(lesson) === 'not-started' && "No Iniciada"}

{lesson.title}

{lesson.summary && (

Resumen

"{lesson.summary}"

)} {/* Render Blocks or Locked View */} {lesson.is_graded && userGrade && lesson.max_attempts && userGrade.attempts_count >= lesson.max_attempts ? ( ) : ( (lesson.metadata?.blocks || []).length > 0 ? (
{lesson.metadata?.blocks?.map((block) => { const renderBlock = () => { switch (block.type) { case 'description': return ; case 'media': return ( )[block.id] || 0 : 0 } onPlay={async () => { if (user && lesson.max_attempts && (!userGrade || userGrade.attempts_count < lesson.max_attempts)) { const currentPlayCounts = (userGrade?.metadata?.play_counts as Record) || {}; const newPlayCounts = { ...currentPlayCounts, [block.id]: (currentPlayCounts[block.id] || 0) + 1 }; try { const res = await lmsApi.submitScore( user.id, params.id, params.lessonId, userGrade?.score || 0, { ...userGrade?.metadata, play_counts: newPlayCounts } ); setUserGrade(res); } catch (err) { console.error("Error al guardar el recuento de reproducciones", err); } } }} isGraded={lesson.is_graded} hasTranscription={!!lesson.transcription} /> ); case 'document': return ; case 'quiz': return ( )[block.id] || 0 : 0 } onAttempt={async () => { if (user) { const currentAttempts = (userGrade?.metadata?.block_attempts as Record) || {}; const newAttempts = { ...currentAttempts, [block.id]: (currentAttempts[block.id] || 0) + 1 }; try { const res = await lmsApi.submitScore( user.id, params.id, params.lessonId, userGrade?.score || 0, { ...userGrade?.metadata, block_attempts: newAttempts } ); setUserGrade(res); } catch (err) { console.error("Error al guardar los intentos del bloque", err); } } }} /> ); case 'fill-in-the-blanks': return ; case 'matching': return ; case 'ordering': return ; case 'short-answer': return ( ); case 'audio-response': return ( handleBlockComplete(block.id, score)} /> ); case 'code': return ( handleBlockComplete(block.id, score)} /> ); case 'hotspot': return ( handleBlockComplete(block.id, score)} /> ); case 'memory-match': return ( handleBlockComplete(block.id, score)} /> ); case 'peer-review': return ( ); default: return
Tipo de Bloque Desconocido: {block.type}
; } }; return (
{renderBlock()}
); })}
) : (

Actualmente, esta lección no tiene contenido.

) )} {lesson.is_graded && (
{userGrade && lesson.max_attempts && userGrade.attempts_count >= lesson.max_attempts ? null : ( <> {lesson.max_attempts && (

Intento {userGrade ? userGrade.attempts_count : 0} de {lesson.max_attempts} usado

)} )}
)}
{/* Right Side Panels */} {((hasTranscription && transcriptOpen) || notesOpen) && ( )}
{/* Footer Controls */}
{prevLesson ? (

Anterior

{prevLesson.title}

) :
}
{allLessons.map((l, i) => (
))}
{currentIndex + 1} OF {allLessons.length} COMPLETADO
{nextLesson ? ( Siguiente Lección ) : ( Finalizar Curso )}
{/* AI Tutor Bubble/Panel */}
); }