desvergue-de-alexandro
js 114 lines 6.8 KB
Raw
1 /**
2 * Panel de inicio.
3 * Contrato esperado de GET /inicio:
4 * { perfil, materias, publicaciones, circuloActual, asesoria, empresaDestacada }
5 */
6 document.addEventListener('DOMContentLoaded', async () => {
7 Layout.init('inicio.html');
8 Layout.setPageTitle('Inicio');
9
10 const content = document.getElementById('main-content');
11 if (!content) return;
12
13 content.innerHTML = '<div class="inicio-container"><p class="loading-message">Cargando tu información académica…</p></div>';
14
15 let dashboard = {};
16 try {
17 dashboard = await API.request('/inicio');
18 } catch {
19 // El endpoint se implementará en el backend. La UI mantiene estados vacíos.
20 }
21
22 renderDashboard(content, dashboard || {});
23 });
24
25 function renderDashboard(content, data) {
26 const perfil = data.perfil || null;
27 const materias = Array.isArray(data.materias) ? data.materias : [];
28 const publicaciones = Array.isArray(data.publicaciones) ? data.publicaciones : [];
29 const accesos = renderAccessCards(data);
30
31 content.innerHTML = `
32 <div class="inicio-container">
33 ${perfil ? renderBanner(perfil) : ''}
34 <div class="inicio-layout">
35 <section class="feed-column" aria-label="Actividad reciente">
36 ${materias.length ? renderQuickQuestion(materias) : ''}
37 ${publicaciones.length ? publicaciones.map(renderPost).join('') : renderEmptyFeed()}
38 </section>
39 ${accesos ? `<aside class="sidebar-section" aria-label="Accesos directos">${accesos}</aside>` : ''}
40 </div>
41 </div>
42 `;
43
44 document.getElementById('quick-question-form')?.addEventListener('submit', event => {
45 event.preventDefault();
46 window.location.href = 'foro.html';
47 });
48 }
49
50 function renderBanner(perfil) {
51 const semestre = Number(perfil.semestre) || 0;
52 const totalSemestres = Number(perfil.totalSemestres) || 0;
53 const avance = totalSemestres ? Math.min(100, Math.round((semestre / totalSemestres) * 100)) : 0;
54
55 return `
56 <section class="card maps-banner" aria-labelledby="maps-title">
57 <div class="banner-content">
58 <div>
59 <h1 id="maps-title">${escapeHtml(perfil.carrera || 'Mi trayectoria MAPS')}</h1>
60 <p>${perfil.certificado ? `Certificado activo: <strong>${escapeHtml(perfil.certificado)}</strong>` : 'Sin certificado activo'}${semestre ? ` · Semestre vigente: <strong>${semestre}.º</strong>` : ''}</p>
61 </div>
62 <span class="badge badge-materia">Plan MAPS oficial</span>
63 </div>
64 ${totalSemestres ? `<div class="progress-container"><div class="progress-labels"><span>Avance de carrera</span><span>Semestre ${semestre} de ${totalSemestres} · ${avance} %</span></div><div class="progress-bar" role="progressbar" aria-label="Avance de carrera" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${avance}"><div class="progress-fill" style="width: ${avance}%"></div></div></div>` : ''}
65 </section>
66 `;
67 }
68
69 function renderQuickQuestion(materias) {
70 const options = materias.map(materia => `<option value="${escapeHtml(materia.id ?? '')}">${escapeHtml(materia.nombre || materia)}</option>`).join('');
71 return `
72 <form class="card create-post" id="quick-question-form">
73 <label class="visually-hidden" for="quick-question">Nueva duda académica</label>
74 <input id="quick-question" name="question" type="text" maxlength="180" placeholder="¿Tienes una duda académica? Pregunta a tu comunidad..." required>
75 <div class="create-post-actions"><select aria-label="Materia de la duda"><option value="">Seleccionar materia...</option>${options}</select><button class="btn-solid" type="submit">Publicar duda</button></div>
76 </form>
77 `;
78 }
79
80 function renderPost(post) {
81 const author = post.autor || {};
82 const initials = (author.iniciales || author.nombre || '?').trim().split(/\s+/).map(word => word[0]).slice(0, 2).join('').toUpperCase();
83 const tags = [post.materia && `<span class="badge badge-materia">${escapeHtml(post.materia)}</span>`, post.solucionAceptada && '<span class="badge badge-solved">✓ Solución aceptada</span>', post.verificadoDocente && '<span class="badge badge-verified">Verificado por docente</span>'].filter(Boolean).join('');
84
85 return `
86 <article class="card post-card">
87 <div class="post-header"><div class="user-block"><div class="avatar ${post.verificadoDocente ? 'prof' : ''}">${escapeHtml(initials)}</div><div class="user-data"><strong>${escapeHtml(author.nombre || 'Usuario')}</strong><span>${escapeHtml(post.fechaRelativa || '')}</span></div></div><div class="badges-wrap">${tags}</div></div>
88 <h2 class="post-title">${escapeHtml(post.titulo || '')}</h2>
89 ${post.contenido ? `<p class="post-body">${escapeHtml(post.contenido)}</p>` : ''}
90 <div class="post-footer"><div class="action-links"><a class="btn-action" href="foro.html">💬 ${Number(post.respuestas) || 0} respuestas</a></div><span class="post-status">${post.solucionAceptada ? 'Resuelto' : ''}</span></div>
91 </article>
92 `;
93 }
94
95 function renderAccessCards(data) {
96 const cards = [];
97 const circulo = data.circuloActual;
98 const asesoria = data.asesoria;
99 const empresa = data.empresaDestacada;
100
101 if (circulo) cards.push(`<section class="card side-card"><span class="badge badge-materia">${escapeHtml(circulo.fecha || 'Próximamente')}</span><h3>${escapeHtml(circulo.nombre || 'Círculo de estudio')}</h3>${circulo.descripcion ? `<p>${escapeHtml(circulo.descripcion)}</p>` : ''}<a class="btn-solid" href="circulos.html">Ver círculo</a></section>`);
102 if (asesoria) cards.push(`<section class="card side-card prof-card"><span class="badge badge-verified">Asesoría disponible</span><h3>${escapeHtml(asesoria.docente || 'Docente')}</h3>${asesoria.horario ? `<p>${escapeHtml(asesoria.horario)}</p>` : ''}<a class="btn-clean" href="mensajes.html">Enviar mensaje privado</a></section>`);
103 if (empresa) cards.push(`<section class="card side-card"><h3>Radar Semestre Empresarial</h3><p><strong>${escapeHtml(empresa.nombre || '')}${empresa.calificacion ? ` · ${escapeHtml(empresa.calificacion)} ★` : ''}</strong>${empresa.resena ? `<br>${escapeHtml(empresa.resena)}` : ''}</p><a class="side-link" href="empresarial.html">Ver directorio de empresas →</a></section>`);
104
105 return cards.join('');
106 }
107
108 function renderEmptyFeed() {
109 return '<section class="card empty-state"><h2>Aún no hay actividad</h2><p>Las dudas, avisos y recursos de tu comunidad aparecerán aquí cuando estén disponibles.</p><a class="side-link" href="foro.html">Ir al foro →</a></section>';
110 }
111
112 function escapeHtml(value) {
113 return String(value ?? '').replace(/[&<>'"]/g, character => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' })[character]);
114 }