let currentUser = null; let currentDataType = null; let currentData = []; // Beim Laden der Seite document.addEventListener('DOMContentLoaded', function () { checkAuth(); loadStatistics(); // Add cookie settings button functionality const cookieSettingsBtn = document.getElementById('cookie-settings-footer'); if (cookieSettingsBtn) { cookieSettingsBtn.addEventListener('click', function () { if (window.cookieConsent) { window.cookieConsent.resetConsent(); } }); } loadPageStatistics(); setupEventListeners(); }); function setupEventListeners() { // Logout Button document.getElementById('logoutBtn').addEventListener('click', logout); // Generator Button document.getElementById('generatorBtn').addEventListener('click', function () { window.location.href = '/generator'; }); // Modal Close Buttons document.querySelectorAll('.close').forEach(closeBtn => { closeBtn.addEventListener('click', closeModal); }); // Confirm Modal Buttons document.getElementById('confirmNo').addEventListener('click', closeModal); // Search Input document.getElementById('searchInput').addEventListener('input', filterData); // Add Form document.getElementById('addForm').addEventListener('submit', handleAddSubmit); } async function checkAuth() { try { const response = await fetch('/api/v1/web/check-session'); const result = await response.json(); if (!result.success) { window.location.href = '/adminlogin.html'; return; } currentUser = result.user; document.getElementById('username').textContent = currentUser.username; const accessBadge = document.getElementById('accessBadge'); accessBadge.textContent = `Level ${currentUser.access_level}`; accessBadge.className = `access-badge level-${currentUser.access_level}`; // Generator-Button nur für Level 2 if (currentUser.access_level >= 2) { document.getElementById('generatorBtn').style.display = 'inline-block'; } } catch (error) { console.error('Auth check failed:', error); window.location.href = '/adminlogin.html'; } } async function logout() { try { await fetch('/api/v1/public/logout', { method: 'POST' }); window.location.href = '/adminlogin.html'; } catch (error) { console.error('Logout failed:', error); window.location.href = '/adminlogin.html'; } } async function loadStatistics() { try { const response = await fetch('/api/v1/admin/stats'); const stats = await response.json(); if (stats.success) { document.getElementById('totalPlayers').textContent = stats.data.players || 0; document.getElementById('totalRuns').textContent = stats.data.runs || 0; document.getElementById('totalLocations').textContent = stats.data.locations || 0; document.getElementById('totalAdminUsers').textContent = stats.data.adminUsers || 0; } } catch (error) { console.error('Failed to load statistics:', error); } } async function loadPageStatistics() { try { const response = await fetch('/api/v1/admin/page-stats'); const result = await response.json(); if (result.success) { const data = result.data; // Display page view statistics displayPageStats('todayStats', data.today); displayPageStats('weekStats', data.week); displayPageStats('monthStats', data.month); displayPageStats('totalStats', data.total); // Display link statistics if (data.linkStats) { document.getElementById('totalPlayersCount').textContent = data.linkStats.total_players || 0; document.getElementById('linkedPlayersCount').textContent = data.linkStats.linked_players || 0; document.getElementById('linkPercentage').textContent = `${data.linkStats.link_percentage || 0}%`; } } } catch (error) { console.error('Failed to load page statistics:', error); // Show error in all stat containers ['todayStats', 'weekStats', 'monthStats', 'totalStats'].forEach(id => { document.getElementById(id).innerHTML = '
Fehler beim Laden
'; }); } } function displayPageStats(containerId, stats) { const container = document.getElementById(containerId); if (!stats || stats.length === 0) { container.innerHTML = '
0
'; return; } // Find main page visits const mainPageStat = stats.find(stat => stat.page === 'main_page_visit'); const count = mainPageStat ? mainPageStat.count : 0; container.innerHTML = `
${count}
`; } function getPageDisplayName(page) { const pageNames = { 'main_page_visit': '🏠 Hauptseiten-Besuche', 'home': '🏠 Hauptseite', 'login': '🔐 Login', 'dashboard': '📊 Dashboard', 'admin_login': '🛡️ Admin Login', 'admin_dashboard': '🛡️ Admin Dashboard', 'license_generator': '🔧 Lizenzgenerator', 'reset_password': '🔑 Passwort Reset' }; return pageNames[page] || page; } function showUserManagement() { showDataSection('users', 'Benutzer-Verwaltung'); loadUsers(); } function showPlayerManagement() { showDataSection('players', 'Spieler-Verwaltung'); loadPlayers(); } function showRunManagement() { showDataSection('runs', 'Läufe-Verwaltung'); loadRuns(); } function showLocationManagement() { showDataSection('locations', 'Standort-Verwaltung'); loadLocations(); } function showAdminUserManagement() { showDataSection('adminusers', 'Admin-Benutzer'); loadAdminUsers(); } function showSystemInfo() { showDataSection('system', 'System-Informationen'); loadSystemInfo(); } function showDataSection(type, title) { currentDataType = type; document.getElementById('dataTitle').textContent = title; document.getElementById('dataSection').style.display = 'block'; document.getElementById('searchInput').value = ''; } async function loadUsers() { // Implementation für Supabase-Benutzer document.getElementById('dataContent').innerHTML = '
Supabase-Benutzer werden über die Supabase-Console verwaltet
'; } async function loadPlayers() { try { const response = await fetch('/api/v1/admin/players'); const result = await response.json(); if (result.success) { currentData = result.data; displayPlayersTable(result.data); } else { showError('Fehler beim Laden der Spieler'); } } catch (error) { console.error('Failed to load players:', error); showError('Fehler beim Laden der Spieler'); } } async function loadRuns() { try { const response = await fetch('/api/v1/admin/runs'); const result = await response.json(); if (result.success) { currentData = result.data; displayRunsTable(result.data); } else { showError('Fehler beim Laden der Läufe'); } } catch (error) { console.error('Failed to load runs:', error); showError('Fehler beim Laden der Läufe'); } } async function loadLocations() { try { const response = await fetch('/api/v1/admin/locations'); const result = await response.json(); if (result.success) { currentData = result.data; displayLocationsTable(result.data); } else { showError('Fehler beim Laden der Standorte'); } } catch (error) { console.error('Failed to load locations:', error); showError('Fehler beim Laden der Standorte'); } } async function loadAdminUsers() { try { const response = await fetch('/api/v1/admin/adminusers'); const result = await response.json(); if (result.success) { currentData = result.data; displayAdminUsersTable(result.data); } else { showError('Fehler beim Laden der Admin-Benutzer'); } } catch (error) { console.error('Failed to load admin users:', error); showError('Fehler beim Laden der Admin-Benutzer'); } } function displayPlayersTable(players) { let html = '
'; html += ''; players.forEach(player => { html += ``; }); html += '
IDNameRFID UIDSupabase UserRegistriertAktionen
${player.id} ${player.full_name || '-'} ${player.rfiduid || '-'} ${player.supabase_user_id ? '✅' : '❌'} ${new Date(player.created_at).toLocaleDateString('de-DE')}
'; document.getElementById('dataContent').innerHTML = html; } function displayRunsTable(runs) { let html = '
'; html += ''; runs.forEach(run => { // Use the time_seconds value from the backend const timeInSeconds = parseFloat(run.time_seconds) || 0; html += ``; }); html += '
IDSpielerStandortZeitDatumAktionen
${run.id} ${run.player_name || `Player ${run.player_id}`} ${run.location_name || `Location ${run.location_id}`} ${timeInSeconds.toFixed(3)}s ${new Date(run.created_at).toLocaleDateString('de-DE')} ${new Date(run.created_at).toLocaleTimeString('de-DE')}
'; document.getElementById('dataContent').innerHTML = html; } function displayLocationsTable(locations) { let html = '
'; html += ''; locations.forEach(location => { html += ``; }); html += '
IDNameLatitudeLongitudeMindestzeit (s)ErstelltAktionen
${location.id} ${location.name} ${location.latitude} ${location.longitude} ${location.time_threshold ? (typeof location.time_threshold === 'object' ? location.time_threshold.seconds : location.time_threshold).toFixed(3) : '-'} ${new Date(location.created_at).toLocaleDateString('de-DE')}
'; document.getElementById('dataContent').innerHTML = html; } function displayAdminUsersTable(users) { let html = '
'; html += ''; users.forEach(user => { const isCurrentUser = user.id === currentUser.id; html += ``; }); html += '
IDBenutzernameAccess LevelAktivLetzter LoginAktionen
${user.id} ${user.username} ${isCurrentUser ? '(Du)' : ''} Level ${user.access_level} ${user.is_active ? '✅' : '❌'} ${user.last_login ? new Date(user.last_login).toLocaleDateString('de-DE') : 'Nie'} ${!isCurrentUser ? `` : ''}
'; document.getElementById('dataContent').innerHTML = html; } function loadSystemInfo() { document.getElementById('dataContent').innerHTML = `

