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:
+61
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user