Atlas: Infrastruktur + Team-Konventionen + Sprachregel Lernarbeit
- Design-System (assets/css/design-system.css) mit 21 Komponenten, iPad-Responsive-Breakpoints, Touch-Ziele 36px, Music-Player, Glossar-Tooltips - Templates (sims/template.html + student/teacher-Dashboard) - Docs: module-interface.md (inkl. 4a Sprachregel, 4b Leichte Sprache, 4c iPad), content-architecture.md, crash-recovery.md, music-registry.md - Admin-Infrastruktur: admin-modules.html + api/admin-modules.php (Titel, Emoji, Bild, Status, Dauer, Alter pro Modul) - Inbox-System: _inbox/README.md + _status.md fuer Atlas + Briefings an Klima, Glossar, Lehrplan, Fluss - Zentrale SFX-Pipeline (scripts/generate-sounds.py) - DALL-E-Bilder: 8 Badges + 5 Glossar-Repraesentationsbilder (Querformat) - Logo + Inter-Font lokal - PHP-APIs: admin, glossar, levels, licenses, progress, waypoints, assignments, profile, tickets - Spielsprache entfernt (admin-modules, admin-levels, schueler) - Landing-Page-Bearbeitungen (Boote sichtbarer, Button-Hintergrund) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+139
-29
@@ -1,26 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* API: Modulfreigabe (Lehrer steuert welche Module Schueler sehen/spielen)
|
||||
* GET /api/modules?class_id=X → Modulstatus fuer Klasse
|
||||
* POST /api/modules {action} → Modul freigeben/sperren/starten
|
||||
* API: Modulfreigabe (Klasse + individuell pro Schüler)
|
||||
* GET /api/modules?class_id=X → Modulstatus für Klasse
|
||||
* GET /api/modules?class_id=X&matrix=1 → Matrix: alle Schüler × alle Module
|
||||
* POST /api/modules {action} → Modul freigeben/sperren
|
||||
*/
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$db = Database::get();
|
||||
|
||||
// Alle verfuegbaren Module
|
||||
$ALL_MODULES = [
|
||||
['id' => 'sim-05', 'name' => 'Klimawächter', 'desc' => 'Treibhauseffekt-Simulation'],
|
||||
['id' => 'sim-05-3d', 'name' => 'Klimawächter 3D', 'desc' => '3D-Version der Klimasimulation'],
|
||||
['id' => 'sim-07', 'name' => 'Erdbeben', 'desc' => 'Plattentektonik und Stadtplanung'],
|
||||
['id' => 'sim-09', 'name' => 'Energiemix', 'desc' => 'Energiewende planen'],
|
||||
['id' => 'sim-10', 'name' => 'Lieferketten', 'desc' => 'Globale Lieferketten verstehen'],
|
||||
['id' => 'sim-11', 'name' => 'Regenwald', 'desc' => 'Regenwald-Exploration'],
|
||||
['id' => 'sim-12', 'name' => 'Flussmanagement', 'desc' => 'Fluss und Hochwasserschutz'],
|
||||
['id' => 'sim-13', 'name' => 'Stadt & Raumplanung', 'desc' => 'Stadtsimulation mit Kacheln'],
|
||||
['id' => 'sim-05', 'name' => 'Klimawächter 2D', 'desc' => 'Treibhauseffekt-Simulation', 'icon' => '🌡️'],
|
||||
['id' => 'sim-05-3d', 'name' => 'Klimawächter 3D', 'desc' => '3D-Klimasimulation', 'icon' => '🌊'],
|
||||
['id' => 'sim-07', 'name' => 'Erdbeben', 'desc' => 'Plattentektonik & Stadtplanung', 'icon' => '🌋'],
|
||||
['id' => 'sim-09', 'name' => 'Energiemix', 'desc' => 'Energiewende planen', 'icon' => '⚡'],
|
||||
['id' => 'sim-10', 'name' => 'Lieferketten', 'desc' => 'Globale Lieferketten', 'icon' => '👕'],
|
||||
['id' => 'sim-11', 'name' => 'Regenwald', 'desc' => 'Regenwald-Expedition', 'icon' => '🌴'],
|
||||
['id' => 'sim-12', 'name' => 'Flussmanagement', 'desc' => 'Fluss & Hochwasserschutz', 'icon' => '🏞️'],
|
||||
['id' => 'sim-13', 'name' => 'Stadt & Raumplanung', 'desc' => 'Stadtsimulation mit Kacheln', 'icon' => '🏗️'],
|
||||
['id' => 'sim-14', 'name' => 'Helikopter-Navigation', 'desc' => 'Rettungshubschrauber steuern', 'icon' => '🚁'],
|
||||
];
|
||||
|
||||
if ($method === 'GET') {
|
||||
// Schüler*innen-Ansicht: welche Module sind für mich freigeschaltet?
|
||||
if (isset($_GET['student']) && $_GET['student'] === '1') {
|
||||
$sessionId = Session::requireStudent();
|
||||
$session = $db->fetchOne('SELECT class_id FROM student_sessions WHERE id = ?', [$sessionId]);
|
||||
if (!$session || !$session['class_id']) {
|
||||
// Kein Klasse = alle Module frei (Autodidakt)
|
||||
$result = [];
|
||||
foreach ($ALL_MODULES as $mod) {
|
||||
$result[] = ['id' => $mod['id'], 'name' => $mod['name'], 'icon' => $mod['icon'], 'mode' => 'free'];
|
||||
}
|
||||
Response::ok($result);
|
||||
}
|
||||
$classId = $session['class_id'];
|
||||
$classMap = [];
|
||||
$settings = $db->fetchAll('SELECT module_id, mode FROM class_modules WHERE class_id = ?', [$classId]);
|
||||
foreach ($settings as $s) $classMap[$s['module_id']] = $s['mode'];
|
||||
|
||||
// Individuelle Overrides für diesen Schüler
|
||||
// Finde student_id aus session
|
||||
$studentRow = $db->fetchOne(
|
||||
'SELECT s.id FROM students s JOIN student_sessions ss ON ss.class_id = s.class_id AND ss.display_name = s.display_name WHERE ss.id = ?',
|
||||
[$sessionId]
|
||||
);
|
||||
$overrides = [];
|
||||
if ($studentRow) {
|
||||
$rows = $db->fetchAll('SELECT module_id, mode FROM student_modules WHERE student_id = ?', [$studentRow['id']]);
|
||||
foreach ($rows as $r) $overrides[$r['module_id']] = $r['mode'];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($ALL_MODULES as $mod) {
|
||||
$mode = $overrides[$mod['id']] ?? $classMap[$mod['id']] ?? 'locked';
|
||||
$result[] = ['id' => $mod['id'], 'name' => $mod['name'], 'icon' => $mod['icon'], 'mode' => $mode];
|
||||
}
|
||||
Response::ok($result);
|
||||
}
|
||||
|
||||
$teacherId = Session::requireTeacher();
|
||||
$classId = (int)($_GET['class_id'] ?? 0);
|
||||
if (!$classId) Response::error('class_id erforderlich');
|
||||
@@ -28,22 +66,72 @@ if ($method === 'GET') {
|
||||
$class = $db->fetchOne('SELECT id FROM classes WHERE id = ? AND teacher_id = ?', [$classId, $teacherId]);
|
||||
if (!$class) Response::error('Klasse nicht gefunden', 404);
|
||||
|
||||
// Aktuelle Freigaben laden
|
||||
$settings = $db->fetchAll('SELECT module_id, mode, started_at, due_date FROM class_modules WHERE class_id = ?', [$classId]);
|
||||
$settingsMap = [];
|
||||
foreach ($settings as $s) $settingsMap[$s['module_id']] = $s;
|
||||
// Klassen-Defaults laden
|
||||
$settings = $db->fetchAll('SELECT module_id, mode FROM class_modules WHERE class_id = ?', [$classId]);
|
||||
$classMap = [];
|
||||
foreach ($settings as $s) $classMap[$s['module_id']] = $s['mode'];
|
||||
|
||||
// Mit allen Modulen zusammenfuehren
|
||||
// Matrix-Modus: alle Schüler × alle Module
|
||||
if (isset($_GET['matrix']) && $_GET['matrix'] === '1') {
|
||||
$students = $db->fetchAll(
|
||||
'SELECT id, username, display_name, emoji_avatar FROM students WHERE class_id = ? ORDER BY username',
|
||||
[$classId]
|
||||
);
|
||||
|
||||
// Individuelle Overrides laden
|
||||
$studentIds = array_column($students, 'id');
|
||||
$overrides = [];
|
||||
if (!empty($studentIds)) {
|
||||
$placeholders = implode(',', array_fill(0, count($studentIds), '?'));
|
||||
$rows = $db->fetchAll(
|
||||
"SELECT student_id, module_id, mode FROM student_modules WHERE student_id IN ($placeholders)",
|
||||
$studentIds
|
||||
);
|
||||
foreach ($rows as $r) {
|
||||
$overrides[$r['student_id']][$r['module_id']] = $r['mode'];
|
||||
}
|
||||
}
|
||||
|
||||
// Matrix aufbauen
|
||||
$matrix = [];
|
||||
foreach ($students as $s) {
|
||||
$row = [
|
||||
'id' => (int)$s['id'],
|
||||
'username' => $s['username'],
|
||||
'displayName' => $s['display_name'] ?: $s['username'],
|
||||
'emoji' => $s['emoji_avatar'] ?: '🧑🎓',
|
||||
'modules' => [],
|
||||
];
|
||||
foreach ($ALL_MODULES as $mod) {
|
||||
$row['modules'][$mod['id']] = $overrides[$s['id']][$mod['id']] ?? $classMap[$mod['id']] ?? 'locked';
|
||||
}
|
||||
$matrix[] = $row;
|
||||
}
|
||||
|
||||
// Modul-Infos mit Klassen-Defaults
|
||||
$modulesInfo = [];
|
||||
foreach ($ALL_MODULES as $mod) {
|
||||
$modulesInfo[] = [
|
||||
'id' => $mod['id'],
|
||||
'name' => $mod['name'],
|
||||
'desc' => $mod['desc'],
|
||||
'icon' => $mod['icon'],
|
||||
'classMode' => $classMap[$mod['id']] ?? 'locked',
|
||||
];
|
||||
}
|
||||
|
||||
Response::ok(['modules' => $modulesInfo, 'students' => $matrix]);
|
||||
}
|
||||
|
||||
// Standard: nur Klassen-Defaults
|
||||
$result = [];
|
||||
foreach ($ALL_MODULES as $mod) {
|
||||
$s = $settingsMap[$mod['id']] ?? null;
|
||||
$result[] = [
|
||||
'id' => $mod['id'],
|
||||
'name' => $mod['name'],
|
||||
'desc' => $mod['desc'],
|
||||
'mode' => $s['mode'] ?? 'locked',
|
||||
'startedAt' => $s['started_at'] ?? null,
|
||||
'dueDate' => $s['due_date'] ?? null,
|
||||
'icon' => $mod['icon'],
|
||||
'mode' => $classMap[$mod['id']] ?? 'locked',
|
||||
];
|
||||
}
|
||||
Response::ok($result);
|
||||
@@ -61,24 +149,46 @@ if ($method === 'POST') {
|
||||
$class = $db->fetchOne('SELECT id FROM classes WHERE id = ? AND teacher_id = ?', [$classId, $teacherId]);
|
||||
if (!$class) Response::error('Klasse nicht gefunden', 404);
|
||||
|
||||
// Klassen-Default setzen
|
||||
if ($action === 'set_mode') {
|
||||
$mode = $body['mode'] ?? 'locked';
|
||||
if (!in_array($mode, ['locked', 'free', 'teacher_started'])) Response::error('Ungültiger Modus');
|
||||
|
||||
$dueDate = $body['dueDate'] ?? null;
|
||||
$db->execute(
|
||||
'INSERT INTO class_modules (class_id, module_id, enabled, mode)
|
||||
VALUES (?, ?, 1, ?)
|
||||
ON DUPLICATE KEY UPDATE mode = VALUES(mode), enabled = 1',
|
||||
[$classId, $moduleId, $mode]
|
||||
);
|
||||
Response::ok();
|
||||
}
|
||||
|
||||
// Individuell pro Schüler setzen
|
||||
if ($action === 'set_student_mode') {
|
||||
$studentId = (int)($body['studentId'] ?? 0);
|
||||
$mode = $body['mode'] ?? 'locked';
|
||||
if (!$studentId) Response::error('studentId erforderlich');
|
||||
if (!in_array($mode, ['locked', 'free', 'teacher_started'])) Response::error('Ungültiger Modus');
|
||||
|
||||
// Prüfen dass Schüler zur Klasse gehört
|
||||
$student = $db->fetchOne('SELECT id FROM students WHERE id = ? AND class_id = ?', [$studentId, $classId]);
|
||||
if (!$student) Response::error('Schüler nicht in dieser Klasse');
|
||||
|
||||
$db->execute(
|
||||
'INSERT INTO class_modules (class_id, module_id, enabled, mode, started_at, due_date)
|
||||
VALUES (?, ?, ?, ?, NOW(), ?)
|
||||
ON DUPLICATE KEY UPDATE mode = VALUES(mode), started_at = VALUES(started_at), due_date = VALUES(due_date), enabled = 1',
|
||||
[$classId, $moduleId, 1, $mode, $dueDate]
|
||||
'INSERT INTO student_modules (student_id, module_id, mode)
|
||||
VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE mode = VALUES(mode)',
|
||||
[$studentId, $moduleId, $mode]
|
||||
);
|
||||
Response::ok();
|
||||
}
|
||||
|
||||
// Klassen-Default für ALLE Schüler übernehmen (reset overrides)
|
||||
if ($action === 'reset_overrides') {
|
||||
$db->execute(
|
||||
"INSERT INTO activity_log (user_type, user_id, action, detail) VALUES ('teacher', ?, 'set_module_mode', ?)",
|
||||
[$teacherId, "$moduleId → $mode"]
|
||||
'DELETE sm FROM student_modules sm JOIN students s ON s.id = sm.student_id WHERE s.class_id = ? AND sm.module_id = ?',
|
||||
[$classId, $moduleId]
|
||||
);
|
||||
|
||||
Response::ok();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user