Lehreransicht aufräumen: Sim-Labels + Logistik-Spalten + assessment-Skip-Fix
- SIM_LABELS: EIN Wort + eindeutiges 3-Buchstaben-Kürzel je App (zentrale Quelle simWord()/simCode()). Tages-Balken nutzt jetzt Kürzel (eng) → Wort (mittel) → Wort+Lektion (breit) statt kryptischem "Log L1". - LIVE_PRIMARY_FIELDS['logistik'] ergänzt (Stufe · Budget · Geliefert/Gesamt · Unterwegs · Offen · Verspätet · Fortschritt) — vorher Roh-Dump aller Keys. - Aktuell-Fallback (uncurated Sims): interne __-Heartbeat-Keys + simId/ timestamp/screen ausgeblendet, nicht mehr als Spalten. - assessment.php: Skip-Bug gefixt — las phase aus $body['processLog']['phase'], gesendet wird $body['phase']. Dadurch landeten alle 'started'/'running'-Pings als LEERE Zeilen in assessments und verschmutzten Ergebnisse/Tag. Jetzt greift der Skip; nur echte 'completed'-Abschlüsse werden gespeichert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -29,8 +29,13 @@ $durationMs = max(0, min($rawDuration, GGS_MAX_SESSION_MS));
|
||||
// Der Live-Zustand für das Lehrer-Cockpit kommt über den Heartbeat (live_sessions);
|
||||
// diese 30-s-Pings (phase 'running'/'started') haben früher die Tabelle zugemüllt
|
||||
// (~96 % der Zeilen). Nur echte Abschlüsse/Ergebnisse werden gespeichert.
|
||||
// phase steht auf oberster Body-Ebene (alle Sims senden {simId, phase, …}).
|
||||
// Früher wurde fälschlich $body['processLog']['phase'] gelesen → der Skip
|
||||
// griff nie, und JEDER 'started'/'running'-Ping landete als leere Zeile in
|
||||
// assessments (verschmutzt Ergebnisse/Tag). Robust: oberste Ebene, Fallback nested.
|
||||
$pl = $body['processLog'] ?? [];
|
||||
$phase = (is_array($pl) && isset($pl['phase'])) ? $pl['phase'] : null;
|
||||
$phase = $body['phase']
|
||||
?? ((is_array($pl) && isset($pl['phase'])) ? $pl['phase'] : null);
|
||||
if ($phase === 'running' || $phase === 'started') {
|
||||
Response::ok(['skipped' => true]);
|
||||
}
|
||||
|
||||
+64
-11
@@ -888,6 +888,26 @@ var LIVE_PRIMARY_FIELDS = {
|
||||
{k:'time_left_s', label:'⏱ Rest', align:'center', fmt:function(v){ if (v==null) return '<span style="color:#8a8a8a">∞</span>'; var c = v<=30 ? '#c07a6b' : v<=120 ? '#c9913a' : '#5a8a5e'; return '<span style="color:'+c+';font-weight:700">'+_fmtSeconds(v)+'</span>'; }},
|
||||
{k:'airport', label:'✈ Flughafen', align:'left'},
|
||||
],
|
||||
'logistik': [
|
||||
{k:'levelId', label:'Stufe', align:'center'},
|
||||
{k:'balance', label:'Budget', fmt:function(v){
|
||||
if (v==null) return '—';
|
||||
var c = v>=5000 ? '#5a8a5e' : v>=0 ? '#c9913a' : '#c07a6b';
|
||||
return '<span style="color:'+c+';font-weight:700">'+Number(v).toLocaleString('de-AT')+' €</span>';
|
||||
}},
|
||||
{k:'ordersDelivered', label:'Geliefert', align:'center', fmt:function(v, row){
|
||||
var s = row.state||{}; var t = s.totalOrders||0; var done = v||0;
|
||||
var c = done>0 ? '#5a8a5e' : '#8a8a8a';
|
||||
return '<b style="color:'+c+'">'+done+'</b>'+(t?'<span style="color:#8a8a8a"> / '+t+'</span>':'');
|
||||
}},
|
||||
{k:'ordersInFlight', label:'Unterwegs', align:'center'},
|
||||
{k:'ordersOpen', label:'Offen', align:'center'},
|
||||
{k:'ordersLate', label:'Verspätet', align:'center', fmt:function(v){
|
||||
var n = v||0;
|
||||
return n>0 ? '<span style="color:#c07a6b;font-weight:700">'+n+'</span>' : '<span style="color:#5a8a5e">0</span>';
|
||||
}},
|
||||
{k:'progressPct', label:'Fortschritt', fmt:function(v){ return _liveBar(v, 0, 100, 'high'); }},
|
||||
],
|
||||
// Default-Schema: zeigt einfach den state, soweit Felder benannt sind.
|
||||
_default: null,
|
||||
};
|
||||
@@ -1337,10 +1357,16 @@ async function renderLive() {
|
||||
// Spalten-Schema: bevorzugt LIVE_PRIMARY_FIELDS, sonst Vereinigung aller state-Keys.
|
||||
var fields = LIVE_PRIMARY_FIELDS[modId];
|
||||
if (!fields) {
|
||||
// Kein kuratiertes Schema → nur sinnvolle State-Keys zeigen.
|
||||
// Interne Heartbeat-Keys (__…) und Rausch-Felder NIE anzeigen.
|
||||
var _hideKeys = {simId:1, timestamp:1, screen:1};
|
||||
var keySet = {};
|
||||
grp.rows.forEach(function(r) {
|
||||
var s = r.state || {};
|
||||
Object.keys(s).forEach(function(k) { keySet[k] = true; });
|
||||
Object.keys(s).forEach(function(k) {
|
||||
if (k.indexOf('__') === 0 || _hideKeys[k]) return;
|
||||
keySet[k] = true;
|
||||
});
|
||||
});
|
||||
fields = Object.keys(keySet).map(function(k) { return {k:k, label:k}; });
|
||||
}
|
||||
@@ -1430,14 +1456,39 @@ async function renderLive() {
|
||||
|
||||
// === LIVE-ÜBERSICHT (Tages-Balkenchart pro Schüler*in) ===
|
||||
// Kurz-Form pro Sim für Balken-Label
|
||||
var LIVE_SIM_SHORT = {
|
||||
heli: 'Heli', klima: 'Klima', 'klima-3d': 'Klima 3D',
|
||||
busfahrt: 'Bus', fluggesellschaft: 'Flug',
|
||||
sonnensystem: 'Sonne', 'eu-werkstatt': 'EU', energiemanager: 'Energie',
|
||||
farmer: 'Farmer', fluss: 'Fluss', logistik: 'Log', stadt: 'Stadt',
|
||||
regenwald: 'Regenw.', staustufen: 'Stau', erdbeben: 'Erdb.',
|
||||
vulkan: 'Vulkan', wal: 'Wal',
|
||||
// Kanonische Sim-Bezeichner: EIN Wort + 3-Buchstaben-Kürzel je App.
|
||||
// Wort wo Platz ist, Kürzel in engen Balken. Zentrale Quelle — auch anderswo
|
||||
// nutzbar (Tages-Balken, Kompakt-Anzeigen). Kürzel sind eindeutig.
|
||||
var SIM_LABELS = {
|
||||
weltkueche: { w:'Weltküche', c:'WEK' },
|
||||
klima: { w:'Klima', c:'KLI' },
|
||||
'klima-3d': { w:'Klima 3D', c:'K3D' },
|
||||
staustufen: { w:'Staustufen', c:'STU' },
|
||||
fluss: { w:'Fluss', c:'FLU' },
|
||||
heli: { w:'Heli', c:'HEL' },
|
||||
energiemanager: { w:'Energie', c:'ENE' },
|
||||
'eu-werkstatt': { w:'EU-Werkstatt', c:'EUW' },
|
||||
regenwald: { w:'Regenwald', c:'RGW' },
|
||||
erdbeben: { w:'Erdbeben', c:'ERD' },
|
||||
energiemix: { w:'Energiemix', c:'EMX' },
|
||||
lieferketten: { w:'Lieferketten', c:'LIF' },
|
||||
farmer: { w:'Farmer', c:'FAR' },
|
||||
busfahrt: { w:'Busfahrt', c:'BUS' },
|
||||
sonnensystem: { w:'Sonne', c:'SON' },
|
||||
tourismustal: { w:'Tourismustal', c:'TAL' },
|
||||
tourismusregion: { w:'Tourismusregion', c:'REG' },
|
||||
logistik: { w:'Logistik', c:'LOG' },
|
||||
fluggesellschaft: { w:'Flug', c:'FLG' },
|
||||
vulkan: { w:'Vulkan', c:'VUL' },
|
||||
wal: { w:'Wal', c:'WAL' },
|
||||
stadt: { w:'Stadt', c:'STD' },
|
||||
kofferdetektiv: { w:'Koffer', c:'KOF' },
|
||||
entscheidungstag: { w:'Alltag', c:'ALT' },
|
||||
};
|
||||
function simWord(id){ return (SIM_LABELS[id] && SIM_LABELS[id].w) || id; }
|
||||
function simCode(id){ return (SIM_LABELS[id] && SIM_LABELS[id].c) || String(id||'').slice(0,3).toUpperCase(); }
|
||||
// Rückwärtskompatibel: LIVE_SIM_SHORT[id] = Wort je Sim.
|
||||
var LIVE_SIM_SHORT = Object.keys(SIM_LABELS).reduce(function(o,k){ o[k]=SIM_LABELS[k].w; return o; }, {});
|
||||
function _liveFmtMin(sec) {
|
||||
if (sec < 60) return sec + 's';
|
||||
var m = Math.floor(sec/60); var s = sec%60;
|
||||
@@ -1998,14 +2049,16 @@ async function renderLiveSummary() {
|
||||
var pctL = _msToPct(sess.startMs);
|
||||
var pctR = _msToPct(endMs);
|
||||
var pctW = Math.max(0.8, pctR - pctL); // mindestens 0.8 % sichtbar
|
||||
var simShort = LIVE_SIM_SHORT[sess.simId] || sess.simIcon || sess.simId;
|
||||
var lbl = simShort + (sess.lessonLabel ? ' '+sess.lessonLabel : '');
|
||||
var simW = simWord(sess.simId);
|
||||
var simC = simCode(sess.simId);
|
||||
var lbl = simW + (sess.lessonLabel ? ' '+sess.lessonLabel : '');
|
||||
var color = _liveSuccessColor(sess.success);
|
||||
var tip = (sess.simTitle||sess.simId) + (sess.lessonLabel?' · '+sess.lessonLabel:'')
|
||||
+ ' · ' + _fmtHM(sess.startMs) + '–' + _fmtHM(endMs)
|
||||
+ ' · ' + _liveFmtMin(realDurSec)
|
||||
+ (sess.stars!=null?' · '+sess.stars+'/5★':'');
|
||||
var inner = (pctW < 3) ? '' : (pctW < 8) ? simShort : lbl;
|
||||
// enger Balken → 3-Buchstaben-Kürzel, mittel → Wort, breit → Wort + Lektion
|
||||
var inner = (pctW < 2.5) ? '' : (pctW < 6) ? simC : (pctW < 11) ? simW : lbl;
|
||||
var sessKey = s.studentId + ':' + sess.simId + ':' + (sess.lessonLabel||'_') + ':' + sess.startMs;
|
||||
html += '<div onclick="openLiveSessionDetail(\''+sessKey+'\')" '
|
||||
+ 'title="'+esc(tip)+'" '
|
||||
|
||||
Reference in New Issue
Block a user