Lehrer-Live-Zeiten fest auf Europe/Vienna (Server laeuft UTC) -> immer oesterreichische Zeit

_hhmm/_vdt/_fmtHM formatieren mit timeZone Europe/Vienna; _asMs normalisiert Epoch UND
MySQL-UTC-Strings. Betrifft Tag-Achse+Balken, Session-Detail, "Heute erledigt", Cockpit
"zuletzt aktiv", Reflexionen, letztes Lebenszeichen. Vorher zeigte new Date().getHours()
je nach Geraet evtl. UTC.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 11:55:44 +02:00
parent e4b877de4b
commit 5633d3f9f9
+19 -9
View File
@@ -1082,7 +1082,7 @@ function _cockpitStudentRow(s) {
sub += ' <span style="color:#8a7a4a;font-size:.7rem;font-weight:600;margin-left:.4rem">📵 idle seit '+idleStr+' — Zeit pausiert</span>';
}
} else if (s.status === 'heute') {
var when = s.lastSeen ? new Date(s.lastSeen).toLocaleTimeString('de-AT',{hour:'2-digit',minute:'2-digit'}) : '';
var when = s.lastSeen ? _hhmm(s.lastSeen) : '';
sub = '<span style="color:#6a6a6a;font-size:.74rem">heute aktiv · zuletzt um '+when+'</span>';
} else if (s.status === 'kalt') {
sub = '<span style="color:#8a8a8a;font-size:.74rem;font-style:italic">heute noch nicht eingeloggt'+(s.lastSeen?' · zuletzt '+_cockpitFmtAgo(Math.floor((Date.now()-new Date(s.lastSeen).getTime())/1000)):'')+'</span>';
@@ -1227,7 +1227,7 @@ async function _loadCockpitReflections() {
(data.modules || []).forEach(function(m){ simIcons[m.id] = m.icon || ''; });
var html = '<div style="display:flex;flex-direction:column;gap:.55rem">';
items.forEach(function(it) {
var when = it.recordedAt ? new Date(it.recordedAt).toLocaleString('de-AT', {day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}) : '';
var when = it.recordedAt ? _vdt(it.recordedAt) : '';
html += '<div style="background:#fbfaf6;border:1px solid rgba(0,0,0,.06);border-left:4px solid #c9913a;border-radius:8px;padding:.55rem .8rem">'
+ '<div style="display:flex;justify-content:space-between;font-size:.7rem;color:#6a6a6a;margin-bottom:.2rem">'
+ '<span><b style="color:#3a3a3a">' + _avatarHtml(it.avatarSlug, it.emoji, 1.2) + ' ' + esc(it.displayName||'—') + '</b>'
@@ -1368,8 +1368,19 @@ 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); }
// Uhrzeit/Datum IMMER in österreichischer Zeit (Europe/Vienna) — unabhängig von der
// Zeitzone des Servers (läuft in UTC) ODER des Anzeigegeräts.
// Eingabe: ms-Epoch (Zahl) ODER MySQL-Datetime-String (wird als UTC interpretiert).
function _asMs(t){
if (t == null || t === '') return 0;
if (typeof t === 'number') return t;
var s = String(t).trim();
if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/.test(s) && !/[zZ]|[+-]\d{2}:?\d{2}$/.test(s))
return new Date(s.replace(' ', 'T') + 'Z').getTime(); // DB-Zeit ist UTC
return new Date(s).getTime();
}
function _hhmm(t){ var ms=_asMs(t); if(!ms) return '—'; return new Date(ms).toLocaleTimeString('de-AT',{timeZone:'Europe/Vienna',hour:'2-digit',minute:'2-digit'}); }
function _vdt(t){ var ms=_asMs(t); if(!ms) return '—'; return new Date(ms).toLocaleString('de-AT',{timeZone:'Europe/Vienna',day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}); }
// Avatar als Bild rendern — egal ob der Slug in avatar_slug ODER als
// „avatar:<slug>" im emoji_avatar-Feld steckt (Daten sind uneinheitlich).
@@ -2160,8 +2171,7 @@ async function renderLiveSummary() {
var winSpanMs = winEnd - winStart;
function _msToPct(ms) { return ((ms - winStart) / winSpanMs) * 100; }
function _fmtHM(ms) {
var d = new Date(ms);
return ('0'+d.getHours()).slice(-2) + ':' + ('0'+d.getMinutes()).slice(-2);
return new Date(ms).toLocaleTimeString('de-AT', {timeZone:'Europe/Vienna', hour:'2-digit', minute:'2-digit'});
}
var activeCount = students.filter(function(s){return (s.sessions||[]).length>0;}).length;
@@ -2292,8 +2302,8 @@ async function openLiveSessionDetail(sessionKey) {
(sess.simIcon||'📚') + ' ' + (sess.simTitle||sess.simId) + (sess.lessonLabel?' · '+sess.lessonLabel:'');
var body = document.getElementById('sessdet-body');
var avatarHtml = _avatarHtml(sess.studentAvatarSlug, sess.studentEmoji, 2);
var startTime = new Date(sess.startMs).toLocaleString('de-AT',{hour:'2-digit',minute:'2-digit',day:'2-digit',month:'2-digit'});
var endTime = new Date(sess.endMs).toLocaleString('de-AT',{hour:'2-digit',minute:'2-digit'});
var startTime = _vdt(sess.startMs);
var endTime = _hhmm(sess.endMs);
var dur = _liveFmtMin(sess.durationSec||0);
var stars = sess.stars;
var starsHtml = stars != null
@@ -2433,7 +2443,7 @@ async function renderSpectator(studentId) {
html += '<div style="margin-top:.6rem;padding-top:.5rem;border-top:1px dashed rgba(0,0,0,.1);font-size:.78rem"><span style="color:#8a8a8a">Erfolgreich abgeschlossen: </span><strong>'+wonVal+'</strong></div>';
}
}
html += '<div style="margin-top:.8rem;font-size:.62rem;color:#8a8a8a">Aktualisiert alle 3 Sekunden · letztes Lebenszeichen: '+(data.last_seen_ms?new Date(data.last_seen_ms).toLocaleTimeString('de-AT'):'?')+'</div>';
html += '<div style="margin-top:.8rem;font-size:.62rem;color:#8a8a8a">Aktualisiert alle 3 Sekunden · letztes Lebenszeichen: '+(data.last_seen_ms?_hhmm(data.last_seen_ms):'?')+'</div>';
host.innerHTML = html;
}