Tag-Ansicht: student_results einbeziehen + Datum/Zoom-Verhalten
- live.php summary: fertige Laeufe aus student_results ergaenzt (dedupliziert gegen assessment/ live) -> JEDER heutige Durchgang erscheint als Balken, egal ueber welchen Submit-Pfad. - teacher.html: Ergebnisse-Tab/Menuewechsel setzt Datum auf HEUTE + Regler auf Auto zurueck; Datumswahl/Heute setzt Regler auf Auto; Auto-Zoom jetzt +/-1 h um die Aktivitaet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -388,6 +388,71 @@ if ($method === 'GET') {
|
||||
unset($st);
|
||||
}
|
||||
|
||||
// === student_results (V2) ergaenzen: fertige Laeufe des Tages, die NICHT schon
|
||||
// als assessment/live-Session vorliegen (manche Sims schreiben nur student_results).
|
||||
// So taucht JEDER Durchgang im Tag-Balken auf, unabhaengig vom Submit-Pfad. ===
|
||||
$srRows = $db->fetchAll(
|
||||
"SELECT sr.student_id, sr.module_id, sr.score, sr.duration_sec, sr.detail_json,
|
||||
UNIX_TIMESTAMP(sr.completed_at) AS done_ts,
|
||||
s.display_name, s.username, s.emoji_avatar, s.avatar_slug
|
||||
FROM student_results sr
|
||||
JOIN students s ON s.id = sr.student_id
|
||||
WHERE sr.class_id = ?
|
||||
AND sr.completed_at >= ? AND sr.completed_at < ?
|
||||
ORDER BY sr.completed_at ASC",
|
||||
[$classId, $date . ' 00:00:00', $date . ' 23:59:59.999']
|
||||
);
|
||||
foreach ($srRows as $sr) {
|
||||
$sid = (int)$sr['student_id'];
|
||||
$simId = $sr['module_id'];
|
||||
$endMs = ((int)$sr['done_ts']) * 1000;
|
||||
$durSec = max(0, (int)$sr['duration_sec']);
|
||||
$startMs = $endMs - $durSec * 1000;
|
||||
// Dedup: schon eine Session (assessment/live) fuer diese Sim mit ~gleicher Endzeit?
|
||||
$dup = false;
|
||||
if (isset($byStudent[$sid])) {
|
||||
foreach ($byStudent[$sid]['sessions'] as $ex) {
|
||||
if ($ex['simId'] === $simId && abs((int)($ex['endMs'] ?? 0) - $endMs) < 150000) { $dup = true; break; }
|
||||
}
|
||||
}
|
||||
if ($dup) continue;
|
||||
$lk = $extractLessonKey(['results' => $sr['detail_json'], 'process_log' => null]);
|
||||
$mi = $moduleMap[$simId] ?? null;
|
||||
if (!isset($byStudent[$sid])) {
|
||||
$byStudent[$sid] = [
|
||||
'studentId' => $sid,
|
||||
'displayName' => $sr['display_name'] ?: $sr['username'],
|
||||
'avatarSlug' => $sr['avatar_slug'],
|
||||
'emoji' => $sr['emoji_avatar'],
|
||||
'totalSec' => 0,
|
||||
'sessions' => [],
|
||||
];
|
||||
}
|
||||
$score = is_numeric($sr['score']) ? max(0, min(100, (int)round($sr['score']))) : null;
|
||||
$success = ($score === null) ? ($durSec < 60 ? 'aborted' : 'good')
|
||||
: ($score >= 65 ? 'good' : ($score >= 40 ? 'mid' : 'bad'));
|
||||
$byStudent[$sid]['sessions'][] = [
|
||||
'simId' => $simId,
|
||||
'simTitle' => $mi['title'] ?? $simId,
|
||||
'simIcon' => $mi['icon'] ?? '📚',
|
||||
'lessonKind' => $lk['kind'],
|
||||
'lessonLabel' => $lk['label'],
|
||||
'startMs' => $startMs,
|
||||
'endMs' => $endMs,
|
||||
'durationSec' => $durSec,
|
||||
'stars' => null,
|
||||
'score' => $score,
|
||||
'completed' => true,
|
||||
'success' => $success,
|
||||
'rowCount' => max(1, (int)round($durSec/4)),
|
||||
];
|
||||
$byStudent[$sid]['totalSec'] += $durSec;
|
||||
}
|
||||
foreach ($byStudent as &$stSR) {
|
||||
usort($stSR['sessions'], function($a,$b){ return $a['startMs'] <=> $b['startMs']; });
|
||||
}
|
||||
unset($stSR);
|
||||
|
||||
// Stille Schüler*innen (keine Sessions an dem Tag) als leere Zeilen ergänzen
|
||||
$allStudents = $db->fetchAll(
|
||||
'SELECT id, display_name, username, emoji_avatar, avatar_slug FROM students WHERE class_id = ? AND deleted_at IS NULL ORDER BY COALESCE(display_name, username)',
|
||||
|
||||
+11
-5
@@ -485,6 +485,8 @@ function switchTab(tab) {
|
||||
tabs.forEach(function(t) { document.getElementById('tab-' + t).classList.toggle('hidden', t !== tab); });
|
||||
// Live-Tab darf seinen Polling-Timer beim Verlassen aufräumen
|
||||
if (tab !== 'live' && _liveTimer) { clearInterval(_liveTimer); _liveTimer = null; }
|
||||
// Betreten des Ergebnisse-Tabs → immer HEUTE + Auto-Zoom (Datum/Regler zurücksetzen).
|
||||
if (tab === 'live') { _liveDate = new Date().toISOString().slice(0,10); _liveWin = {from:null, to:null}; }
|
||||
// "classes"-Tab funktioniert ohne aktive Klasse
|
||||
if (tab === 'classes') { loadClassesAdmin(); return; }
|
||||
if (currentClassId) loadTab(tab);
|
||||
@@ -548,18 +550,22 @@ window.setCompareSim = function(simId){ _liveCompareSim = simId; loadLive(); };
|
||||
function setLiveMode(mode) {
|
||||
console.log('[live] setLiveMode('+mode+')');
|
||||
_liveMode = mode;
|
||||
// Menüwechsel → immer zurück auf HEUTE + Auto-Zoom (Regler-Auswahl verwerfen).
|
||||
_liveDate = new Date().toISOString().slice(0,10);
|
||||
_liveWin = {from:null, to:null};
|
||||
loadLive(); // re-init mit passendem Refresh-Intervall
|
||||
}
|
||||
// Auf window expose — falls Bundler/Scope-Probleme bei inline-onclick
|
||||
window.setLiveMode = setLiveMode;
|
||||
window.setLiveDate = function(d){ _liveDate = d; loadLive(); };
|
||||
window.setLiveDate = function(d){ _liveDate = d; _liveWin={from:null,to:null}; loadLive(); };
|
||||
window.shiftLiveDate= function(n){
|
||||
var d = new Date(_liveDate + 'T12:00:00'); d.setDate(d.getDate()+n);
|
||||
_liveDate = d.toISOString().slice(0,10); loadLive();
|
||||
_liveDate = d.toISOString().slice(0,10); _liveWin={from:null,to:null}; loadLive();
|
||||
};
|
||||
window.liveDateToday= function(){ _liveDate = new Date().toISOString().slice(0,10); loadLive(); };
|
||||
window.liveDateToday= function(){ _liveDate = new Date().toISOString().slice(0,10); _liveWin={from:null,to:null}; loadLive(); };
|
||||
function setLiveDate(dateStr) {
|
||||
_liveDate = dateStr;
|
||||
_liveWin = {from:null, to:null}; // neue Datumswahl → Auto-Zoom auf diesen Tag
|
||||
loadLive();
|
||||
}
|
||||
function shiftLiveDate(deltaDays) {
|
||||
@@ -2253,8 +2259,8 @@ async function renderLiveSummary() {
|
||||
if (!isFinite(minMs)) { minMs = dayBase + 8*3600*1000; maxMs = dayBase + 14*3600*1000; }
|
||||
// Auto-Grenzen (volle Stunden, Wiener Zeit): erste bis letzte Aktivität.
|
||||
var _lo=_vHM(minMs), _hi=_vHM(maxMs);
|
||||
var autoFromH = Math.max(0, _lo.h);
|
||||
var autoToH = Math.min(24, _hi.h + ((_hi.m>0)?1:0));
|
||||
var autoFromH = Math.max(0, _lo.h - 1); // 1 h VOR der ersten Aktivität
|
||||
var autoToH = Math.min(24, _hi.h + ((_hi.m>0)?1:0) + 1); // 1 h NACH der letzten Aktivität
|
||||
// Auto-Grenzen als EINHEITEN der gestauchten Skala (auf Block-Grenzen gerundet).
|
||||
var autoFromU = Math.floor(_hourToUnit(autoFromH));
|
||||
var autoToU = Math.ceil(_hourToUnit(autoToH));
|
||||
|
||||
Reference in New Issue
Block a user