From f22caf1cb45a2e8f1a96416616fcd90989037cd8 Mon Sep 17 00:00:00 2001 From: 5t4l1n Date: Tue, 29 Jul 2025 13:52:35 +0530 Subject: [PATCH] course completed up to module lesson --- backend/routes/admin.py | 335 +++++- frontend/app/admin/page.tsx | 951 ++++++++++++++---- .../[courseId]/lesson/[lessonId]/page.tsx | 192 +++- frontend/app/courses/[courseId]/page.tsx | 856 ++++++++++++---- 4 files changed, 1907 insertions(+), 427 deletions(-) diff --git a/backend/routes/admin.py b/backend/routes/admin.py index a69d074..ca82ad0 100644 --- a/backend/routes/admin.py +++ b/backend/routes/admin.py @@ -54,6 +54,14 @@ def admin_required(f): return decorated_function +def serialize_document(doc): + """Convert MongoDB document to JSON-serializable format""" + if doc: + if '_id' in doc: + doc['_id'] = str(doc['_id']) + return doc + return None + def serialize_course(course): """Convert MongoDB document to JSON-serializable format""" if course: @@ -100,11 +108,13 @@ def admin_dashboard(): try: total_courses = db.courses.count_documents({}) total_lessons = db.lessons.count_documents({}) + total_modules = db.modules.count_documents({}) active_students = db.users.count_documents({"status": "active"}) or 2341 stats = { "total_courses": total_courses, "total_lessons": total_lessons, + "total_modules": total_modules, "active_students": active_students, "completion_rate": 78 } @@ -137,7 +147,7 @@ def create_course(): """Create new course""" try: data = request.json - print(f"Creating course with data: {data}") # Debug log + print(f"Creating course with data: {data}") course_id = data.get('id') or f"{data.get('title', '').lower().replace(' ', '-').replace('&', 'and')}-course" @@ -176,10 +186,10 @@ def create_course(): @bp.route("/courses/", methods=["PUT"]) @admin_required def update_course(course_id): - """Update existing course - FIXED VERSION""" + """Update existing course""" try: data = request.json - print(f"Updating course {course_id} with data: {data}") # Debug log + print(f"Updating course {course_id} with data: {data}") update_data = { "title": data.get('title'), @@ -194,14 +204,14 @@ def update_course(course_id): # Remove None values update_data = {k: v for k, v in update_data.items() if v is not None} - print(f"Filtered update data: {update_data}") # Debug log + print(f"Filtered update data: {update_data}") result = db.courses.update_one( {"id": course_id}, {"$set": update_data} ) - print(f"Update result: matched={result.matched_count}, modified={result.modified_count}") # Debug log + print(f"Update result: matched={result.matched_count}, modified={result.modified_count}") if result.matched_count == 0: return jsonify({"error": "Course not found"}), 404 @@ -217,54 +227,323 @@ def update_course(course_id): @bp.route("/courses/", methods=["DELETE"]) @admin_required def delete_course(course_id): - """Delete course""" + """Delete course and all related modules and lessons""" try: - print(f"Deleting course: {course_id}") # Debug log + print(f"Deleting course: {course_id}") + # Delete related lessons first + lesson_result = db.lessons.delete_many({"course_id": course_id}) + print(f"Deleted {lesson_result.deleted_count} related lessons") + + # Delete related modules + module_result = db.modules.delete_many({"course_id": course_id}) + print(f"Deleted {module_result.deleted_count} related modules") + + # Delete the course result = db.courses.delete_one({"id": course_id}) if result.deleted_count == 0: return jsonify({"error": "Course not found"}), 404 - # Also delete related lessons - lesson_result = db.lessons.delete_many({"course_id": course_id}) - print(f"Deleted {lesson_result.deleted_count} related lessons") # Debug log - return jsonify({"success": True, "message": "Course deleted successfully"}) except Exception as e: print(f"Error deleting course: {e}") return jsonify({"error": str(e)}), 500 +# ✅ FIXED: Module Management Endpoints (removed duplicates) +@bp.route("/courses//modules", methods=["GET"]) +@admin_required +def get_course_modules(course_id): + """Get all modules for a specific course""" + try: + print(f"Fetching modules for course: {course_id}") + + modules = list(db.modules.find({"course_id": course_id}).sort("order", 1)) + + # Convert ObjectId to string + for module in modules: + if '_id' in module: + module['id'] = str(module['_id']) + del module['_id'] + + print(f"Found {len(modules)} modules for course {course_id}") + return jsonify({"success": True, "modules": modules}) + + except Exception as e: + print(f"Error fetching modules: {str(e)}") + return jsonify({"error": str(e)}), 500 + @bp.route("/courses//modules", methods=["POST"]) @admin_required -def add_module(course_id): - """Add module to course""" +def create_module(course_id): + """Create a new module for a course""" try: data = request.json + print(f"Creating module for course {course_id} with data: {data}") + + # Verify course exists + course = db.courses.find_one({"id": course_id}) + if not course: + return jsonify({"error": "Course not found"}), 404 module = { - "id": data.get('id') or str(uuid.uuid4()), + "course_id": course_id, "title": data.get('title'), - "lessons": [] + "description": data.get('description', ''), + "order": data.get('order', 1), + "created_at": datetime.now().isoformat(), + "updated_at": datetime.now().isoformat() } - result = db.courses.update_one( - {"id": course_id}, - {"$push": {"modules": module}} + result = db.modules.insert_one(module) + module['id'] = str(result.inserted_id) + if '_id' in module: + del module['_id'] + + print(f"Module created with ID: {result.inserted_id}") + return jsonify({"success": True, "module": module}), 201 + + except Exception as e: + print(f"Error creating module: {str(e)}") + return jsonify({"error": str(e)}), 500 + +@bp.route("/modules/", methods=["GET"]) +@admin_required +def get_module(module_id): + """Get a specific module by ID""" + try: + print(f"Fetching module: {module_id}") + + module = db.modules.find_one({"_id": ObjectId(module_id)}) + + if not module: + return jsonify({"error": "Module not found"}), 404 + + # Convert ObjectId to string + if '_id' in module: + module['id'] = str(module['_id']) + del module['_id'] + + return jsonify({"success": True, "module": module}) + + except Exception as e: + print(f"Error fetching module: {str(e)}") + return jsonify({"error": str(e)}), 500 + +@bp.route("/modules/", methods=["PUT"]) +@admin_required +def update_module(module_id): + """Update an existing module""" + try: + data = request.json + print(f"Updating module {module_id} with data: {data}") + + update_data = { + "title": data.get('title'), + "description": data.get('description'), + "order": data.get('order'), + "updated_at": datetime.now().isoformat() + } + + # Remove None values + update_data = {k: v for k, v in update_data.items() if v is not None} + + result = db.modules.update_one( + {"_id": ObjectId(module_id)}, + {"$set": update_data} ) if result.matched_count == 0: - return jsonify({"error": "Course not found"}), 404 + return jsonify({"error": "Module not found"}), 404 + + # Get updated module + updated_module = db.modules.find_one({"_id": ObjectId(module_id)}) + if updated_module: + updated_module['id'] = str(updated_module['_id']) + del updated_module['_id'] + + return jsonify({"success": True, "module": updated_module}) - return jsonify({"success": True, "module": module}) except Exception as e: + print(f"Error updating module: {str(e)}") return jsonify({"error": str(e)}), 500 +@bp.route("/modules/", methods=["DELETE"]) +@admin_required +def delete_module(module_id): + """Delete a module and all its lessons""" + try: + print(f"Deleting module: {module_id}") + + # Delete related lessons first + lesson_result = db.lessons.delete_many({"module_id": module_id}) + print(f"Deleted {lesson_result.deleted_count} related lessons") + + # Delete the module + result = db.modules.delete_one({"_id": ObjectId(module_id)}) + + if result.deleted_count == 0: + return jsonify({"error": "Module not found"}), 404 + + return jsonify({"success": True, "message": "Module deleted successfully"}) + + except Exception as e: + print(f"Error deleting module: {str(e)}") + return jsonify({"error": str(e)}), 500 + +# ✅ FIXED: Lesson Management Endpoints +@bp.route("/modules//lessons", methods=["GET"]) +@admin_required +def get_module_lessons(module_id): + """Get all lessons for a specific module""" + try: + print(f"Fetching lessons for module: {module_id}") + + lessons = list(db.lessons.find({"module_id": module_id}).sort("order", 1)) + + # Convert ObjectId to string + for lesson in lessons: + if '_id' in lesson: + lesson['id'] = str(lesson['_id']) + del lesson['_id'] + + print(f"Found {len(lessons)} lessons for module {module_id}") + return jsonify({"success": True, "lessons": lessons}) + + except Exception as e: + print(f"Error fetching lessons: {str(e)}") + return jsonify({"error": str(e)}), 500 + +@bp.route("/modules//lessons", methods=["POST"]) +@admin_required +def create_lesson(module_id): + """Create a new lesson for a module""" + try: + data = request.json + print(f"Creating lesson for module {module_id} with data: {data}") + + # Verify module exists + module = db.modules.find_one({"_id": ObjectId(module_id)}) + if not module: + return jsonify({"error": "Module not found"}), 404 + + lesson = { + "module_id": module_id, + "course_id": module.get('course_id'), + "title": data.get('title'), + "description": data.get('description', ''), + "video_url": data.get('video_url'), + "embed_url": convert_to_embed_url(data.get('video_url')) if data.get('video_url') else None, + "order": data.get('order', 1), + "duration": data.get('duration'), + "type": data.get('type', 'video'), + "content": data.get('content', ''), + "created_at": datetime.now().isoformat(), + "updated_at": datetime.now().isoformat() + } + + result = db.lessons.insert_one(lesson) + lesson['id'] = str(result.inserted_id) + if '_id' in lesson: + del lesson['_id'] + + print(f"Lesson created with ID: {result.inserted_id}") + return jsonify({"success": True, "lesson": lesson}), 201 + + except Exception as e: + print(f"Error creating lesson: {str(e)}") + return jsonify({"error": str(e)}), 500 + +@bp.route("/lessons/", methods=["GET"]) +@admin_required +def get_lesson(lesson_id): + """Get a specific lesson by ID""" + try: + print(f"Fetching lesson: {lesson_id}") + + lesson = db.lessons.find_one({"_id": ObjectId(lesson_id)}) + + if not lesson: + return jsonify({"error": "Lesson not found"}), 404 + + # Convert ObjectId to string + if '_id' in lesson: + lesson['id'] = str(lesson['_id']) + del lesson['_id'] + + return jsonify({"success": True, "lesson": lesson}) + + except Exception as e: + print(f"Error fetching lesson: {str(e)}") + return jsonify({"error": str(e)}), 500 + +@bp.route("/lessons/", methods=["PUT"]) +@admin_required +def update_lesson(lesson_id): + """Update an existing lesson""" + try: + data = request.json + print(f"Updating lesson {lesson_id} with data: {data}") + + update_data = { + "title": data.get('title'), + "description": data.get('description'), + "video_url": data.get('video_url'), + "embed_url": convert_to_embed_url(data.get('video_url')) if data.get('video_url') else None, + "order": data.get('order'), + "duration": data.get('duration'), + "type": data.get('type'), + "content": data.get('content'), + "updated_at": datetime.now().isoformat() + } + + # Remove None values + update_data = {k: v for k, v in update_data.items() if v is not None} + + result = db.lessons.update_one( + {"_id": ObjectId(lesson_id)}, + {"$set": update_data} + ) + + if result.matched_count == 0: + return jsonify({"error": "Lesson not found"}), 404 + + # Get updated lesson + updated_lesson = db.lessons.find_one({"_id": ObjectId(lesson_id)}) + if updated_lesson: + updated_lesson['id'] = str(updated_lesson['_id']) + del updated_lesson['_id'] + + return jsonify({"success": True, "lesson": updated_lesson}) + + except Exception as e: + print(f"Error updating lesson: {str(e)}") + return jsonify({"error": str(e)}), 500 + +@bp.route("/lessons/", methods=["DELETE"]) +@admin_required +def delete_lesson(lesson_id): + """Delete a lesson""" + try: + print(f"Deleting lesson: {lesson_id}") + + result = db.lessons.delete_one({"_id": ObjectId(lesson_id)}) + + if result.deleted_count == 0: + return jsonify({"error": "Lesson not found"}), 404 + + return jsonify({"success": True, "message": "Lesson deleted successfully"}) + + except Exception as e: + print(f"Error deleting lesson: {str(e)}") + return jsonify({"error": str(e)}), 500 + +# ✅ LEGACY: For backward compatibility with old course structure @bp.route("/courses//lessons", methods=["POST"]) @admin_required -def add_lesson(course_id): - """Add lesson to course""" +def add_lesson_legacy(course_id): + """Add lesson to course (legacy endpoint)""" try: data = request.json @@ -382,6 +661,7 @@ def get_admin_stats(): try: total_courses = db.courses.count_documents({}) total_lessons = db.lessons.count_documents({}) + total_modules = db.modules.count_documents({}) # Course statistics by subject pipeline = [ @@ -398,6 +678,7 @@ def get_admin_stats(): stats = { "total_courses": total_courses, "total_lessons": total_lessons, + "total_modules": total_modules, "subjects": subjects, "difficulties": difficulties, "last_updated": datetime.now().isoformat() @@ -421,6 +702,16 @@ def admin_health(): "POST /api/admin/courses", "PUT /api/admin/courses/", "DELETE /api/admin/courses/", + "GET /api/admin/courses//modules", + "POST /api/admin/courses//modules", + "GET /api/admin/modules/", + "PUT /api/admin/modules/", + "DELETE /api/admin/modules/", + "GET /api/admin/modules//lessons", + "POST /api/admin/modules//lessons", + "GET /api/admin/lessons/", + "PUT /api/admin/lessons/", + "DELETE /api/admin/lessons/", "POST /api/admin/initialize", "GET /api/admin/test", "GET /api/admin/stats" diff --git a/frontend/app/admin/page.tsx b/frontend/app/admin/page.tsx index e14916c..b3e2e9b 100644 --- a/frontend/app/admin/page.tsx +++ b/frontend/app/admin/page.tsx @@ -1,7 +1,7 @@ 'use client' import React, { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' -import { Plus, Edit, Trash2, Eye, RefreshCw } from 'lucide-react' +import { Plus, Edit, Trash2, Eye, RefreshCw, BookOpen, List, X } from 'lucide-react' interface Course { id: string @@ -16,6 +16,25 @@ interface Course { created_at: string } +interface Module { + id: string + course_id: string + title: string + description: string + order: number + created_at?: string +} + +interface Lesson { + id: string + module_id: string + title: string + description: string + video_url: string + order: number + created_at?: string +} + export default function AdminDashboard() { const [isClient, setIsClient] = useState(false) const [isAuthenticated, setIsAuthenticated] = useState(false) @@ -24,6 +43,16 @@ export default function AdminDashboard() { const [loading, setLoading] = useState(true) const [showAddForm, setShowAddForm] = useState(false) const [editingCourse, setEditingCourse] = useState(null) + + // Module/Lesson Management State + const [showModulesModal, setShowModulesModal] = useState(false) + const [selectedCourse, setSelectedCourse] = useState(null) + const [modules, setModules] = useState([]) + const [lessons, setLessons] = useState([]) + const [modulesLoading, setModulesLoading] = useState(false) + const [lessonsLoading, setLessonsLoading] = useState(false) + const [errorMessage, setErrorMessage] = useState(null) + const [stats, setStats] = useState({ total_courses: 0, total_lessons: 0, @@ -32,54 +61,43 @@ export default function AdminDashboard() { }) const router = useRouter() - // Enhanced authentication with API verification to prevent redirect loops + // Authentication logic useEffect(() => { setIsClient(true) const checkAuth = async () => { try { - // Add delay to prevent race conditions await new Promise(resolve => setTimeout(resolve, 500)) const token = localStorage.getItem('admin_token') - console.log('Dashboard - checking token:', token) if (!token) { - console.log('Dashboard - no token found') router.push('/admin/login') return } if (token === 'admin-secret-key') { - console.log('Dashboard - token format valid, verifying with API...') - try { const response = await fetch('http://127.0.0.1:5000/api/admin/courses', { headers: { 'Authorization': `Bearer ${token}` } }) if (response.ok) { - console.log('✅ Dashboard - API confirms token valid') setIsAuthenticated(true) fetchData() } else { - console.log('❌ Dashboard - API rejects token') localStorage.removeItem('admin_token') router.push('/admin/login') } } catch (apiError) { - console.error('Dashboard - API check failed:', apiError) - // Don't redirect on API error, might be temporary network issue setIsAuthenticated(true) fetchData() } } else { - console.log('Dashboard - invalid token format') localStorage.removeItem('admin_token') router.push('/admin/login') } } catch (error) { - console.error('Dashboard - auth check error:', error) router.push('/admin/login') } finally { setAuthChecked(true) @@ -95,8 +113,6 @@ export default function AdminDashboard() { const fetchCourses = async () => { try { - console.log('Fetching courses...') - const response = await fetch('http://127.0.0.1:5000/api/admin/courses', { headers: { 'Authorization': 'Bearer admin-secret-key', @@ -104,24 +120,18 @@ export default function AdminDashboard() { } }) - console.log('Response status:', response.status) - if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const data = await response.json() - console.log('Received data:', data) if (Array.isArray(data)) { setCourses(data) - console.log('✅ Courses set successfully:', data.length, 'courses') } else { - console.error('❌ API returned non-array data:', data) setCourses([]) } } catch (error) { - console.error('❌ Error fetching courses:', error) setCourses([]) } finally { setLoading(false) @@ -133,13 +143,112 @@ export default function AdminDashboard() { const response = await fetch('http://127.0.0.1:5000/api/admin/dashboard', { headers: { 'Authorization': 'Bearer admin-secret-key' } }) - const data = await response.json() - setStats(data) + if (response.ok) { + const data = await response.json() + setStats(data) + } } catch (error) { - console.error('Error fetching stats:', error) + // Silent fail for stats } } + // ✅ FIXED: Module fetching - the key fix here + const fetchModules = async (courseId: string) => { + setModulesLoading(true) + setErrorMessage(null) + + try { + console.log('🔍 Fetching modules for course:', courseId) // Debug log + + const response = await fetch(`http://127.0.0.1:5000/api/admin/courses/${courseId}/modules`, { + headers: { + 'Authorization': 'Bearer admin-secret-key', + 'Content-Type': 'application/json' + } + }) + + console.log('🔍 Modules response status:', response.status) // Debug log + + if (response.ok) { + const data = await response.json() + console.log('🔍 Modules response data:', data) // Debug log + + // ✅ FIXED: Proper handling of modules response + let modulesList: Module[] = [] + + if (data.modules && Array.isArray(data.modules)) { + modulesList = data.modules + } else if (Array.isArray(data)) { + modulesList = data + } else if (data.success && data.data && Array.isArray(data.data)) { + modulesList = data.data + } + + console.log('🔍 Setting modules:', modulesList) // Debug log + setModules(modulesList) + } else { + console.error('❌ Failed to fetch modules:', response.status) + setModules([]) + setErrorMessage(`Failed to load modules: ${response.status}`) + } + } catch (error) { + console.error('❌ Network error fetching modules:', error) + setModules([]) + setErrorMessage('Network error loading modules') + } finally { + setModulesLoading(false) + } + } + + // ✅ FIXED: Lesson fetching - the key fix here + const fetchLessons = async (moduleId: string) => { + setLessonsLoading(true) + setErrorMessage(null) + + try { + console.log('🔍 Fetching lessons for module:', moduleId) // Debug log + + const response = await fetch(`http://127.0.0.1:5000/api/admin/modules/${moduleId}/lessons`, { + headers: { + 'Authorization': 'Bearer admin-secret-key', + 'Content-Type': 'application/json' + } + }) + + console.log('🔍 Lessons response status:', response.status) // Debug log + + if (response.ok) { + const data = await response.json() + console.log('🔍 Lessons response data:', data) // Debug log + + // ✅ FIXED: Proper handling of lessons response + let lessonsList: Lesson[] = [] + + if (data.lessons && Array.isArray(data.lessons)) { + lessonsList = data.lessons + } else if (Array.isArray(data)) { + lessonsList = data + } else if (data.success && data.data && Array.isArray(data.data)) { + lessonsList = data.data + } + + console.log('🔍 Setting lessons:', lessonsList) // Debug log + setLessons(lessonsList) + } else { + console.error('❌ Failed to fetch lessons:', response.status) + setLessons([]) + setErrorMessage(`Failed to load lessons: ${response.status}`) + } + } catch (error) { + console.error('❌ Network error fetching lessons:', error) + setLessons([]) + setErrorMessage('Network error loading lessons') + } finally { + setLessonsLoading(false) + } + } + + // Course CRUD operations const handleCreateCourse = async (formData: any) => { try { const response = await fetch('http://127.0.0.1:5000/api/admin/courses', { @@ -157,10 +266,9 @@ export default function AdminDashboard() { alert('Course created successfully!') } else { const error = await response.json() - alert(`Error: ${error.error}`) + alert(`Error: ${error.error || 'Failed to create course'}`) } } catch (error) { - console.error('Error creating course:', error) alert('Failed to create course') } } @@ -181,17 +289,15 @@ export default function AdminDashboard() { setEditingCourse(null) alert('Course updated successfully!') } else { - const error = await response.json() - alert(`Error: ${error.error}`) + alert('Failed to update course') } } catch (error) { - console.error('Error updating course:', error) alert('Failed to update course') } } const handleDeleteCourse = async (courseId: string) => { - if (confirm('Are you sure you want to delete this course? This action cannot be undone.')) { + if (confirm('Are you sure you want to delete this course?')) { try { const response = await fetch(`http://127.0.0.1:5000/api/admin/courses/${courseId}`, { method: 'DELETE', @@ -202,29 +308,72 @@ export default function AdminDashboard() { await fetchData() alert('Course deleted successfully!') } else { - const error = await response.json() - alert(`Error: ${error.error}`) + alert('Failed to delete course') } } catch (error) { - console.error('Error deleting course:', error) alert('Failed to delete course') } } } - const initializeDefaultCourses = async () => { + // Module/Lesson Management + const openModulesManager = async (course: Course) => { + console.log('🔍 Opening modules manager for course:', course.id) + setSelectedCourse(course) + setShowModulesModal(true) + setModules([]) + setLessons([]) + setErrorMessage(null) + await fetchModules(course.id) + } + + const handleCreateModule = async (formData: any) => { try { - const response = await fetch('http://127.0.0.1:5000/api/admin/initialize', { + console.log('🔍 Creating module with data:', formData) + + const response = await fetch(`http://127.0.0.1:5000/api/admin/courses/${selectedCourse?.id}/modules`, { method: 'POST', - headers: { 'Authorization': 'Bearer admin-secret-key' } + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer admin-secret-key' + }, + body: JSON.stringify(formData) }) if (response.ok) { - await fetchData() - alert('Default courses initialized!') + await fetchModules(selectedCourse!.id) + alert('Module created successfully!') + } else { + const error = await response.json() + alert(`Error: ${error.error || 'Failed to create module'}`) } } catch (error) { - console.error('Error initializing courses:', error) + alert('Failed to create module') + } + } + + const handleCreateLesson = async (moduleId: string, formData: any) => { + try { + console.log('🔍 Creating lesson for module:', moduleId, 'with data:', formData) + + const response = await fetch(`http://127.0.0.1:5000/api/admin/modules/${moduleId}/lessons`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer admin-secret-key' + }, + body: JSON.stringify(formData) + }) + + if (response.ok) { + await fetchLessons(moduleId) + alert('Lesson created successfully!') + } else { + const error = await response.json() + alert(`Error: ${error.error || 'Failed to create lesson'}`) + } + } catch (error) { + alert('Failed to create lesson') } } @@ -233,7 +382,6 @@ export default function AdminDashboard() { router.push('/') } - // Show loading until auth is checked if (!isClient || !authChecked) { return (
@@ -245,7 +393,6 @@ export default function AdminDashboard() { ) } - // Show redirect message if not authenticated if (!isAuthenticated) { return (
@@ -280,7 +427,7 @@ export default function AdminDashboard() { > - Welcome, 5t4l1n! 👋 + Welcome, Admin! 👋 +
+ )} + {/* Action Buttons */}

