Lizenz-Gate: Schueler ohne Lizenz gesperrt + Lehrer-Cockpit-Warnung
Teil 1 — Schueler-Lizenz-Gate (gilt fuer alle Klassen-Schueler ohne gueltige Lizenz = zugewiesen, nicht storniert, nicht abgelaufen): - Server-Gate zentral im Front-Controller (index.php + php/lib/license_gate.php): eingeloggte Klassen-Schueler ohne Lizenz sehen bei JEDER Sim-Route statt der Simulation eine Hinweis-Seite "Deine Lehrperson muss dir noch eine gueltige Lizenz zuweisen". Schnell-Ausstieg fuer Nicht-Schueler/Nicht-Sim (kein Cookie -> keine DB-Abfrage). Autodidakt ohne Klasse: nicht lizenzpflichtig. - modules.php?student=1 liefert 'licensed'; Schueler-Cockpit (schueler.html) zeigt oben die Meldung und sperrt die Start-Buttons. Teil 2 — Lehrer-Cockpit: - Rote Warnleiste ganz oben, wenn Schueler:innen ohne gueltige Lizenz in der Klasse sind (+ Link "Jetzt zuweisen"). licenses?class_id=X liefert dazu die Liste 'unlicensed'. - Lizenzen-Lasche: neue Sektion "Ohne gueltige Lizenz" mit Einzel- und Sammel-Zuweisung (auto_assign) — sonst fuehrt die Warnung ins Leere. Kunden-Vereinfachung (Feedback): keine Auto-Erkennung/Dedup mehr (Ansprech- partner koennen wechseln). Kunde = Freitext "Name/Verlag/Schule" + Mailadresse, je Bestellung gespeichert. Editierbares Kundennummer-Feld + Autocomplete entfernt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -209,6 +209,9 @@
|
||||
<button class="btn-sm" onclick="openTickets()">🎫 Tickets</button>
|
||||
</div>
|
||||
|
||||
<!-- Lizenz-Warnung (Schüler:innen ohne gültige Lizenz) -->
|
||||
<div id="license-warning" style="display:none"></div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="tabs" id="tab-bar">
|
||||
<div class="tab on" onclick="switchTab('overview')">Übersicht</div>
|
||||
@@ -2801,6 +2804,7 @@ async function loadClasses() {
|
||||
if (validTabs[initialTab]) currentTab = initialTab;
|
||||
loadTab(currentTab);
|
||||
if (validTabs[initialTab]) switchTab(initialTab);
|
||||
checkLicenseWarning();
|
||||
window.addEventListener('hashchange', function() {
|
||||
var t = (location.hash || '').replace('#','');
|
||||
if (validTabs[t]) switchTab(t);
|
||||
@@ -2815,6 +2819,26 @@ function onClassChange() {
|
||||
document.getElementById('class-code').textContent = currentClass?.join_code || '—';
|
||||
loadTab(currentTab);
|
||||
startLiveStatusPoll();
|
||||
checkLicenseWarning();
|
||||
}
|
||||
|
||||
// Rote Cockpit-Warnung: Schüler:innen der Klasse ohne gültige Lizenz.
|
||||
async function checkLicenseWarning() {
|
||||
var el = document.getElementById('license-warning');
|
||||
if (!el) return;
|
||||
if (!currentClassId) { el.style.display='none'; return; }
|
||||
try {
|
||||
var r = await api('licenses?class_id=' + currentClassId);
|
||||
var n = (r && r.unlicensed) ? r.unlicensed.length : 0;
|
||||
if (n > 0) {
|
||||
el.innerHTML = '<div style="background:#fdece9;border-bottom:2px solid #c0392b;color:#7a2318;padding:.6rem 1rem;font-size:.8rem;display:flex;align-items:center;gap:.6rem;flex-wrap:wrap">'
|
||||
+ '<span style="font-size:1.1rem">🔑</span>'
|
||||
+ '<span><strong>'+n+' '+(n===1?'Schüler:in hat':'Schüler:innen haben')+' noch keine gültige Lizenz</strong> — ohne Lizenz können sie keine Simulationen nutzen.</span>'
|
||||
+ '<button class="btn-sm" style="margin-left:auto" onclick="switchTab(\'licenses\')">Jetzt zuweisen →</button>'
|
||||
+ '</div>';
|
||||
el.style.display = 'block';
|
||||
} else { el.style.display='none'; el.innerHTML=''; }
|
||||
} catch(e) { el.style.display='none'; }
|
||||
}
|
||||
|
||||
async function saveClass() {
|
||||
@@ -5428,6 +5452,25 @@ async function loadLicenses() {
|
||||
html += '<button class="btn" onclick="showModal(\'modal-license\')" style="margin-bottom:.8rem">🔑 Lizenzen einlösen</button>';
|
||||
html += '</div>';
|
||||
|
||||
// Ohne gültige Lizenz — direkt zuweisbar
|
||||
var unlic = r.unlicensed || [];
|
||||
html += '<div class="card"><div class="card-h">Ohne gültige Lizenz ('+unlic.length+')</div>';
|
||||
if (unlic.length > 0) {
|
||||
var freeN = r.free ? r.free.length : 0;
|
||||
html += '<p style="font-size:.72rem;color:#7a2318;margin-bottom:.5rem">Diese Schüler:innen können noch keine Simulationen nutzen.'+(freeN < unlic.length ? ' <b>Nur '+freeN+' freie Lizenz(en) verfügbar.</b>' : '')+'</p>';
|
||||
if (freeN > 0) html += '<button class="btn" style="margin-bottom:.6rem" onclick=\'assignAllUnlicensed('+JSON.stringify(unlic.map(function(s){return s.id;}))+')\'>Allen eine freie Lizenz zuweisen</button>';
|
||||
html += '<table class="students"><tr><th>Schüler*in</th><th></th></tr>';
|
||||
unlic.forEach(function(s) {
|
||||
html += '<tr><td>'+esc(s.display_name||s.username)+'</td><td>'
|
||||
+ (freeN>0 ? '<button class="btn-sm" onclick="assignLicenseToStudent('+s.id+')">Lizenz zuweisen</button>' : '<span style="color:#8a8a8a;font-size:.68rem">keine freie Lizenz</span>')
|
||||
+ '</td></tr>';
|
||||
});
|
||||
html += '</table>';
|
||||
} else {
|
||||
html += '<p style="font-size:.72rem;color:#5a8a5e">Alle Schüler:innen haben eine gültige Lizenz. ✓</p>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
// Zugewiesene Lizenzen
|
||||
html += '<div class="card"><div class="card-h">Zugewiesene Lizenzen</div>';
|
||||
if (r.assigned && r.assigned.length > 0) {
|
||||
@@ -5458,6 +5501,24 @@ async function loadLicenses() {
|
||||
html += '</div>';
|
||||
|
||||
document.getElementById('tab-licenses').innerHTML = html;
|
||||
checkLicenseWarning();
|
||||
}
|
||||
|
||||
async function assignLicenseToStudent(studentId) {
|
||||
var r = await api('licenses', {action:'auto_assign', studentId:studentId});
|
||||
if (r.error) { alert(r.error); return; }
|
||||
loadLicenses();
|
||||
}
|
||||
async function assignAllUnlicensed(ids) {
|
||||
if (!ids || !ids.length) return;
|
||||
if (!confirm(ids.length + ' Schüler:innen je eine freie Lizenz zuweisen?')) return;
|
||||
var failed = null;
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var r = await api('licenses', {action:'auto_assign', studentId:ids[i]});
|
||||
if (r.error) { failed = r.error; break; }
|
||||
}
|
||||
if (failed) alert('Nicht alle konnten zugewiesen werden: ' + failed);
|
||||
loadLicenses();
|
||||
}
|
||||
|
||||
// === BULK SELECTION ===
|
||||
|
||||
Reference in New Issue
Block a user