/** * Learn Module - JavaScript Controller * Learning Management System for General Bots */ // State management const LearnState = { courses: [], myCourses: [], mandatoryAssignments: [], certificates: [], categories: [], currentCourse: null, currentLesson: null, currentQuiz: null, quizAnswers: {}, quizTimer: null, quizTimeRemaining: 0, currentQuestionIndex: 0, filters: { category: 'all', difficulty: ['beginner', 'intermediate', 'advanced'], search: '', sort: 'recent' }, pagination: { offset: 0, limit: 12, hasMore: true }, userStats: { coursesCompleted: 0, coursesInProgress: 0, certificates: 0, timeSpent: 0 } }; // API Base URL const LEARN_API = '/api/learn'; // ============================================================================ // INITIALIZATION // ============================================================================ document.addEventListener('DOMContentLoaded', () => { initLearn(); }); function initLearn() { loadUserStats(); loadCategories(); loadCourses(); loadMyCourses(); loadMandatoryAssignments(); loadCertificates(); loadRecommendations(); bindEvents(); } function bindEvents() { // Search input const searchInput = document.getElementById('searchCourses'); if (searchInput) { let searchTimeout; searchInput.addEventListener('input', (e) => { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { LearnState.filters.search = e.target.value; LearnState.pagination.offset = 0; loadCourses(); }, 300); }); } // Sort select const sortSelect = document.getElementById('sortCourses'); if (sortSelect) { sortSelect.addEventListener('change', (e) => { LearnState.filters.sort = e.target.value; LearnState.pagination.offset = 0; loadCourses(); }); } // Category filters document.querySelectorAll('.category-item').forEach(item => { item.addEventListener('click', () => { document.querySelectorAll('.category-item').forEach(i => i.classList.remove('active')); item.classList.add('active'); LearnState.filters.category = item.dataset.category; LearnState.pagination.offset = 0; loadCourses(); }); }); // Difficulty filters document.querySelectorAll('[data-difficulty]').forEach(checkbox => { checkbox.addEventListener('change', () => { LearnState.filters.difficulty = Array.from( document.querySelectorAll('[data-difficulty]:checked') ).map(cb => cb.dataset.difficulty); LearnState.pagination.offset = 0; loadCourses(); }); }); // Close modals on background click document.querySelectorAll('.modal').forEach(modal => { modal.addEventListener('click', (e) => { if (e.target === modal) { modal.classList.add('hidden'); } }); }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { closeAllModals(); } }); } // ============================================================================ // API CALLS // ============================================================================ async function apiCall(endpoint, options = {}) { try { const response = await fetch(`${LEARN_API}${endpoint}`, { headers: { 'Content-Type': 'application/json', ...options.headers }, ...options }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'API request failed'); } return data; } catch (error) { console.error('API Error:', error); showNotification('error', 'Erro ao carregar dados. Tente novamente.'); throw error; } } async function loadUserStats() { try { const response = await apiCall('/stats/user'); if (response.success) { LearnState.userStats = response.data; updateUserStatsUI(); } } catch (error) { // Use mock data if API fails updateUserStatsUI(); } } async function loadCategories() { try { const response = await apiCall('/categories'); if (response.success) { LearnState.categories = response.data; updateCategoryCounts(); } } catch (error) { // Categories loaded from HTML } } async function loadCourses() { try { const params = new URLSearchParams({ limit: LearnState.pagination.limit, offset: LearnState.pagination.offset }); if (LearnState.filters.category !== 'all') { params.append('category', LearnState.filters.category); } if (LearnState.filters.search) { params.append('search', LearnState.filters.search); } if (LearnState.filters.difficulty.length < 3) { params.append('difficulty', LearnState.filters.difficulty.join(',')); } const response = await apiCall(`/courses?${params}`); if (response.success) { if (LearnState.pagination.offset === 0) { LearnState.courses = response.data; } else { LearnState.courses = [...LearnState.courses, ...response.data]; } LearnState.pagination.hasMore = response.data.length >= LearnState.pagination.limit; renderCourses(); } } catch (error) { // Render mock courses for demo renderMockCourses(); } } async function loadMyCourses() { try { const response = await apiCall('/progress'); if (response.success) { LearnState.myCourses = response.data; renderMyCourses(); } } catch (error) { renderMockMyCourses(); } } async function loadMandatoryAssignments() { try { const response = await apiCall('/assignments/pending'); if (response.success) { LearnState.mandatoryAssignments = response.data; renderMandatoryAssignments(); updateMandatoryAlert(); } } catch (error) { renderMockMandatory(); } } async function loadCertificates() { try { const response = await apiCall('/certificates'); if (response.success) { LearnState.certificates = response.data; renderCertificates(); } } catch (error) { renderMockCertificates(); } } async function loadRecommendations() { try { const response = await apiCall('/recommendations'); if (response.success) { renderRecommendations(response.data); } } catch (error) { renderMockRecommendations(); } } async function loadCourseDetail(courseId) { try { const response = await apiCall(`/courses/${courseId}`); if (response.success) { LearnState.currentCourse = response.data; renderCourseModal(response.data); } } catch (error) { showMockCourseDetail(courseId); } } async function startCourseAPI(courseId) { try { const response = await apiCall(`/progress/${courseId}/start`, { method: 'POST' }); if (response.success) { showNotification('success', 'Curso iniciado com sucesso!'); loadMyCourses(); return response.data; } } catch (error) { showNotification('error', 'Erro ao iniciar o curso.'); } } async function completeLessonAPI(lessonId) { try { const response = await apiCall(`/progress/${lessonId}/complete`, { method: 'POST' }); if (response.success) { showNotification('success', 'Aula concluída!'); return response.data; } } catch (error) { showNotification('error', 'Erro ao marcar aula como concluída.'); } } async function submitQuizAPI(courseId, answers) { try { const response = await apiCall(`/courses/${courseId}/quiz`, { method: 'POST', body: JSON.stringify({ answers }) }); if (response.success) { return response.data; } } catch (error) { showNotification('error', 'Erro ao enviar respostas.'); } } // ============================================================================ // UI RENDERING // ============================================================================ function updateUserStatsUI() { document.getElementById('statCoursesCompleted').textContent = LearnState.userStats.courses_completed || 0; document.getElementById('statCoursesInProgress').textContent = LearnState.userStats.courses_in_progress || 0; document.getElementById('statCertificates').textContent = LearnState.userStats.certificates_earned || 0; document.getElementById('statTimeSpent').textContent = `${LearnState.userStats.total_time_spent_hours || 0}h`; } function updateCategoryCounts() { // Update category counts based on courses const counts = {}; LearnState.courses.forEach(course => { counts[course.category] = (counts[course.category] || 0) + 1; }); document.getElementById('countAll').textContent = LearnState.courses.length; document.getElementById('countMandatory').textContent = LearnState.mandatoryAssignments.length; } function renderCourses() { const grid = document.getElementById('coursesGrid'); const countLabel = document.getElementById('coursesCountLabel'); if (!grid) return; if (LearnState.pagination.offset === 0) { grid.innerHTML = ''; } if (LearnState.courses.length === 0) { grid.innerHTML = `
Tente ajustar os filtros de busca.
Nenhum curso em andamento
Nenhum curso concluído ainda
Você não possui treinamentos obrigatórios pendentes.
Complete seus cursos para ganhar certificados.
Nenhum certificado ainda
Explore o catálogo para encontrar cursos.
'; return; } carousel.innerHTML = courses.slice(0, 6).map(course => { const card = createCourseCard(course); card.style.minWidth = '280px'; return card.outerHTML; }).join(''); } // ============================================================================ // MODALS // ============================================================================ function openCourseModal(courseId) { loadCourseDetail(courseId); document.getElementById('courseModal').classList.remove('hidden'); } function closeCourseModal() { document.getElementById('courseModal').classList.add('hidden'); LearnState.currentCourse = null; } function renderCourseModal(data) { const { course, lessons, quiz } = data; document.getElementById('modalCourseTitle').textContent = course.title; document.getElementById('modalDescription').textContent = course.description || 'Sem descrição disponível.'; document.getElementById('modalDifficulty').textContent = formatDifficulty(course.difficulty); document.getElementById('modalDifficulty').className = `difficulty-badge ${(course.difficulty || 'beginner').toLowerCase()}`; document.getElementById('modalDuration').querySelector('span').textContent = formatDuration(course.duration_minutes); document.getElementById('modalLessonsCount').querySelector('span').textContent = `${lessons?.length || 0} aulas`; // Render lessons const lessonsList = document.getElementById('modalLessonsList'); if (lessons && lessons.length > 0) { lessonsList.innerHTML = lessons.map((lesson, index) => `Nenhuma aula disponível.
'; } // Render quiz section const quizSection = document.getElementById('modalQuizSection'); if (quiz) { quizSection.style.display = 'block'; const questions = typeof quiz.questions === 'string' ? JSON.parse(quiz.questions) : (quiz.questions || []); document.getElementById('modalQuizQuestions').textContent = `${questions.length} questões`; document.getElementById('modalQuizTime').textContent = quiz.time_limit_minutes ? `${quiz.time_limit_minutes} min` : 'Sem limite'; document.getElementById('modalQuizPassing').textContent = `${quiz.passing_score}% para aprovação`; LearnState.currentQuiz = quiz; } else { quizSection.style.display = 'none'; } // Update button text based on progress const startBtn = document.getElementById('startCourseBtn'); if (data.user_progress) { const progress = data.user_progress; if (progress.status === 'completed') { startBtn.innerHTML = ` Revisar Curso `; } else if (progress.status === 'in_progress') { startBtn.innerHTML = ` Continuar `; } // Show progress document.getElementById('modalProgress').style.display = 'block'; document.getElementById('modalProgressFill').style.width = `${progress.completion_percentage || 0}%`; document.getElementById('modalProgressText').textContent = `${progress.completion_percentage || 0}% completo`; } else { document.getElementById('modalProgress').style.display = 'none'; startBtn.innerHTML = ` Iniciar Curso `; } } function startCourse() { if (!LearnState.currentCourse) return; const course = LearnState.currentCourse.course || LearnState.currentCourse; const lessons = LearnState.currentCourse.lessons || []; // Start course via API startCourseAPI(course.id); // Open first lesson if (lessons.length > 0) { openLesson(lessons[0].id, 0); } else { showNotification('info', 'Este curso ainda não possui aulas.'); } } function openLesson(lessonId, index) { const lessons = LearnState.currentCourse?.lessons || []; const lesson = lessons.find(l => l.id === lessonId) || lessons[index]; if (!lesson) { showNotification('error', 'Aula não encontrada.'); return; } LearnState.currentLesson = lesson; LearnState.currentLessonIndex = index; // Close course modal, open lesson modal closeCourseModal(); document.getElementById('lessonModal').classList.remove('hidden'); // Update lesson UI document.getElementById('lessonTitle').textContent = lesson.title; document.getElementById('lessonNavTitle').textContent = `Aula ${index + 1} de ${lessons.length}`; // Render content based on type const contentDiv = document.getElementById('lessonContent'); if (lesson.video_url) { contentDiv.innerHTML = `Conteúdo da aula será exibido aqui.
'}