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} |
-- |
|
`;
}).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}
📅 ${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');
}
});
});