document.addEventListener('DOMContentLoaded', () => { const state = { dashboard: null, courses: [], tasks: [], grades: [], schedule: [], projects: [], exams: [] }; const UI = { loading: document.getElementById('loading'), tabs: document.querySelectorAll('.nav-link'), tabContents: document.querySelectorAll('.tab-content'), title: document.getElementById('topbar-title') }; async function init() { try { const res = await fetch(`/api/student/dashboard`); if (res.status === 401) { window.location.href = '/login'; return; } if (!res.ok) throw new Error('API not reachable'); state.dashboard = await res.json(); updateHeader(); await switchTab('inicio'); UI.loading.style.display = 'none'; UI.tabs.forEach(btn => { btn.addEventListener('click', () => { switchTab(btn.getAttribute('data-tab')); }); }); } catch (err) { UI.loading.textContent = 'Error al cargar los datos. Verifica la API.'; UI.loading.className = 'alert alert-error'; } } function updateHeader() { const d = state.dashboard; // Topbar document.getElementById('user-name-display').textContent = d.name; document.getElementById('user-level-display').textContent = d.level_name; document.getElementById('user-avatar-initial').textContent = d.name.charAt(0); // Profile Header (Gamification) const phName = document.getElementById('ph-name'); if (phName) { phName.textContent = d.name; document.getElementById('ph-avatar').textContent = d.name.charAt(0); document.getElementById('ph-xp').textContent = `${d.xp} XP`; document.getElementById('ph-level').textContent = d.level; document.getElementById('ph-streak').textContent = d.streak; document.getElementById('ph-xp-bar').style.width = `${(d.xp % 1000) / 10}%`; } } async function switchTab(tabId) { UI.tabs.forEach(t => t.classList.remove('active')); document.querySelector(`[data-tab="${tabId}"]`).classList.add('active'); UI.tabContents.forEach(c => { c.style.display = 'none'; c.innerHTML = ''; }); const container = document.getElementById(`tab-${tabId}`); const template = document.getElementById(`tmpl-${tabId}`); if (!template) return; container.appendChild(template.content.cloneNode(true)); container.style.display = 'block'; if (tabId === 'inicio') { UI.title.textContent = 'Inicio'; document.getElementById('kpi-gpa').textContent = state.dashboard.gpa || 7.5; document.getElementById('kpi-cursos').textContent = state.dashboard.active_courses || 0; document.getElementById('kpi-tareas').textContent = state.dashboard.pending_tasks || 0; document.getElementById('kpi-streak-card').textContent = state.dashboard.streak; if (!state.schedule.length) { const schRes = await fetch(`/api/student/schedule`); if (schRes.ok) state.schedule = await schRes.json(); } if (!state.exams.length) { const exRes = await fetch(`/api/student/exams`); if (exRes.ok) state.exams = await exRes.json(); } renderInicioLists(); } else if (tabId === 'clases') { UI.title.textContent = 'Mis Clases'; if (!state.schedule.length) { const schRes = await fetch(`/api/student/schedule`); if (schRes.ok) state.schedule = await schRes.json(); } renderSchedule(); } else if (tabId === 'tareas') { UI.title.textContent = 'Tareas'; if (!state.tasks.length) { const tsRes = await fetch(`/api/student/tasks`); if (tsRes.ok) state.tasks = await tsRes.json(); } renderTasks(); } else if (tabId === 'calificaciones') { UI.title.textContent = 'Calificaciones'; if (!state.grades.length) { const grRes = await fetch(`/api/student/grades`); if (grRes.ok) state.grades = await grRes.json(); } renderGrades(); } else if (tabId === 'proyectos') { UI.title.textContent = 'Proyectos'; if (!state.projects.length) { const prRes = await fetch(`/api/student/projects`); if (prRes.ok) state.projects = await prRes.json(); } renderProjects(); } else if (tabId === 'examenes') { UI.title.textContent = 'Exámenes'; if (!state.exams.length) { const exRes = await fetch(`/api/student/exams`); if (exRes.ok) state.exams = await exRes.json(); } renderExams(); } } function renderInicioLists() { const sContainer = document.getElementById('inicio-upcoming-sessions'); if (state.schedule.length) { sContainer.innerHTML = state.schedule.slice(0,3).map(s => `
${s.course}
${s.start} - ${s.end} | ${s.room}
`).join(''); } const eContainer = document.getElementById('inicio-upcoming-exams'); if (state.exams.length) { eContainer.innerHTML = state.exams.slice(0,2).map(e => `
${e.title}
${new Date(e.date).toLocaleDateString()} | ${e.course}
`).join(''); } } function renderSchedule() { if(state.schedule.length > 0) { const next = state.schedule[0]; document.getElementById('next-class-name').textContent = next.course; document.getElementById('next-class-time').textContent = next.start; document.getElementById('next-class-room').textContent = next.room; } const grid = document.getElementById('schedule-grid'); const days = ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes']; let html = `
`; for(let d=1; d<=5; d++) { const dayClasses = state.schedule.filter(s => s.day === d).sort((a,b) => a.start.localeCompare(b.start)); html += `

${days[d-1]}

${dayClasses.map(c => `
${c.start} - ${c.end}
${c.course}
📍 ${c.room}
`).join('') || '
Libre
'}
`; } html += `
`; grid.innerHTML = html; } function renderTasks() { const c = document.getElementById('tasks-container'); c.innerHTML = state.tasks.map(t => { let badge = ''; if (t.status === 'graded') badge = `Calificada (${t.score}/${t.max_score})`; else if (t.status === 'submitted') badge = 'Entregada'; else if (t.status === 'overdue') badge = 'Atrasada'; else badge = 'Pendiente'; const dateStr = t.due_date ? new Date(t.due_date).toLocaleDateString() : 'Sin fecha límite'; const btn = (t.status === 'pending' || t.status === 'overdue') ? `` : ``; return `
${t.course} ${badge}

${t.title}

Vence: ${dateStr}

${btn}
`; }).join(''); } function renderGrades() { const tb = document.getElementById('grades-table-body'); // Group grades by course ideally, but here we just list them. tb.innerHTML = state.grades.map(g => { const pct = (g.score / g.max_score) * 100; let color = pct >= 80 ? 'var(--teal)' : pct >= 50 ? 'var(--amber)' : 'var(--rose)'; return ` ${g.course}
${g.task_title} -- ${g.score}/${g.max_score} --
${g.score}
`; }).join(''); } function renderProjects() { const c = document.getElementById('projects-container'); c.innerHTML = state.projects.map(p => { const teamHtml = p.team.map(m => `
${m.initial}
`).join(''); const badge = p.status === 'entregado' ? 'Entregado' : 'En Curso'; return `
${p.course} ${badge}

${p.title}

${p.description}

${teamHtml}
Progreso: ${p.progress}%
`; }).join(''); } function renderExams() { const c = document.getElementById('exams-container'); c.innerHTML = state.exams.map(e => `
${e.mode} ${e.duration} min

${e.title}

${e.course}

Temario:
${e.syllabus}
📅 ${new Date(e.date).toLocaleDateString()}
`).join(''); } if (document.getElementById('sidebar-nav')) { init(); // Setup Logout const logoutBtn = document.getElementById('logout-btn'); if (logoutBtn) { logoutBtn.addEventListener('click', async (e) => { e.preventDefault(); await fetch('/api/auth/logout', { method: 'POST' }); window.location.href = '/login'; }); } } // --- Modal Logic --- let activeTaskId = null; let activeProjectId = null; window.closeModals = function() { document.querySelectorAll('.modal-overlay').forEach(m => m.classList.remove('active')); activeTaskId = null; activeProjectId = null; }; window.openTaskModal = function(taskId, taskTitle) { activeTaskId = taskId; document.getElementById('task-modal-title').textContent = `Entregar Tarea: ${taskTitle}`; document.getElementById('task-content').value = ''; document.getElementById('task-modal').classList.add('active'); }; window.submitTask = async function() { if (!activeTaskId) return; const content = document.getElementById('task-content').value.trim(); if (!content) return alert('Por favor introduce el contenido o enlace de la tarea.'); const btn = document.querySelector('#task-modal .btn-primary'); btn.disabled = true; btn.textContent = 'Enviando...'; try { const res = await fetch(`/api/student/tasks/${activeTaskId}/submit`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contenido: content }) }); if (res.ok) { window.closeModals(); // Refresh tasks state.tasks = await (await fetch(`/api/student/tasks`)).json(); renderTasks(); } else { alert('Error al enviar la tarea.'); } } catch(err) { alert('Error de red.'); } btn.disabled = false; btn.textContent = 'Enviar Tarea'; }; window.openProjectModal = function(projectId) { const p = state.projects.find(x => x.id === projectId); if (!p) return; activeProjectId = projectId; document.getElementById('project-modal-title').textContent = p.title; document.getElementById('project-modal-desc').textContent = p.description; const rng = document.getElementById('project-modal-progress'); rng.value = p.progress; document.getElementById('project-modal-progress-text').textContent = p.progress; document.getElementById('project-modal').classList.add('active'); }; window.updateProjectProgress = async function() { if (!activeProjectId) return; const progress = parseInt(document.getElementById('project-modal-progress').value, 10); const btn = document.querySelector('#project-modal .btn-primary'); btn.disabled = true; btn.textContent = 'Guardando...'; try { const res = await fetch(`/api/student/projects/${activeProjectId}/progress`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ progress: progress }) }); if (res.ok) { window.closeModals(); state.projects = await (await fetch(`/api/student/projects`)).json(); renderProjects(); } else { alert('Error al actualizar el proyecto.'); } } catch(err) { alert('Error de red.'); } btn.disabled = false; btn.textContent = 'Guardar Cambios'; }; window.joinClass = function(courseName) { document.getElementById('class-modal-text').textContent = `Conectando con el aula virtual de ${courseName}...`; document.getElementById('class-modal').classList.add('active'); setTimeout(() => { document.getElementById('class-modal-text').textContent = 'El profesor aún no ha iniciado la sesión. Por favor, espera.'; }, 2000); }; const fakeNotifs = [ { title: "Nueva Tarea", desc: "Se ha publicado la tarea de Matemáticas Avanzadas.", time: "Hace 10 min", icon: "📚" }, { title: "Calificación Publicada", desc: "Has recibido un 9.5 en Física.", time: "Hace 2 horas", icon: "⭐" } ]; window.toggleNotifications = function(e) { if (e) e.stopPropagation(); const drop = document.getElementById('notifications-dropdown'); const badge = document.getElementById('notif-badge'); if (!drop) return; if (!drop.classList.contains('active')) { const list = document.getElementById('notifications-list'); list.innerHTML = fakeNotifs.map(n => `
${n.icon}
${n.title}
${n.desc}
${n.time}
`).join(''); drop.classList.add('active'); if (badge) badge.style.display = 'none'; // clear badge } else { drop.classList.remove('active'); } }; // Close dropdown when clicking outside document.addEventListener('click', (e) => { const drop = document.getElementById('notifications-dropdown'); if (drop && drop.classList.contains('active') && !e.target.closest('.notification-bell')) { drop.classList.remove('active'); } }); });