Server-Informationen

Node.js Version: ${navigator.userAgent}

Aktuelle Zeit: ${new Date().toLocaleString('de-DE')}

Angemeldeter Benutzer: ${currentUser.username} (Level ${currentUser.access_level})

`; } function filterData() { const searchTerm = document.getElementById('searchInput').value.toLowerCase(); if (!currentData) return; let filteredData = currentData.filter(item => { return Object.values(item).some(value => value && value.toString().toLowerCase().includes(searchTerm) ); }); switch (currentDataType) { case 'players': displayPlayersTable(filteredData); break; case 'runs': displayRunsTable(filteredData); break; case 'locations': displayLocationsTable(filteredData); break; case 'adminusers': displayAdminUsersTable(filteredData); break; case 'achievements': if (currentAchievementMode === 'achievements') { currentAchievements = filteredData; displayAchievements(); } else { currentPlayers = filteredData; displayPlayers(); } break; } } function refreshData() { switch (currentDataType) { case 'players': loadPlayers(); break; case 'runs': loadRuns(); break; case 'locations': loadLocations(); break; case 'adminusers': loadAdminUsers(); break; case 'system': loadSystemInfo(); break; case 'achievements': if (currentAchievementMode === 'achievements') { loadAchievements(); } else { loadPlayers(); } break; } } function showAddModal() { const modal = document.getElementById('addModal'); const modalTitle = document.getElementById('modalTitle'); const formFields = document.getElementById('formFields'); // Modal-Titel basierend auf aktuellem Datentyp setzen switch (currentDataType) { case 'players': modalTitle.textContent = 'Neuen Spieler hinzufügen'; formFields.innerHTML = `
`; break; case 'locations': modalTitle.textContent = 'Neuen Standort hinzufügen'; formFields.innerHTML = `
`; break; case 'adminusers': modalTitle.textContent = 'Neuen Admin-Benutzer hinzufügen'; formFields.innerHTML = `
`; break; case 'runs': modalTitle.textContent = 'Neuen Lauf hinzufügen'; formFields.innerHTML = `
`; break; default: modalTitle.textContent = 'Element hinzufügen'; formFields.innerHTML = '

Keine Felder für diesen Datentyp verfügbar.

'; } modal.style.display = 'block'; } function closeModal() { document.querySelectorAll('.modal').forEach(modal => { modal.style.display = 'none'; }); // Formular zurücksetzen document.getElementById('addForm').reset(); document.getElementById('modalMessage').style.display = 'none'; } async function handleAddSubmit(e) { e.preventDefault(); const formData = new FormData(e.target); const data = Object.fromEntries(formData.entries()); // Prüfen ob es sich um eine Edit-Operation handelt const isEdit = data.edit_id; const editId = data.edit_id; delete data.edit_id; // edit_id aus den Daten entfernen try { let endpoint = ''; let successMessage = ''; let method = 'POST'; switch (currentDataType) { case 'players': endpoint = isEdit ? `/api/v1/admin/players/${editId}` : '/api/v1/admin/players'; successMessage = isEdit ? 'Spieler erfolgreich aktualisiert' : 'Spieler erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; case 'locations': endpoint = isEdit ? `/api/v1/admin/locations/${editId}` : '/api/v1/admin/locations'; successMessage = isEdit ? 'Standort erfolgreich aktualisiert' : 'Standort erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; case 'adminusers': endpoint = isEdit ? `/api/v1/admin/adminusers/${editId}` : '/api/v1/admin/adminusers'; successMessage = isEdit ? 'Admin-Benutzer erfolgreich aktualisiert' : 'Admin-Benutzer erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; case 'runs': endpoint = isEdit ? `/api/v1/admin/runs/${editId}` : '/api/v1/admin/runs'; successMessage = isEdit ? 'Lauf erfolgreich aktualisiert' : 'Lauf erfolgreich hinzugefügt'; method = isEdit ? 'PUT' : 'POST'; break; case 'achievements': // Handle achievement form submission await handleAchievementSubmit(formData); return; default: showError('Unbekannter Datentyp'); return; } const response = await fetch(endpoint, { method: method, headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data) }); const result = await response.json(); if (result.success) { showSuccess(successMessage); closeModal(); refreshData(); // Daten neu laden } else { showError(result.message || `Fehler beim ${isEdit ? 'Aktualisieren' : 'Hinzufügen'}`); } } catch (error) { console.error('Submit failed:', error); showError(`Fehler beim ${isEdit ? 'Aktualisieren' : 'Hinzufügen'}`); } } // Edit-Funktionen async function editPlayer(id) { const player = currentData.find(p => p.id == id); if (!player) return; const modal = document.getElementById('addModal'); const modalTitle = document.getElementById('modalTitle'); const formFields = document.getElementById('formFields'); modalTitle.textContent = 'Spieler bearbeiten'; formFields.innerHTML = `
`; modal.style.display = 'block'; } async function editLocation(id) { const location = currentData.find(l => l.id == id); if (!location) return; const modal = document.getElementById('addModal'); const modalTitle = document.getElementById('modalTitle'); const formFields = document.getElementById('formFields'); modalTitle.textContent = 'Standort bearbeiten'; formFields.innerHTML = `
`; modal.style.display = 'block'; } async function editRun(id) { try { // Lade die spezifischen Lauf-Daten direkt von der API const response = await fetch(`/api/admin-runs/${id}`); const result = await response.json(); if (!result.success) { showError('Fehler beim Laden der Lauf-Daten: ' + (result.message || 'Unbekannter Fehler')); return; } const run = result.data; if (!run) { showError('Lauf nicht gefunden'); return; } console.log('Editing run:', run); // Debug log const modal = document.getElementById('addModal'); const modalTitle = document.getElementById('modalTitle'); const formFields = document.getElementById('formFields'); modalTitle.textContent = 'Lauf bearbeiten'; formFields.innerHTML = `
`; modal.style.display = 'block'; } catch (error) { console.error('Error loading run data:', error); showError('Fehler beim Laden der Lauf-Daten'); } } async function deletePlayer(id) { if (await confirmDelete(`Spieler mit ID ${id} löschen?`)) { try { const response = await fetch(`/api/v1/admin/players/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showSuccess('Spieler erfolgreich gelöscht'); loadPlayers(); } else { showError('Fehler beim Löschen des Spielers'); } } catch (error) { console.error('Delete failed:', error); showError('Fehler beim Löschen des Spielers'); } } } async function deleteRun(id) { if (await confirmDelete(`Lauf mit ID ${id} löschen?`)) { try { const response = await fetch(`/api/v1/admin/runs/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showSuccess('Lauf erfolgreich gelöscht'); loadRuns(); } else { showError('Fehler beim Löschen des Laufs'); } } catch (error) { console.error('Delete failed:', error); showError('Fehler beim Löschen des Laufs'); } } } async function deleteLocation(id) { if (await confirmDelete(`Standort mit ID ${id} löschen?`)) { try { const response = await fetch(`/api/v1/admin/locations/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showSuccess('Standort erfolgreich gelöscht'); loadLocations(); } else { showError('Fehler beim Löschen des Standorts'); } } catch (error) { console.error('Delete failed:', error); showError('Fehler beim Löschen des Standorts'); } } } async function deleteAdminUser(id) { if (await confirmDelete(`Admin-Benutzer mit ID ${id} löschen?`)) { try { const response = await fetch(`/api/v1/admin/adminusers/${id}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showSuccess('Admin-Benutzer erfolgreich gelöscht'); loadAdminUsers(); } else { showError('Fehler beim Löschen des Admin-Benutzers'); } } catch (error) { console.error('Delete failed:', error); showError('Fehler beim Löschen des Admin-Benutzers'); } } } function confirmDelete(message) { return new Promise((resolve) => { document.getElementById('confirmMessage').textContent = message; document.getElementById('confirmModal').style.display = 'block'; document.getElementById('confirmYes').onclick = () => { closeModal(); resolve(true); }; document.getElementById('confirmNo').onclick = () => { closeModal(); resolve(false); }; }); } function showSuccess(message) { const messageDiv = document.getElementById('modalMessage'); messageDiv.textContent = message; messageDiv.className = 'message success'; messageDiv.style.display = 'block'; setTimeout(() => { messageDiv.style.display = 'none'; }, 3000); } function showError(message) { const messageDiv = document.getElementById('modalMessage'); messageDiv.textContent = message; messageDiv.className = 'message error'; messageDiv.style.display = 'block'; setTimeout(() => { messageDiv.style.display = 'none'; }, 3000); } // ============================================================================ // BLACKLIST MANAGEMENT FUNCTIONS // ============================================================================ async function showBlacklistManagement() { currentDataType = 'blacklist'; document.getElementById('blacklistModal').style.display = 'block'; await loadBlacklist(); await loadBlacklistStats(); } async function loadBlacklist() { try { const response = await fetch('/api/v1/admin/blacklist'); const result = await response.json(); if (result.success) { displayBlacklist(result.data); } else { showBlacklistMessage('Fehler beim Laden der Blacklist: ' + result.message, 'error'); } } catch (error) { console.error('Error loading blacklist:', error); showBlacklistMessage('Fehler beim Laden der Blacklist', 'error'); } } function displayBlacklist(blacklist) { const content = document.getElementById('blacklistContent'); let html = '

📋 Blacklist-Inhalte

'; Object.entries(blacklist).forEach(([category, terms]) => { const categoryName = getCategoryDisplayName(category); const categoryIcon = getCategoryIcon(category); html += `

${categoryIcon} ${categoryName} ${terms.length} Einträge

`; if (terms.length === 0) { html += `
Keine Einträge in dieser Kategorie
`; } else { terms.forEach(term => { html += ` ${term} `; }); } html += `
`; }); content.innerHTML = html; } // Kategorie-Icons hinzufügen function getCategoryIcon(category) { const icons = { historical: '🏛️', offensive: '⚠️', titles: '👑', brands: '🏷️', inappropriate: '🚫', racial: '🌍', religious: '⛪', disability: '♿', leetspeak: '🔤', cyberbullying: '💻', drugs: '💊', violence: '⚔️' }; return icons[category] || '📝'; } function getCategoryDisplayName(category) { const names = { historical: 'Historisch belastet', offensive: 'Beleidigend/anstößig', titles: 'Titel/Berufsbezeichnung', brands: 'Markenname', inappropriate: 'Unpassend', racial: 'Rassistisch/ethnisch', religious: 'Religiös beleidigend', disability: 'Behinderungsbezogen', leetspeak: 'Verschleiert', cyberbullying: 'Cyberbullying', drugs: 'Drogenbezogen', violence: 'Gewalt/Bedrohung' }; return names[category] || category; } async function testNameAgainstBlacklist() { const firstname = document.getElementById('testFirstname').value.trim(); const lastname = document.getElementById('testLastname').value.trim(); const resultDiv = document.getElementById('testResult'); if (!firstname || !lastname) { showBlacklistMessage('Bitte gib Vorname und Nachname ein', 'error'); return; } try { const response = await fetch('/api/v1/admin/blacklist/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ firstname, lastname }) }); const result = await response.json(); if (result.success) { const testResult = result.data; if (testResult.isBlocked) { let matchTypeText = ''; let similarityText = ''; if (testResult.matchType === 'exact') { matchTypeText = 'Exakte Übereinstimmung'; } else if (testResult.matchType === 'similar') { matchTypeText = 'Ähnliche Übereinstimmung (Levenshtein)'; const similarityPercent = Math.round((1 - testResult.similarity) * 100); similarityText = `
Ähnlichkeit: ${similarityPercent}% (Distanz: ${testResult.levenshteinDistance})`; } resultDiv.innerHTML = `
❌ Name ist blockiert
Grund: ${testResult.reason}
Kategorie: ${getCategoryDisplayName(testResult.category)}
Gefundener Begriff: "${testResult.matchedTerm}"
Typ: ${matchTypeText}${similarityText}
`; } else { resultDiv.innerHTML = `
✅ Name ist erlaubt
Der Name "${firstname} ${lastname}" ist nicht in der Blacklist.
`; } resultDiv.style.display = 'block'; } else { showBlacklistMessage('Fehler beim Testen: ' + result.message, 'error'); } } catch (error) { console.error('Error testing name:', error); showBlacklistMessage('Fehler beim Testen des Namens', 'error'); } } // Detailed Levenshtein test function async function testLevenshteinDetailed() { const firstname = document.getElementById('testFirstname').value.trim(); const lastname = document.getElementById('testLastname').value.trim(); const resultDiv = document.getElementById('testResult'); if (!firstname || !lastname) { showBlacklistMessage('Bitte gib Vorname und Nachname ein', 'error'); return; } try { const response = await fetch('/api/v1/admin/blacklist/levenshtein-test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ firstname, lastname, threshold: 0.3 }) }); const result = await response.json(); if (result.success) { const data = result.data; let html = `

🔍 Detaillierte Levenshtein-Analyse

Getesteter Name: ${data.input.firstname} ${data.input.lastname}

Schwellenwert: ${data.threshold}


`; // Show results for each category for (const [category, categoryResult] of Object.entries(data.results)) { if (categoryResult.hasSimilarTerms && categoryResult.similarTerms.length > 0) { html += `
📁 ${getCategoryDisplayName(category)} (Schwellenwert: ${categoryResult.categoryThreshold})
`; categoryResult.similarTerms.forEach(term => { const similarityPercent = Math.round((1 - term.distance) * 100); html += `
Begriff: "${term.term}"
Levenshtein-Distanz: ${term.levenshteinDistance}
Normalisierte Distanz: ${term.distance.toFixed(4)}
Ähnlichkeit: ${similarityPercent}%
Match-Typ: ${term.matchType || 'similar'}
`; }); html += `
`; } } // If no matches found if (!Object.values(data.results).some(r => r.hasSimilarTerms)) { html += `
✅ Keine ähnlichen Begriffe gefunden
Der Name "${data.input.firstname} ${data.input.lastname}" hat keine ähnlichen Begriffe in der Blacklist.
`; } html += `
`; resultDiv.innerHTML = html; resultDiv.style.display = 'block'; } else { showBlacklistMessage(result.message || 'Fehler beim Testen', 'error'); } } catch (error) { console.error('Error testing Levenshtein:', error); showBlacklistMessage('Fehler beim Testen der Levenshtein-Distanz', 'error'); } } async function addToBlacklist() { const term = document.getElementById('newTerm').value.trim(); const category = document.getElementById('newCategory').value; if (!term) { showBlacklistMessage('Bitte gib einen Begriff ein', 'error'); return; } try { const response = await fetch('/api/v1/admin/blacklist', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ term, category }) }); const result = await response.json(); if (result.success) { showBlacklistMessage('Begriff erfolgreich hinzugefügt', 'success'); document.getElementById('newTerm').value = ''; await loadBlacklist(); await loadBlacklistStats(); } else { showBlacklistMessage('Fehler beim Hinzufügen: ' + result.message, 'error'); } } catch (error) { console.error('Error adding to blacklist:', error); showBlacklistMessage('Fehler beim Hinzufügen zur Blacklist', 'error'); } } async function removeFromBlacklist(term, category) { if (!confirm(`Möchtest du "${term}" aus der Kategorie "${getCategoryDisplayName(category)}" entfernen?`)) { return; } try { const response = await fetch('/api/v1/admin/blacklist', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ term, category }) }); const result = await response.json(); if (result.success) { showBlacklistMessage('Begriff erfolgreich entfernt', 'success'); await loadBlacklist(); await loadBlacklistStats(); } else { showBlacklistMessage('Fehler beim Entfernen: ' + result.message, 'error'); } } catch (error) { console.error('Error removing from blacklist:', error); showBlacklistMessage('Fehler beim Entfernen aus der Blacklist', 'error'); } } function showBlacklistMessage(message, type) { const messageDiv = document.getElementById('blacklistMessage'); messageDiv.textContent = message; messageDiv.className = `message ${type}`; messageDiv.style.display = 'block'; setTimeout(() => { messageDiv.style.display = 'none'; }, 3000); } // Blacklist-Statistiken laden async function loadBlacklistStats() { try { const response = await fetch('/api/v1/admin/blacklist/stats'); const result = await response.json(); if (result.success) { displayBlacklistStats(result.data); } else { console.error('Error loading blacklist stats:', result.message); } } catch (error) { console.error('Error loading blacklist stats:', error); } } // Blacklist-Statistiken anzeigen function displayBlacklistStats(stats) { // Erstelle oder aktualisiere Statistiken-Bereich let statsDiv = document.getElementById('blacklistStats'); if (!statsDiv) { // Erstelle Statistiken-Bereich am Anfang des Modals const modalContent = document.querySelector('#blacklistModal .modal-content'); const firstChild = modalContent.firstChild; statsDiv = document.createElement('div'); statsDiv.id = 'blacklistStats'; statsDiv.style.cssText = 'border: 1px solid #4ade80; padding: 1rem; margin-bottom: 1rem; border-radius: 5px; background: rgba(34, 197, 94, 0.1);'; modalContent.insertBefore(statsDiv, firstChild); } let html = `

📊 Blacklist-Statistiken

`; stats.categories.forEach(category => { const categoryName = getCategoryDisplayName(category.category); const lastAdded = new Date(category.last_added).toLocaleDateString('de-DE'); html += `
${category.count}
${categoryName}
Letzte: ${lastAdded}
`; }); html += `
Gesamt: ${stats.total} Begriffe
`; statsDiv.innerHTML = html; } // ==================== ACHIEVEMENT MANAGEMENT ==================== let currentAchievementMode = 'achievements'; // 'achievements' or 'players' let currentAchievements = []; let currentPlayers = []; // Show achievement management async function showAchievementManagement() { currentDataType = 'achievements'; currentAchievementMode = 'achievements'; document.getElementById('dataTitle').textContent = '🏆 Achievement-Verwaltung'; document.getElementById('dataSection').style.display = 'block'; // Update search placeholder document.getElementById('searchInput').placeholder = 'Achievements durchsuchen...'; await loadAchievements(); } // Load all achievements async function loadAchievements() { try { const response = await fetch('/api/v1/admin/achievements'); const result = await response.json(); if (result.success) { currentAchievements = result.data; currentData = result.data; // Set for filtering displayAchievements(); } else { showError('Fehler beim Laden der Achievements: ' + result.message); } } catch (error) { console.error('Error loading achievements:', error); showError('Fehler beim Laden der Achievements'); } } // Display achievements in table function displayAchievements() { const content = document.getElementById('dataContent'); if (currentAchievements.length === 0) { content.innerHTML = '
Keine Achievements gefunden
'; return; } let html = `
`; currentAchievements.forEach(achievement => { const statusClass = achievement.is_active ? 'active' : 'inactive'; const statusText = achievement.is_active ? 'Aktiv' : 'Inaktiv'; const multipleText = achievement.can_be_earned_multiple_times ? 'Ja' : 'Nein'; html += ` `; }); html += `
Status Icon Name Kategorie Bedingung Punkte Mehrmals Aktionen
${statusText} ${achievement.icon || '🏆'} ${achievement.name} ${achievement.name_en ? `
${achievement.name_en}` : ''}
${achievement.category} ${achievement.condition_type}: ${achievement.condition_value} ${achievement.points} ${multipleText}
`; content.innerHTML = html; } // Handle achievement form submission async function handleAchievementSubmit(formData) { const isEdit = formData.has('achievement_id'); const url = isEdit ? `/api/v1/admin/achievements/${formData.get('achievement_id')}` : '/api/v1/admin/achievements'; const method = isEdit ? 'PUT' : 'POST'; const data = { name: formData.get('name'), name_en: formData.get('name_en') || null, description: formData.get('description'), description_en: formData.get('description_en') || null, category: formData.get('category'), condition_type: formData.get('condition_type'), condition_value: parseInt(formData.get('condition_value')), icon: formData.get('icon') || '🏆', points: parseInt(formData.get('points')) || 10, is_active: formData.has('is_active'), can_be_earned_multiple_times: formData.has('can_be_earned_multiple_times') }; try { const response = await fetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await response.json(); if (result.success) { showSuccess(result.message); closeModal(); await loadAchievements(); } else { showError('Fehler beim Speichern: ' + result.message); } } catch (error) { console.error('Error saving achievement:', error); showError('Fehler beim Speichern des Achievements'); } } // Toggle between achievements and players view async function toggleAchievementMode() { if (currentAchievementMode === 'achievements') { currentAchievementMode = 'players'; document.getElementById('dataTitle').textContent = '👥 Spieler-Achievements'; document.getElementById('searchInput').placeholder = 'Spieler durchsuchen...'; await loadPlayers(); } else { currentAchievementMode = 'achievements'; document.getElementById('dataTitle').textContent = '🏆 Achievement-Verwaltung'; document.getElementById('searchInput').placeholder = 'Achievements durchsuchen...'; await loadAchievements(); } } // Load all players with achievement statistics async function loadPlayers() { try { const response = await fetch('/api/v1/admin/achievements/players'); const result = await response.json(); if (result.success) { currentPlayers = result.data; currentData = result.data; // Set for filtering displayPlayers(); } else { showError('Fehler beim Laden der Spieler: ' + result.message); } } catch (error) { console.error('Error loading players:', error); showError('Fehler beim Laden der Spieler'); } } // Display players in table function displayPlayers() { const content = document.getElementById('dataContent'); if (currentPlayers.length === 0) { content.innerHTML = '
Keine Spieler gefunden
'; return; } let html = `
`; currentPlayers.forEach(player => { const progressPercentage = player.completion_percentage || 0; const progressBar = `
${progressPercentage}%
`; html += ` `; }); html += `
Spieler Abgeschlossen Gesamt Fortschritt Punkte Aktionen
${player.firstname} ${player.lastname} ${player.completed_achievements} ${player.total_achievements} ${progressBar} ${player.total_points}
`; content.innerHTML = html; } // Show add achievement modal function showAddAchievementModal() { const modal = document.getElementById('addModal'); const modalTitle = document.getElementById('modalTitle'); const formFields = document.getElementById('formFields'); modalTitle.textContent = 'Neues Achievement erstellen'; formFields.innerHTML = `
`; modal.style.display = 'block'; } // Edit achievement async function editAchievement(achievementId) { const achievement = currentAchievements.find(a => a.id === achievementId); if (!achievement) return; const modal = document.getElementById('addModal'); const modalTitle = document.getElementById('modalTitle'); const formFields = document.getElementById('formFields'); modalTitle.textContent = 'Achievement bearbeiten'; formFields.innerHTML = `
`; modal.style.display = 'block'; } // Delete achievement function deleteAchievement(achievementId, achievementName) { document.getElementById('confirmMessage').textContent = `Möchten Sie das Achievement "${achievementName}" wirklich deaktivieren?`; document.getElementById('confirmYes').onclick = () => confirmDeleteAchievement(achievementId); document.getElementById('confirmModal').style.display = 'block'; } // Confirm delete achievement async function confirmDeleteAchievement(achievementId) { try { const response = await fetch(`/api/v1/admin/achievements/${achievementId}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showSuccess(result.message); closeModal(); await loadAchievements(); } else { showError('Fehler beim Löschen: ' + result.message); } } catch (error) { console.error('Error deleting achievement:', error); showError('Fehler beim Löschen des Achievements'); } } // View player achievements async function viewPlayerAchievements(playerId, playerName) { try { const response = await fetch(`/api/v1/admin/achievements/players/${playerId}`); const result = await response.json(); if (result.success) { showPlayerAchievementsModal(result.player, result.data); } else { showError('Fehler beim Laden der Spieler-Achievements: ' + result.message); } } catch (error) { console.error('Error loading player achievements:', error); showError('Fehler beim Laden der Spieler-Achievements'); } } // Show player achievements modal function showPlayerAchievementsModal(player, achievements) { const modal = document.getElementById('addModal'); const modalTitle = document.getElementById('modalTitle'); const formFields = document.getElementById('formFields'); modalTitle.textContent = `Achievements von ${player.firstname} ${player.lastname}`; let html = `
Abgeschlossen: ${achievements.filter(a => a.is_completed).length} / ${achievements.length}
Gesamtpunkte: ${achievements.filter(a => a.is_completed).reduce((sum, a) => sum + a.points, 0)}
`; achievements.forEach(achievement => { const statusClass = achievement.is_completed ? 'completed' : 'not-completed'; const statusIcon = achievement.is_completed ? '✅' : '❌'; const completionCount = achievement.completion_count || 0; html += `
${achievement.icon} ${achievement.name} ${statusIcon}

${achievement.description}

Kategorie: ${achievement.category} Punkte: ${achievement.points} ${achievement.is_completed ? `Erreicht: ${new Date(achievement.earned_at).toLocaleDateString('de-DE')}` : ''} ${completionCount > 1 ? `Anzahl: ${completionCount}` : ''}
${!achievement.is_completed ? `` : `` }
`; }); html += `
`; formFields.innerHTML = html; modal.style.display = 'block'; } // Award achievement to player async function awardAchievement(playerId, achievementId, achievementName) { try { const response = await fetch(`/api/v1/admin/achievements/players/${playerId}/award`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ achievement_id: achievementId, progress: 1 }) }); const result = await response.json(); if (result.success) { showSuccess(result.message); // Refresh player achievements await viewPlayerAchievements(playerId, ''); } else { showError('Fehler beim Vergeben: ' + result.message); } } catch (error) { console.error('Error awarding achievement:', error); showError('Fehler beim Vergeben des Achievements'); } } // Revoke achievement from player async function revokeAchievement(playerId, achievementId, achievementName) { if (!confirm(`Möchten Sie das Achievement "${achievementName}" wirklich entfernen?`)) { return; } try { const response = await fetch(`/api/v1/admin/achievements/players/${playerId}/revoke`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ achievement_id: achievementId }) }); const result = await response.json(); if (result.success) { showSuccess(result.message); // Refresh player achievements await viewPlayerAchievements(playerId, ''); } else { showError('Fehler beim Entfernen: ' + result.message); } } catch (error) { console.error('Error revoking achievement:', error); showError('Fehler beim Entfernen des Achievements'); } }