user(); $course = Course::where('slug', $courseSlug) ->with(['modules.lessons']) ->firstOrFail(); // Check enrollment $isEnrolled = Enrollment::where('user_id', $user->id) ->where('course_id', $course->id) ->exists(); if (!$isEnrolled) { return Inertia::render('Errors/Unauthorized', [ 'message' => 'Anda belum terdaftar di kursus ini.' ]); } // Get all lessons for navigation and progress check $allLessons = $course->modules->flatMap->lessons; // Find current lesson with vocabularies $currentLesson = $lessonSlug ? Lesson::where('slug', $lessonSlug)->with('vocabularies')->firstOrFail() : $allLessons->first()->load('vocabularies'); // Navigation Logic (Next/Prev) $currentIndex = $allLessons->search(function ($item) use ($currentLesson) { return $item->id === $currentLesson->id; }); $prevLesson = $currentIndex > 0 ? $allLessons[$currentIndex - 1] : null; $nextLesson = $currentIndex < $allLessons->count() - 1 ? $allLessons[$currentIndex + 1] : null; // Get user progress for this course $completedLessonsIds = UserProgress::where('user_id', $user->id) ->whereIn('lesson_id', $allLessons->pluck('id')) ->whereNotNull('completed_at') ->pluck('lesson_id') ->toArray(); // Initialize started_at if first time viewing UserProgress::firstOrCreate( ['user_id' => $user->id, 'lesson_id' => $currentLesson->id], ['started_at' => now(), 'last_heartbeat_at' => now()] ); return Inertia::render('Courses/Learn', [ 'course' => [ 'id' => $course->id, 'title' => $course->title, 'slug' => $course->slug, 'progress_percentage' => count($allLessons) > 0 ? round((count($completedLessonsIds) / count($allLessons)) * 100) : 0, ], 'modules' => $course->modules->map(function ($module) use ($completedLessonsIds) { return [ 'id' => $module->id, 'title' => $module->title, 'lessons' => $module->lessons->map(function ($lesson) use ($completedLessonsIds) { return [ 'id' => $lesson->id, 'title' => $lesson->title, 'slug' => $lesson->slug, 'type' => $lesson->type, 'duration_seconds' => $lesson->duration_seconds, 'is_completed' => in_array($lesson->id, $completedLessonsIds), ]; }) ]; }), 'lesson' => [ 'id' => $currentLesson->id, 'title' => $currentLesson->title, 'slug' => $currentLesson->slug, 'type' => $currentLesson->type, 'content' => $currentLesson->content, 'video_url' => $currentLesson->video_url, // Prefer Spatie Media Library URL (R2), fallback to legacy column 'content_pdf' => $currentLesson->getFirstMediaUrl('content_pdf') ?: $currentLesson->content_pdf, 'duration_seconds' => $currentLesson->duration_seconds, 'is_completed' => in_array($currentLesson->id, $completedLessonsIds), 'vocabularies' => $currentLesson->vocabularies, 'next_lesson_slug' => $nextLesson?->slug, 'prev_lesson_slug' => $prevLesson?->slug, 'attachments' => $currentLesson->getMedia('attachments')->map(function ($media) { return [ 'id' => $media->uuid, 'name' => $media->file_name, 'url' => $media->getUrl(), 'size' => $media->human_readable_size, ]; }), ], ]); } /** * Mark a lesson as completed. */ public function complete(Request $request, Lesson $lesson) { $user = $request->user(); $alreadyCompleted = UserProgress::where('user_id', $user->id) ->where('lesson_id', $lesson->id) ->exists(); UserProgress::updateOrCreate( ['user_id' => $user->id, 'lesson_id' => $lesson->id], ['completed_at' => now()] ); if (!$alreadyCompleted) { $user->increment('xp_points', 50); } return back()->with('success', 'Materi selesai! +50 XP'); } /** * Update learning duration via heartbeat. */ public function heartbeat(Request $request, Lesson $lesson) { $user = $request->user(); $now = now(); $progress = UserProgress::firstOrCreate( ['user_id' => $user->id, 'lesson_id' => $lesson->id], ['started_at' => $now, 'last_heartbeat_at' => $now] ); $lastHeartbeat = $progress->last_heartbeat_at ?? $progress->created_at; $diffSeconds = $now->diffInSeconds($lastHeartbeat); // Limit diff to prevent massive jumps (e.g. if user leaves tab open and comes back hours later) // Max 90 seconds per heartbeat (assuming heartbeat is every 60s) $increment = min($diffSeconds, 90); $progress->increment('time_spent_seconds', $increment); $progress->update(['last_heartbeat_at' => $now]); return response()->json([ 'status' => 'success', 'time_spent' => $progress->time_spent_seconds, ]); } }