Übersicht: Schüler-Drilldown (Klick → aktuelle Simulationsparameter, cockpit-artig)
live.php: student_runs=<id> liefert letzte Läufe (detail_json = Parameter) + Live-State + Recency. teacher.html: Klick auf Triage-Zeile öffnet Modal mit Erfolg/Zuletzt, Live- Tätigkeit und den letzten Durchläufen samt geparsten Sim-Parametern (CO₂, Fehlklicks …). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -443,6 +443,56 @@ if ($method === 'GET') {
|
||||
]);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// SCHÜLER-DRILLDOWN — student_runs=<id>
|
||||
// Aktuelle Sim-Parameter: letzte Läufe (detail_json) + Live-State + Recency.
|
||||
// ===================================================================
|
||||
if (isset($_GET['student_runs'])) {
|
||||
$studentId = (int)$_GET['student_runs'];
|
||||
$stu = $db->fetchOne(
|
||||
'SELECT s.id, s.display_name, s.username, s.emoji_avatar
|
||||
FROM students s JOIN classes c ON c.id = s.class_id
|
||||
WHERE s.id = ? AND c.teacher_id = ?',
|
||||
[$studentId, $teacherId]);
|
||||
if (!$stu) Response::error('Schüler:in nicht gefunden', 404);
|
||||
|
||||
$runs = $db->fetchAll(
|
||||
'SELECT module_id, score, duration_sec, detail_json, completed_at
|
||||
FROM student_results WHERE student_id = ? ORDER BY completed_at DESC LIMIT 12',
|
||||
[$studentId]);
|
||||
$live = $db->fetchOne(
|
||||
'SELECT module_id, state_json, UNIX_TIMESTAMP(last_seen) AS last_seen_ts
|
||||
FROM live_sessions WHERE student_id = ? ORDER BY last_seen DESC LIMIT 1',
|
||||
[$studentId]);
|
||||
$active = $live && ((int)$live['last_seen_ts'] >= time() - LIVE_ACTIVE_WINDOW_SEC);
|
||||
|
||||
// Recency-Kennzahlen aus den Läufen
|
||||
$recentScores = []; $daysSince = null;
|
||||
foreach ($runs as $i => $r) {
|
||||
if ($daysSince === null && $r['completed_at']) $daysSince = (int)floor((time() - strtotime($r['completed_at'])) / 86400);
|
||||
if ($i < 3 && $r['score'] !== null) $recentScores[] = (float)$r['score'];
|
||||
}
|
||||
$recentErfolg = $recentScores ? (int)round(array_sum($recentScores) / count($recentScores)) : null;
|
||||
|
||||
Response::ok([
|
||||
'studentId' => $studentId,
|
||||
'displayName' => $stu['display_name'] ?: $stu['username'],
|
||||
'emoji' => $stu['emoji_avatar'] ?: '🧑🎓',
|
||||
'recentErfolg' => $recentErfolg,
|
||||
'daysSince' => $daysSince,
|
||||
'live' => $active ? ['moduleId' => $live['module_id'], 'state' => json_decode($live['state_json'], true)] : null,
|
||||
'runs' => array_map(function ($r) {
|
||||
return [
|
||||
'moduleId' => $r['module_id'],
|
||||
'score' => $r['score'] !== null ? (float)$r['score'] : null,
|
||||
'durationSec' => (int)$r['duration_sec'],
|
||||
'completedAt' => $r['completed_at'],
|
||||
'params' => $r['detail_json'] ? json_decode($r['detail_json'], true) : null,
|
||||
];
|
||||
}, $runs),
|
||||
]);
|
||||
}
|
||||
|
||||
// Klassen-Liste: alle aktiven Sessions
|
||||
$classId = (int)($_GET['class_id'] ?? 0);
|
||||
if (!$classId) Response::error('class_id erforderlich');
|
||||
|
||||
Reference in New Issue
Block a user