// TeacherPanel.jsx — panel de la profesora (solo admin). window.TeacherPanel
const { useState: useStateT, useEffect: useEffectT } = React;
function studentStats(data) {
const S = window.CourseStore;
const units = S.units();
let unitsDone = 0, partsPassed = 0, finals = 0, sumNota = 0;
units.forEach(u => {
const up = data && data.progress && data.progress[u.slug];
if (!up) return;
if (up.final && up.final.passed) { unitsDone++; finals++; sumNota += (up.final.best / S.FINAL.N) * 10; }
[1, 2, 3, 4].forEach(n => { if (up.parts && up.parts[n] && up.parts[n].passed) partsPassed++; });
});
return { unitsDone, partsPassed, media: finals ? (sumNota / finals) : null, total: units.length };
}
function fechaCorta(iso) {
if (!iso) return '—';
try { const d = new Date(iso); return d.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit', year: '2-digit' }); } catch (e) { return '—'; }
}
function TeacherPanel({ onBack }) {
const C = window.ECCCloud;
const [rows, setRows] = useStateT(null);
const [err, setErr] = useStateT('');
const [sel, setSel] = useStateT(null);
const cargar = async () => {
setErr('');
try {
const data = await C.adminListAll();
setRows(data);
} catch (e) { setErr('No se pudo cargar la lista.'); setRows([]); }
};
useEffectT(() => { cargar(); }, []);
const reiniciar = async (r) => {
const nombre = (r.data && (r.data.name || r.data.email)) || r.user_id;
if (!confirm('¿Reiniciar el progreso de ' + nombre + '? No se puede deshacer.')) return;
await C.adminReset(r.user_id, { email: r.data && r.data.email, name: r.data && r.data.name });
cargar();
};
const total = (window.CourseStore.units() || []).length;
return (
← Volver
Panel del profesorado
Alumnado
↻ Actualizar
{err && {err}
}
{rows === null ? (
Cargando alumnado…
) : rows.length === 0 ? (
Todavía no hay alumnos con progreso.
Aparecerán aquí en cuanto se registren y hagan su primer cuestionario.
) : (
{['Alumno/a', 'Unidades', 'Avance', 'Nota media', ''].map((h, i) =>
{h}
)}
{rows.map(r => {
const st = studentStats(r.data || {});
const pct = Math.round((st.unitsDone / st.total) * 100);
const nombre = (r.data && r.data.name) || '';
const email = (r.data && r.data.email) || r.user_id;
return (
setSel(r)} style={{ display: 'grid', gridTemplateColumns: '2fr 1fr 1.2fr 1fr 0.9fr', borderTop: '1px solid var(--line)', alignItems: 'center', font: 'var(--type-body-sm)', color: 'var(--ink)', cursor: 'pointer' }}>
{nombre || '(Sin nombre)'}
{email}
act. {fechaCorta(r.updated_at)}
{st.unitsDone} / {st.total}
{pct}% · {st.partsPassed} partes
{st.media === null ? '—' : st.media.toFixed(1).replace('.', ',')}
{ e.stopPropagation(); reiniciar(r); }} style={{ background: 'transparent', border: '1px solid var(--line)', color: 'var(--ink-2)', borderRadius: 'var(--r-md)', padding: '6px 10px', font: 'var(--type-ui-sm)', cursor: 'pointer' }}>Reiniciar
);
})}
)}
{rows && rows.length > 0 ? rows.length + ' alumno(s). ' : ''}Solo tú (cuenta de profesora) puedes ver esta página. Cada alumno solo ve su propio progreso.
{sel && setSel(null)} onChanged={cargar} />}
);
}
window.TeacherPanel = TeacherPanel;
// ---- Detalle de un alumno (toda su información) ----
const DETAIL_GLYPH = { bolt: '⚡', star: '★', check: '✓', flame: '🔥', trophy: '🏆' };
function StudentDetail({ row, onClose, onChanged }) {
const S = window.CourseStore;
const units = S.units();
const data = row.data || {};
const pr = data.progress || {};
const badges = data.badges || {};
const st = studentStats(data);
const pct = Math.round((st.unitsDone / st.total) * 100);
const nombre = data.name || '(Sin nombre)';
const email = data.email || row.user_id;
const cell = (node) => {
if (!node || node.best === null || node.best === undefined) return { txt: '—', col: 'var(--ink-4)', sub: '' };
const nota = (Math.round((node.best / S.PARTIAL.N) * 100) / 10).toFixed(1).replace('.', ',');
return { txt: nota, col: node.passed ? 'var(--success)' : 'var(--danger)', sub: node.attempts ? node.attempts + ' int.' : '' };
};
const finalCell = (node) => {
if (!node || node.best === null || node.best === undefined) return { txt: '—', col: 'var(--ink-4)', sub: '' };
const nota = (Math.round((node.best / S.FINAL.N) * 100) / 10).toFixed(1).replace('.', ',');
return { txt: nota, col: node.passed ? 'var(--success)' : 'var(--danger)', sub: node.attempts ? node.attempts + ' int.' : '' };
};
return (
e.stopPropagation()} style={{ background: 'var(--bg-canvas)', width: 'min(820px, 100%)', minHeight: '100%', padding: '0 0 60px' }}>
{nombre}
{email} · última actividad {fechaCorta(row.updated_at)}
Cerrar
{/* resumen */}
{[{ k: 'Avance', v: pct + '%' }, { k: 'Unidades superadas', v: st.unitsDone + ' / ' + st.total }, { k: 'Nota media (finales)', v: st.media === null ? '—' : st.media.toFixed(1).replace('.', ',') }].map(c => (
))}
{/* insignias */}
{Object.keys(badges).length > 0 && (
{S.BADGES.filter(b => badges[b.id]).map(b => (
{DETAIL_GLYPH[b.icon] || '★'} {b.title}
))}
)}
{/* tabla por unidad */}
{['Unidad', 'P1', 'P2', 'P3', 'P4', 'Final'].map(h =>
{h}
)}
{units.map(u => {
const up = pr[u.slug] || { parts: {}, final: null };
return (
{u.ud} {u.topic}
{[1, 2, 3, 4].map(n => { const c = cell(up.parts && up.parts[n]); return (
{c.txt}{c.sub && · {c.sub} }
); })}
{(() => { const c = finalCell(up.final); return
{c.txt}
; })()}
);
})}
{ if (confirm('¿Reiniciar TODO el progreso de ' + nombre + '?')) { await window.ECCCloud.adminReset(row.user_id, { email: data.email, name: data.name }); onClose(); if (onChanged) onChanged(); } }}
style={{ marginTop: 18, background: 'transparent', border: '1px solid var(--line)', color: 'var(--ink-3)', borderRadius: 'var(--r-md)', padding: '9px 16px', font: 'var(--type-ui-sm)', cursor: 'pointer' }}>
Reiniciar progreso de este alumno
);
}
window.StudentDetail = StudentDetail;