169 lines
6.2 KiB
HTML
169 lines
6.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Lehrer Zimmer (Admin)</title>
|
|
<style>
|
|
body { font-family: sans-serif; background: #222; color: #ddd; padding: 20px; max-width: 800px; margin: 0 auto; }
|
|
|
|
/* Header & Suche */
|
|
.header { display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #555; padding-bottom: 20px; margin-bottom: 20px; }
|
|
h1 { margin: 0; color: #ffcc00; }
|
|
|
|
input[type="text"] {
|
|
padding: 10px; font-size: 16px; width: 250px;
|
|
background: #333; border: 1px solid #666; color: white;
|
|
}
|
|
|
|
/* Tabs */
|
|
.tabs { display: flex; gap: 10px; margin-bottom: 20px; }
|
|
.tab-btn {
|
|
flex: 1; padding: 15px; cursor: pointer; background: #333; border: none; color: #888; font-weight: bold; font-size: 16px;
|
|
}
|
|
.tab-btn.active { background: #4caf50; color: white; }
|
|
.tab-btn#tab-public.active { background: #2196F3; } /* Blau für Public */
|
|
|
|
/* Liste */
|
|
.entry {
|
|
background: #333; padding: 15px; margin-bottom: 8px;
|
|
display: flex; justify-content: space-between; align-items: center;
|
|
border-left: 5px solid #555;
|
|
}
|
|
.entry.highlight { border-left-color: #ffeb3b; background: #444; } /* Suchtreffer */
|
|
|
|
.info { font-size: 1.1em; }
|
|
.meta { font-size: 0.85em; color: #aaa; margin-top: 4px; font-family: monospace; }
|
|
.code { color: #00e5ff; font-weight: bold; font-size: 1.1em; letter-spacing: 1px; }
|
|
|
|
/* Buttons */
|
|
button { cursor: pointer; padding: 8px 15px; border: none; font-weight: bold; color: white; border-radius: 4px; }
|
|
.btn-approve { background: #4caf50; margin-right: 5px; }
|
|
.btn-delete { background: #f44336; }
|
|
.btn-delete:hover { background: #d32f2f; }
|
|
|
|
.empty { text-align: center; padding: 40px; color: #666; font-style: italic; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="header">
|
|
<h1>👨🏫 Lehrer Zimmer</h1>
|
|
<input type="text" id="searchInput" placeholder="Suche Code oder Name..." onkeyup="filterList()">
|
|
</div>
|
|
|
|
<div class="tabs">
|
|
<button id="tab-unverified" class="tab-btn active" onclick="switchTab('unverified')">⏳ Warteschlange (Prüfen)</button>
|
|
<button id="tab-public" class="tab-btn" onclick="switchTab('public')">🏆 Bestenliste (Fertig)</button>
|
|
</div>
|
|
|
|
<div id="list">Lade...</div>
|
|
|
|
<script>
|
|
let currentTab = 'unverified';
|
|
let allEntries = []; // Speichert die geladenen Daten für die Suche
|
|
|
|
async function loadList() {
|
|
const listEl = document.getElementById('list');
|
|
listEl.innerHTML = '<div style="text-align:center">Lade Daten...</div>';
|
|
|
|
try {
|
|
// API Aufruf mit Typ (unverified oder public)
|
|
const res = await fetch('/api/admin/list?type=' + currentTab);
|
|
allEntries = await res.json();
|
|
|
|
renderList(allEntries);
|
|
} catch(e) {
|
|
listEl.innerHTML = '<div class="empty">Fehler beim Laden der Daten.</div>';
|
|
}
|
|
}
|
|
|
|
function renderList(data) {
|
|
const listEl = document.getElementById('list');
|
|
|
|
if (!data || data.length === 0) {
|
|
listEl.innerHTML = "<div class='empty'>Liste ist leer.</div>";
|
|
return;
|
|
}
|
|
|
|
let html = "";
|
|
data.forEach(entry => {
|
|
// Buttons: In der "Public" Liste brauchen wir keinen "Freigeben" Button mehr
|
|
let actions = '';
|
|
if (currentTab === 'unverified') {
|
|
actions += `<button class="btn-approve" onclick="decide('${entry.sessionId}', 'approve')">✔ OK</button>`;
|
|
}
|
|
// Löschen darf man immer (falls man sich verklickt hat oder Schüler nervt)
|
|
actions += `<button class="btn-delete" onclick="decide('${entry.sessionId}', 'delete')">🗑 Weg</button>`;
|
|
|
|
html += `
|
|
<div class="entry">
|
|
<div class="info">
|
|
<strong>${entry.name || 'Unbekannt'}</strong>
|
|
<span style="color:#ffcc00; font-weight:bold; margin-left:10px;">${entry.score} Pkt</span>
|
|
<div class="meta">
|
|
CODE: <span class="code">${entry.code || '---'}</span> | ${entry.time || '?'}
|
|
</div>
|
|
</div>
|
|
<div class="actions">
|
|
${actions}
|
|
</div>
|
|
</div>`;
|
|
});
|
|
listEl.innerHTML = html;
|
|
|
|
// Filter direkt anwenden, falls noch Text in der Suche steht
|
|
filterList();
|
|
}
|
|
|
|
// Such-Logik (Client-Side, rasend schnell)
|
|
function filterList() {
|
|
const term = document.getElementById('searchInput').value.toUpperCase();
|
|
const entries = document.querySelectorAll('.entry');
|
|
|
|
entries.forEach(div => {
|
|
// Wir suchen im gesamten Text des Eintrags (Name, Code, Score)
|
|
const text = div.innerText.toUpperCase();
|
|
if (text.includes(term)) {
|
|
div.style.display = "flex";
|
|
// Kleines Highlight wenn gesucht wird
|
|
if(term.length > 0) div.classList.add("highlight");
|
|
else div.classList.remove("highlight");
|
|
} else {
|
|
div.style.display = "none";
|
|
}
|
|
});
|
|
}
|
|
|
|
async function decide(sid, action) {
|
|
if(!confirm(action === 'approve' ? "Freigeben?" : "Endgültig löschen?")) return;
|
|
|
|
await fetch('/api/admin/action', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({ sessionId: sid, action: action })
|
|
});
|
|
loadList(); // Neu laden
|
|
}
|
|
|
|
function switchTab(tab) {
|
|
currentTab = tab;
|
|
|
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
document.getElementById('tab-' + tab).classList.add('active');
|
|
|
|
document.getElementById('searchInput').value = "";
|
|
|
|
loadList();
|
|
}
|
|
|
|
// Start
|
|
loadList();
|
|
|
|
setInterval(() => {
|
|
if(currentTab === 'unverified' && document.getElementById('searchInput').value === "") {
|
|
loadList();
|
|
}
|
|
}, 5000);
|
|
</script>
|
|
</body>
|
|
</html> |