Aktuell + Fortschritt: zweite Tabelle "Heute nicht mehr aktiv" darunter

Unter den aktiven Schueler:innen eine eigene Tabelle (gleiche Form): wer HEUTE
schon gespielt hat, gerade aber offline ist -> Zuletzt aktiv (Uhrzeit),
Aktivitaet (Sim), Leistung (Score-Prozent, Ampelfarbe). Reusable
_renderInactiveToday(activeSet), gespeist aus dem summary-Endpoint (heute),
laufende/online ausgenommen. In renderLive und renderLiveCompare eingehaengt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 15:21:51 +02:00
parent 64a9a62157
commit 3c856ff5ce
+61 -3
View File
@@ -1362,6 +1362,57 @@ function _liveIsActive(ls) {
return ls.last_seen_ms && (Date.now() - ls.last_seen_ms) <= _LIVE_PAUSE_SEC * 1000;
}
// Uhrzeit HH:MM aus ms-Epoch.
function _hhmm(ms){ if(!ms) return '—'; var d=new Date(ms); return ('0'+d.getHours()).slice(-2)+':'+('0'+d.getMinutes()).slice(-2); }
// „Heute nicht mehr aktiv" — Schüler:innen, die HEUTE gespielt haben, gerade aber
// offline sind: zuletzt aktiv (Uhrzeit) + letzte Aktivität + deren Leistung.
// Wiederverwendet in „Aktuell" und „Fortschritt". activeSet = {studentId:true}
// der gerade Online-Schüler:innen (die stehen oben in der aktiven Tabelle).
async function _renderInactiveToday(activeSet) {
var today = new Date().toISOString().slice(0,10);
var data;
try { data = await api('live?summary=1&class_id=' + currentClassId + '&date=' + encodeURIComponent(today)); }
catch(e){ return ''; }
if (!data || data.error || !Array.isArray(data.students)) return '';
var rows = [];
data.students.forEach(function(s){
if (activeSet && activeSet[s.studentId]) return; // gerade online → oben
var sess = (s.sessions||[]).filter(function(x){ return x.startMs; });
if (!sess.length) return; // heute nichts gespielt
if (sess.some(function(x){ return x.isLive || x.success==='running'; })) return; // läuft noch → oben
sess.sort(function(a,b){ return (b.endMs||0)-(a.endMs||0); });
rows.push({ s:s, last:sess[0] });
});
if (!rows.length) return '';
rows.sort(function(a,b){ return (b.last.endMs||0)-(a.last.endMs||0); });
var html = '<div class="card" style="margin-top:.8rem"><div class="card-h">Heute nicht mehr aktiv · '+rows.length+'</div>';
html += '<p style="font-size:.7rem;color:#8a8a8a;margin-bottom:.5rem">Heute schon gearbeitet, gerade aber offline — mit letzter Aktivität und deren Leistung.</p>';
html += '<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:.74rem">';
html += '<thead><tr style="background:#f7f6f3;border-bottom:2px solid rgba(0,0,0,.08)">'
+ '<th style="text-align:left;padding:.4rem .5rem;white-space:nowrap">Schüler*in</th>'
+ '<th style="text-align:left;padding:.4rem .5rem;white-space:nowrap">Zuletzt aktiv</th>'
+ '<th style="text-align:left;padding:.4rem .5rem;white-space:nowrap">Aktivität</th>'
+ '<th style="text-align:right;padding:.4rem .5rem;white-space:nowrap">Leistung</th></tr></thead><tbody>';
rows.forEach(function(r){
var s=r.s, last=r.last;
var av = (typeof s.emoji === 'string' && s.emoji.indexOf('avatar:')===0)
? '<img src="assets/img/avatars/thumbs/avatar-'+s.emoji.substring(7)+'.webp" alt="" style="width:1.5em;height:1.5em;border-radius:50%;object-fit:cover;vertical-align:middle">'
: '<span style="font-size:1.05rem">'+(s.emoji||'🧑‍🎓')+'</span>';
var score = (last.score!=null)?last.score:(last.stars!=null?Math.round(last.stars/5*100):null);
var perf = score!=null
? '<span style="font-weight:700;color:'+_tagTraffic(score)+'">'+score+' %</span>'
: (last.success==='aborted' ? '<span style="color:#b03a2a">abgebrochen</span>' : '<span style="color:#8a8a8a"></span>');
html += '<tr style="border-bottom:1px solid rgba(0,0,0,.04);cursor:pointer" onclick="ivOpenStudent('+s.studentId+')">'
+ '<td style="padding:.4rem .5rem;white-space:nowrap"><span style="display:inline-flex;align-items:center;gap:.4rem">'+av+'<strong>'+esc(s.displayName)+'</strong></span></td>'
+ '<td style="padding:.4rem .5rem;color:#3a3a3a;font-variant-numeric:tabular-nums">'+_hhmm(last.endMs)+' Uhr</td>'
+ '<td style="padding:.4rem .5rem">'+esc((last.simIcon||'📚')+' '+(last.simTitle||last.simId))+(last.lessonLabel?' · '+esc(last.lessonLabel):'')+'</td>'
+ '<td style="text-align:right;padding:.4rem .5rem">'+perf+'</td></tr>';
});
html += '</tbody></table></div></div>';
return html;
}
async function renderLive() {
var sessions = await api('live?class_id=' + currentClassId);
if (sessions.error) { document.getElementById('tab-live').innerHTML = '<p style="color:#c07a6b">'+esc(sessions.error)+'</p>'; return; }
@@ -1370,7 +1421,7 @@ async function renderLive() {
html += '<p style="font-size:.7rem;color:#8a8a8a;margin-bottom:.6rem">Aktualisiert sich alle 4 Sekunden. Aktiv = Lebenszeichen ≤ 90 s · pausierte Sessions (⏸) bleiben sichtbar, bis der/die Lernende weitermacht. Klick auf eine Zeile öffnet die Detailansicht.</p>';
if (!sessions.length) {
html += '<p style="font-size:.78rem;color:#8a8a8a">Gerade niemand online — sobald eine Schülerin oder ein Schüler eine Simulation startet, taucht sie hier auf.</p>';
host.innerHTML = html + '</div>';
host.innerHTML = html + '</div>' + (await _renderInactiveToday({}));
return;
}
@@ -1385,9 +1436,11 @@ async function renderLive() {
// Nach Modul gruppieren
var byModule = {};
var _activeSet = {};
sessions.forEach(function(ls) {
if (!byModule[ls.module_id]) byModule[ls.module_id] = { meta: ls, rows: [] };
byModule[ls.module_id].rows.push(ls);
_activeSet[ls.student_id] = true;
});
Object.keys(byModule).forEach(function(modId) {
@@ -1507,6 +1560,8 @@ async function renderLive() {
html += '</div>';
html += await _renderInactiveToday(_activeSet);
// Reflexionen (letzte Antworten der Klasse) — gecacht, damit der 4s-Refresh nicht flackert
html += '<div class="card" id="cockpit-reflections">'
+ '<div class="card-h">💭 Reflexionen — letzte Antworten der Klasse</div>'
@@ -1769,13 +1824,16 @@ async function renderLiveCompare() {
// Körper
var simSessions = byGroup[sim] ? byGroup[sim].sessions : [];
if (!simSessions.length) {
html += '<div class="card"><div class="card-h">'+esc(_compareGroupLabel(sim))+' · Vergleich</div>'
+ '<p style="font-size:.78rem;color:#8a8a8a">Noch niemand arbeitet gerade an dieser Simulation. Sobald Kinder starten, erscheinen sie hier im Vergleich.</p></div>';
html += '<div class="card"><div class="card-h">'+esc(_compareGroupLabel(sim))+' · Fortschritt</div>'
+ '<p style="font-size:.78rem;color:#8a8a8a">Noch niemand arbeitet gerade an dieser Simulation. Sobald Kinder starten, erscheinen sie hier.</p></div>';
} else if (sim === 'klima') {
html += _renderKlimaCompareBody(simSessions);
} else {
html += _renderGenericCompareBody(simSessions, sim);
}
var _cActive = {};
(Array.isArray(sessions) ? sessions : []).forEach(function(ls){ _cActive[ls.student_id] = true; });
html += await _renderInactiveToday(_cActive);
host.innerHTML = html;
}