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 += 'ID Name RFID UID Supabase User Registriert Aktionen ';
players.forEach(player => {
html += `
${player.id}
${player.full_name || '-'}
${player.rfiduid || '-'}
${player.supabase_user_id ? '✅' : '❌'}
${new Date(player.created_at).toLocaleDateString('de-DE')}
Bearbeiten
Löschen
`;
});
html += '
';
document.getElementById('dataContent').innerHTML = html;
}
function displayRunsTable(runs) {
let html = '';
html += 'ID Spieler Standort Zeit Datum Aktionen ';
runs.forEach(run => {
// Use the time_seconds value from the backend
const timeInSeconds = parseFloat(run.time_seconds) || 0;
html += `
${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')}
Bearbeiten
Löschen
`;
});
html += '
';
document.getElementById('dataContent').innerHTML = html;
}
function displayLocationsTable(locations) {
let html = '';
html += 'ID Name Latitude Longitude Mindestzeit (s) Erstellt Aktionen ';
locations.forEach(location => {
html += `
${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')}
Bearbeiten
Löschen
`;
});
html += '
';
document.getElementById('dataContent').innerHTML = html;
}
function displayAdminUsersTable(users) {
let html = '';
html += 'ID Benutzername Access Level Aktiv Letzter Login Aktionen ';
users.forEach(user => {
const isCurrentUser = user.id === currentUser.id;
html += `
${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 ? `Löschen ` : ''}
`;
});
html += '
';
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 = `
Vollständiger Name:
RFID UID:
Supabase User ID (optional):
`;
break;
case 'locations':
modalTitle.textContent = 'Neuen Standort hinzufügen';
formFields.innerHTML = `
Standort-Name:
Breitengrad (Latitude):
Längengrad (Longitude):
Zeit-Schwellenwert (optional):
`;
break;
case 'adminusers':
modalTitle.textContent = 'Neuen Admin-Benutzer hinzufügen';
formFields.innerHTML = `
Benutzername:
Passwort:
Zugriffslevel:
Level 1 - Basis-Admin
Level 2 - Vollzugriff
`;
break;
case 'runs':
modalTitle.textContent = 'Neuen Lauf hinzufügen';
formFields.innerHTML = `
Spieler ID:
Standort ID:
Zeit (Sekunden):
`;
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 = `
Vollständiger Name:
RFID UID:
Supabase User ID (optional):
`;
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 = `
Standort-Name:
Breitengrad (Latitude):
Längengrad (Longitude):
Zeit-Schwellenwert (optional):
`;
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 = `
Spieler ID:
Standort ID:
Zeit (Sekunden):
`;
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 = `
➕ Neues Achievement
👥 Spieler-Ansicht
Status
Icon
Name
Kategorie
Bedingung
Punkte
Mehrmals
Aktionen
`;
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 += `
${statusText}
${achievement.icon || '🏆'}
${achievement.name}
${achievement.name_en ? `${achievement.name_en} ` : ''}
${achievement.category}
${achievement.condition_type}: ${achievement.condition_value}
${achievement.points}
${multipleText}
✏️
🗑️
`;
});
html += `
`;
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 = `
🏆 Achievement-Ansicht
Spieler
Abgeschlossen
Gesamt
Fortschritt
Punkte
Aktionen
`;
currentPlayers.forEach(player => {
const progressPercentage = player.completion_percentage || 0;
const progressBar = `
`;
html += `
${player.firstname} ${player.lastname}
${player.completed_achievements}
${player.total_achievements}
${progressBar}
${player.total_points}
👁️
`;
});
html += `
`;
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 = `
Name *
Name (Englisch)
Beschreibung *
Beschreibung (Englisch)
Kategorie *
Kategorie wählen
Konsistenz
Verbesserung
Saisonal
Monatlich
Beste Zeit
Bedingungstyp *
Bedingungswert *
Icon
Punkte
Aktiv
Kann mehrmals erreicht werden
`;
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 = `
Name *
Name (Englisch)
Beschreibung *
Beschreibung (Englisch)
Kategorie *
Konsistenz
Verbesserung
Saisonal
Monatlich
Beste Zeit
Bedingungstyp *
Bedingungswert *
Icon
Punkte
Aktiv
Kann mehrmals erreicht werden
`;
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.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 ?
`Vergeben ` :
`Entfernen `
}
`;
});
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');
}
}