Div Erweiterungen

This commit is contained in:
2025-09-15 23:52:59 +02:00
parent ad6ba66220
commit 5ca7b0b19c
9 changed files with 2567 additions and 24 deletions

View File

@@ -317,7 +317,7 @@ function displayRunsTable(runs) {
function displayLocationsTable(locations) {
let html = '<div class="table-container"><table class="data-table">';
html += '<thead><tr><th>ID</th><th>Name</th><th>Latitude</th><th>Longitude</th><th>Erstellt</th><th>Aktionen</th></tr></thead><tbody>';
html += '<thead><tr><th>ID</th><th>Name</th><th>Latitude</th><th>Longitude</th><th>Mindestzeit (s)</th><th>Erstellt</th><th>Aktionen</th></tr></thead><tbody>';
locations.forEach(location => {
html += `<tr>
@@ -325,6 +325,7 @@ function displayLocationsTable(locations) {
<td>${location.name}</td>
<td>${location.latitude}</td>
<td>${location.longitude}</td>
<td>${location.time_threshold ? (typeof location.time_threshold === 'object' ? location.time_threshold.seconds : location.time_threshold).toFixed(3) : '-'}</td>
<td>${new Date(location.created_at).toLocaleDateString('de-DE')}</td>
<td class="action-buttons">
<button class="btn btn-small btn-warning" onclick="editLocation('${location.id}')">Bearbeiten</button>
@@ -393,6 +394,15 @@ function filterData() {
case 'adminusers':
displayAdminUsersTable(filteredData);
break;
case 'achievements':
if (currentAchievementMode === 'achievements') {
currentAchievements = filteredData;
displayAchievements();
} else {
currentPlayers = filteredData;
displayPlayers();
}
break;
}
}
@@ -413,6 +423,13 @@ function refreshData() {
case 'system':
loadSystemInfo();
break;
case 'achievements':
if (currentAchievementMode === 'achievements') {
loadAchievements();
} else {
loadPlayers();
}
break;
}
}
@@ -556,6 +573,10 @@ async function handleAddSubmit(e) {
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;
@@ -1196,7 +1217,552 @@ function displayBlacklistStats(stats) {
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 = '<div class="no-data">Keine Achievements gefunden</div>';
return;
}
let html = `
<div class="achievement-controls">
<button class="btn btn-success" onclick="showAddAchievementModal()"> Neues Achievement</button>
<button class="btn" onclick="toggleAchievementMode()">👥 Spieler-Ansicht</button>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Status</th>
<th>Icon</th>
<th>Name</th>
<th>Kategorie</th>
<th>Bedingung</th>
<th>Punkte</th>
<th>Mehrmals</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
`;
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 += `
<tr>
<td><span class="status-badge ${statusClass}">${statusText}</span></td>
<td>${achievement.icon || '🏆'}</td>
<td>
<strong>${achievement.name}</strong>
${achievement.name_en ? `<br><small>${achievement.name_en}</small>` : ''}
</td>
<td>${achievement.category}</td>
<td>${achievement.condition_type}: ${achievement.condition_value}</td>
<td>${achievement.points}</td>
<td>${multipleText}</td>
<td>
<button class="btn btn-sm" onclick="editAchievement('${achievement.id}')">✏️</button>
<button class="btn btn-sm btn-danger" onclick="deleteAchievement('${achievement.id}', '${achievement.name}')">🗑️</button>
</td>
</tr>
`;
});
html += `
</tbody>
</table>
</div>
`;
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 = '<div class="no-data">Keine Spieler gefunden</div>';
return;
}
let html = `
<div class="achievement-controls">
<button class="btn" onclick="toggleAchievementMode()">🏆 Achievement-Ansicht</button>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Spieler</th>
<th>Abgeschlossen</th>
<th>Gesamt</th>
<th>Fortschritt</th>
<th>Punkte</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
`;
currentPlayers.forEach(player => {
const progressPercentage = player.completion_percentage || 0;
const progressBar = `
<div class="progress-bar">
<div class="progress-fill" style="width: ${progressPercentage}%"></div>
<span class="progress-text">${progressPercentage}%</span>
</div>
`;
html += `
<tr>
<td><strong>${player.firstname} ${player.lastname}</strong></td>
<td>${player.completed_achievements}</td>
<td>${player.total_achievements}</td>
<td>${progressBar}</td>
<td>${player.total_points}</td>
<td>
<button class="btn btn-sm" onclick="viewPlayerAchievements('${player.id}', '${player.firstname} ${player.lastname}')">👁️</button>
</td>
</tr>
`;
});
html += `
</tbody>
</table>
</div>
`;
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 = `
<div class="form-group">
<label for="achievement_name">Name *</label>
<input type="text" id="achievement_name" name="name" required>
</div>
<div class="form-group">
<label for="achievement_name_en">Name (Englisch)</label>
<input type="text" id="achievement_name_en" name="name_en">
</div>
<div class="form-group">
<label for="achievement_description">Beschreibung *</label>
<textarea id="achievement_description" name="description" required></textarea>
</div>
<div class="form-group">
<label for="achievement_description_en">Beschreibung (Englisch)</label>
<textarea id="achievement_description_en" name="description_en"></textarea>
</div>
<div class="form-group">
<label for="achievement_category">Kategorie *</label>
<select id="achievement_category" name="category" required>
<option value="">Kategorie wählen</option>
<option value="consistency">Konsistenz</option>
<option value="improvement">Verbesserung</option>
<option value="seasonal">Saisonal</option>
<option value="monthly">Monatlich</option>
<option value="best_time">Beste Zeit</option>
</select>
</div>
<div class="form-group">
<label for="achievement_condition_type">Bedingungstyp *</label>
<input type="text" id="achievement_condition_type" name="condition_type" required placeholder="z.B. first_time, attempts_per_day">
</div>
<div class="form-group">
<label for="achievement_condition_value">Bedingungswert *</label>
<input type="number" id="achievement_condition_value" name="condition_value" required>
</div>
<div class="form-group">
<label for="achievement_icon">Icon</label>
<input type="text" id="achievement_icon" name="icon" value="🏆">
</div>
<div class="form-group">
<label for="achievement_points">Punkte</label>
<input type="number" id="achievement_points" name="points" value="10">
</div>
<div class="form-group">
<label>
<input type="checkbox" id="achievement_is_active" name="is_active" checked>
Aktiv
</label>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="achievement_multiple" name="can_be_earned_multiple_times">
Kann mehrmals erreicht werden
</label>
</div>
`;
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 = `
<input type="hidden" id="achievement_id" value="${achievement.id}">
<div class="form-group">
<label for="achievement_name">Name *</label>
<input type="text" id="achievement_name" name="name" value="${achievement.name}" required>
</div>
<div class="form-group">
<label for="achievement_name_en">Name (Englisch)</label>
<input type="text" id="achievement_name_en" name="name_en" value="${achievement.name_en || ''}">
</div>
<div class="form-group">
<label for="achievement_description">Beschreibung *</label>
<textarea id="achievement_description" name="description" required>${achievement.description}</textarea>
</div>
<div class="form-group">
<label for="achievement_description_en">Beschreibung (Englisch)</label>
<textarea id="achievement_description_en" name="description_en">${achievement.description_en || ''}</textarea>
</div>
<div class="form-group">
<label for="achievement_category">Kategorie *</label>
<select id="achievement_category" name="category" required>
<option value="consistency" ${achievement.category === 'consistency' ? 'selected' : ''}>Konsistenz</option>
<option value="improvement" ${achievement.category === 'improvement' ? 'selected' : ''}>Verbesserung</option>
<option value="seasonal" ${achievement.category === 'seasonal' ? 'selected' : ''}>Saisonal</option>
<option value="monthly" ${achievement.category === 'monthly' ? 'selected' : ''}>Monatlich</option>
<option value="best_time" ${achievement.category === 'best_time' ? 'selected' : ''}>Beste Zeit</option>
</select>
</div>
<div class="form-group">
<label for="achievement_condition_type">Bedingungstyp *</label>
<input type="text" id="achievement_condition_type" name="condition_type" value="${achievement.condition_type}" required>
</div>
<div class="form-group">
<label for="achievement_condition_value">Bedingungswert *</label>
<input type="number" id="achievement_condition_value" name="condition_value" value="${achievement.condition_value}" required>
</div>
<div class="form-group">
<label for="achievement_icon">Icon</label>
<input type="text" id="achievement_icon" name="icon" value="${achievement.icon || '🏆'}">
</div>
<div class="form-group">
<label for="achievement_points">Punkte</label>
<input type="number" id="achievement_points" name="points" value="${achievement.points}">
</div>
<div class="form-group">
<label>
<input type="checkbox" id="achievement_is_active" name="is_active" ${achievement.is_active ? 'checked' : ''}>
Aktiv
</label>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="achievement_multiple" name="can_be_earned_multiple_times" ${achievement.can_be_earned_multiple_times ? 'checked' : ''}>
Kann mehrmals erreicht werden
</label>
</div>
`;
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 = `
<div class="player-achievements">
<div class="achievement-stats">
<div class="stat-item">
<strong>Abgeschlossen:</strong> ${achievements.filter(a => a.is_completed).length} / ${achievements.length}
</div>
<div class="stat-item">
<strong>Gesamtpunkte:</strong> ${achievements.filter(a => a.is_completed).reduce((sum, a) => sum + a.points, 0)}
</div>
</div>
<div class="achievements-list">
`;
achievements.forEach(achievement => {
const statusClass = achievement.is_completed ? 'completed' : 'not-completed';
const statusIcon = achievement.is_completed ? '✅' : '❌';
const completionCount = achievement.completion_count || 0;
html += `
<div class="achievement-item ${statusClass}">
<div class="achievement-header">
<span class="achievement-icon">${achievement.icon}</span>
<span class="achievement-name">${achievement.name}</span>
<span class="achievement-status">${statusIcon}</span>
</div>
<div class="achievement-details">
<p>${achievement.description}</p>
<div class="achievement-meta">
<span>Kategorie: ${achievement.category}</span>
<span>Punkte: ${achievement.points}</span>
${achievement.is_completed ? `<span>Erreicht: ${new Date(achievement.earned_at).toLocaleDateString('de-DE')}</span>` : ''}
${completionCount > 1 ? `<span>Anzahl: ${completionCount}</span>` : ''}
</div>
<div class="achievement-actions">
${!achievement.is_completed ?
`<button class="btn btn-sm btn-success" onclick="awardAchievement('${player.id}', '${achievement.id}', '${achievement.name}')">Vergeben</button>` :
`<button class="btn btn-sm btn-danger" onclick="revokeAchievement('${player.id}', '${achievement.id}', '${achievement.name}')">Entfernen</button>`
}
</div>
</div>
</div>
`;
});
html += `
</div>
</div>
`;
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');
}
}