Course Management

-

Add, edit, or remove courses dynamically

+

Manage courses, modules, and lessons

-
- ) : !Array.isArray(courses) || courses.length === 0 ? ( + ) : courses.length === 0 ? (
-

No courses found. Initialize default courses or add a new one.

- +

No courses found.

) : (
@@ -419,6 +567,13 @@ export default function AdminDashboard() { > +
@@ -431,7 +586,7 @@ export default function AdminDashboard() {
- {/* Add Course Modal */} + {/* Course Form Modal */} {showAddForm && ( )} - {/* Edit Course Modal */} {editingCourse && ( handleUpdateCourse(editingCourse.id, data)} /> )} + + {/* ✅ FIXED: Modules & Lessons Modal */} + {showModulesModal && selectedCourse && ( + { + setShowModulesModal(false) + setSelectedCourse(null) + setModules([]) + setLessons([]) + setErrorMessage(null) + }} + onCreateModule={handleCreateModule} + onCreateLesson={handleCreateLesson} + onFetchLessons={fetchLessons} + onRefreshModules={() => fetchModules(selectedCourse.id)} + /> + )} ) } -// Course Form Modal Component +// ✅ FIXED: Enhanced Modules & Lessons Modal with better debugging +function ModulesLessonsModal({ + course, + modules, + lessons, + modulesLoading, + lessonsLoading, + onClose, + onCreateModule, + onCreateLesson, + onFetchLessons, + onRefreshModules +}: { + course: Course + modules: Module[] + lessons: Lesson[] + modulesLoading: boolean + lessonsLoading: boolean + onClose: () => void + onCreateModule: (data: any) => void + onCreateLesson: (moduleId: string, data: any) => void + onFetchLessons: (moduleId: string) => void + onRefreshModules: () => void +}) { + const [activeTab, setActiveTab] = useState<'modules' | 'lessons'>('modules') + const [showModuleForm, setShowModuleForm] = useState(false) + const [showLessonForm, setShowLessonForm] = useState(false) + const [selectedModuleId, setSelectedModuleId] = useState(null) + const [moduleFormData, setModuleFormData] = useState({ title: '', description: '', order: 1 }) + const [lessonFormData, setLessonFormData] = useState({ title: '', description: '', video_url: '', order: 1 }) + + const handleModuleSubmit = (e: React.FormEvent) => { + e.preventDefault() + onCreateModule(moduleFormData) + setModuleFormData({ title: '', description: '', order: 1 }) + setShowModuleForm(false) + } + + const handleLessonSubmit = (e: React.FormEvent) => { + e.preventDefault() + console.log('🔍 Creating lesson for module ID:', selectedModuleId) // Debug log + console.log('🔍 Lesson form data:', lessonFormData) // Debug log + if (selectedModuleId) { + onCreateLesson(selectedModuleId, lessonFormData) + setLessonFormData({ title: '', description: '', video_url: '', order: 1 }) + setShowLessonForm(false) + } + } + + // ✅ FIXED: Enhanced module selection with better debugging + const handleSelectModule = (moduleId: string) => { + console.log('🔍 Selecting module with ID:', moduleId) // Debug log + console.log('🔍 Available modules:', modules) // Debug log + setSelectedModuleId(moduleId) + onFetchLessons(moduleId) + setActiveTab('lessons') + } + + return ( +
+
+
+
+

+ Manage: {course.title} +

+ +
+ +
+ +
+ {/* Tab Navigation */} +
+ + +
+ + {/* ✅ DEBUG INFO - Remove after fixing */} +
+

Debug Info:

+

Selected Module ID: {selectedModuleId || 'None'}

+

Modules Count: {modules.length}

+

Lessons Count: {lessons.length}

+

Active Tab: {activeTab}

+
+ + {/* Modules Tab */} + {activeTab === 'modules' && ( +
+
+

Course Modules

+ +
+ + {modulesLoading ? ( +
+
+

Loading modules...

+
+ ) : modules.length === 0 ? ( +
+

No modules found for this course.

+

Click "Add Module" to create your first module.

+
+ ) : ( +
+ {modules.map((module, index) => ( +
+
+
+
{module.title}
+

{module.description}

+

Order: {module.order} | ID: {module.id}

+
+
+ + + +
+
+
+ ))} +
+ )} + + {/* Module Form */} + {showModuleForm && ( +
+
Add New Module
+
+
+ + setModuleFormData({...moduleFormData, title: e.target.value})} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="e.g., Introduction to Python" + /> +
+
+ +