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');
}
}

View File

@@ -1177,6 +1177,266 @@ function displayAchievements() {
achievementsGrid.innerHTML = achievementCards;
}
// Initialize Analytics and Statistics event listeners
document.addEventListener('DOMContentLoaded', function() {
const analyticsCard = document.getElementById('analyticsCard');
const statisticsCard = document.getElementById('statisticsCard');
if (analyticsCard) {
analyticsCard.addEventListener('click', showAnalytics);
console.log('Analytics card event listener added');
}
if (statisticsCard) {
statisticsCard.addEventListener('click', showStatistics);
console.log('Statistics card event listener added');
}
});
// Analytics Functions
function showAnalytics() {
console.log('showAnalytics called');
// Hide other sections
const timesDisplay = document.getElementById('timesDisplay');
const achievementsDisplay = document.getElementById('achievementsDisplay');
const statisticsSection = document.getElementById('statisticsSection');
if (timesDisplay) timesDisplay.style.display = 'none';
if (achievementsDisplay) achievementsDisplay.style.display = 'none';
if (statisticsSection) statisticsSection.style.display = 'none';
// Show analytics section
const analyticsSection = document.getElementById('analyticsSection');
if (analyticsSection) {
analyticsSection.style.display = 'block';
console.log('Analytics section shown');
} else {
console.error('Analytics section not found');
}
// Load analytics data
loadAnalyticsData();
}
function showStatistics() {
console.log('showStatistics called');
// Hide other sections
const timesDisplay = document.getElementById('timesDisplay');
const achievementsDisplay = document.getElementById('achievementsDisplay');
const analyticsSection = document.getElementById('analyticsSection');
if (timesDisplay) timesDisplay.style.display = 'none';
if (achievementsDisplay) achievementsDisplay.style.display = 'none';
if (analyticsSection) analyticsSection.style.display = 'none';
// Show statistics section
const statisticsSection = document.getElementById('statisticsSection');
if (statisticsSection) {
statisticsSection.style.display = 'block';
console.log('Statistics section shown');
} else {
console.error('Statistics section not found');
}
// Load statistics data
loadStatisticsData();
}
async function loadAnalyticsData() {
try {
if (!currentPlayerId) {
console.error('No player ID available');
return;
}
// Load analytics data from API
const response = await fetch(`/api/v1/analytics/player/${currentPlayerId}`);
if (!response.ok) {
throw new Error('Failed to load analytics data');
}
const analyticsData = await response.json();
displayAnalyticsData(analyticsData);
// Update preview in main card
updateAnalyticsPreview(analyticsData);
} catch (error) {
console.error('Error loading analytics data:', error);
// Show fallback data
displayAnalyticsFallback();
}
}
async function loadStatisticsData() {
try {
if (!currentPlayerId) {
console.error('No player ID available');
return;
}
// Load statistics data from API
const response = await fetch(`/api/v1/statistics/player/${currentPlayerId}`);
if (!response.ok) {
throw new Error('Failed to load statistics data');
}
const statisticsData = await response.json();
displayStatisticsData(statisticsData);
// Update preview in main card
updateStatisticsPreview(statisticsData);
} catch (error) {
console.error('Error loading statistics data:', error);
// Show fallback data
displayStatisticsFallback();
}
}
function displayAnalyticsData(data) {
// Performance Trends
document.getElementById('avgTimeThisWeekDetail').textContent = formatTime(data.performance.avgTimeThisWeek);
document.getElementById('avgTimeLastWeek').textContent = formatTime(data.performance.avgTimeLastWeek);
document.getElementById('improvementDetail').textContent = data.performance.improvement + '%';
// Activity Stats
document.getElementById('runsToday').textContent = data.activity.runsToday + ' Läufe';
document.getElementById('runsThisWeekDetail').textContent = data.activity.runsThisWeek + ' Läufe';
document.getElementById('avgRunsPerDay').textContent = data.activity.avgRunsPerDay.toFixed(1);
// Location Performance
displayLocationPerformance(data.locationPerformance);
// Monthly Stats
document.getElementById('runsThisMonth').textContent = data.monthly.runsThisMonth + ' Läufe';
document.getElementById('runsLastMonth').textContent = data.monthly.runsLastMonth + ' Läufe';
document.getElementById('bestTimeThisMonth').textContent = formatTime(data.monthly.bestTimeThisMonth);
}
function displayStatisticsData(data) {
// Personal Records
displayPersonalRecords(data.personalRecords);
// Consistency Metrics
document.getElementById('averageTime').textContent = formatTime(data.consistency.averageTime);
document.getElementById('timeDeviation').textContent = formatTime(data.consistency.timeDeviation);
document.getElementById('consistencyScore').textContent = data.consistency.consistencyScore + '%';
// Ranking Stats
displayRankingStats(data.rankings);
// Progress Stats
document.getElementById('totalRunsStats').textContent = data.progress.totalRuns;
document.getElementById('activeDays').textContent = data.progress.activeDays;
document.getElementById('locationsVisited').textContent = data.progress.locationsVisited;
}
function displayLocationPerformance(locations) {
const container = document.getElementById('locationPerformance');
if (!locations || locations.length === 0) {
container.innerHTML = '<p>Keine Standort-Daten verfügbar</p>';
return;
}
const locationHTML = locations.map(location => `
<div class="location-item">
<span class="location-name">${location.name}</span>
<div>
<span class="location-best">${formatTime(location.bestTime)}</span>
<span class="location-runs">(${location.runs} Läufe)</span>
</div>
</div>
`).join('');
container.innerHTML = locationHTML;
}
function displayPersonalRecords(records) {
const container = document.getElementById('personalRecords');
if (!records || records.length === 0) {
container.innerHTML = '<p>Keine Bestzeiten verfügbar</p>';
return;
}
const recordsHTML = records.map((record, index) => `
<div class="record-item">
<div>
<span class="record-rank">#${index + 1}</span>
<span class="record-time">${formatTime(record.time)}</span>
</div>
<span class="record-location">${record.location}</span>
</div>
`).join('');
container.innerHTML = recordsHTML;
}
function displayRankingStats(rankings) {
const container = document.getElementById('rankingStats');
if (!rankings || rankings.length === 0) {
container.innerHTML = '<p>Keine Ranglisten-Daten verfügbar</p>';
return;
}
const rankingsHTML = rankings.map(ranking => `
<div class="ranking-item">
<span class="ranking-category">${ranking.category}</span>
<div>
<span class="ranking-position">#${ranking.position}</span>
<span class="ranking-total">von ${ranking.total}</span>
</div>
</div>
`).join('');
container.innerHTML = rankingsHTML;
}
function updateAnalyticsPreview(data) {
document.getElementById('avgTimeThisWeek').textContent = formatTime(data.performance.avgTimeThisWeek);
document.getElementById('improvementThisWeek').textContent = data.performance.improvement + '%';
document.getElementById('runsThisWeek').textContent = data.activity.runsThisWeek;
document.getElementById('analyticsPreview').style.display = 'block';
}
function updateStatisticsPreview(data) {
document.getElementById('personalBest').textContent = formatTime(data.personalRecords[0]?.time || 0);
document.getElementById('totalRunsCount').textContent = data.progress.totalRuns;
document.getElementById('rankPosition').textContent = data.rankings[0]?.position || '-';
document.getElementById('statisticsPreview').style.display = 'block';
}
function displayAnalyticsFallback() {
// Show fallback data when API fails
document.getElementById('avgTimeThisWeekDetail').textContent = '--:--';
document.getElementById('avgTimeLastWeek').textContent = '--:--';
document.getElementById('improvementDetail').textContent = '+0.0%';
document.getElementById('runsToday').textContent = '0 Läufe';
document.getElementById('runsThisWeekDetail').textContent = '0 Läufe';
document.getElementById('avgRunsPerDay').textContent = '0.0';
document.getElementById('locationPerformance').innerHTML = '<p>Daten nicht verfügbar</p>';
document.getElementById('runsThisMonth').textContent = '0 Läufe';
document.getElementById('runsLastMonth').textContent = '0 Läufe';
document.getElementById('bestTimeThisMonth').textContent = '--:--';
}
function displayStatisticsFallback() {
// Show fallback data when API fails
document.getElementById('personalRecords').innerHTML = '<p>Daten nicht verfügbar</p>';
document.getElementById('averageTime').textContent = '--:--';
document.getElementById('timeDeviation').textContent = '--:--';
document.getElementById('consistencyScore').textContent = '0%';
document.getElementById('rankingStats').innerHTML = '<p>Daten nicht verfügbar</p>';
document.getElementById('totalRunsStats').textContent = '0';
document.getElementById('activeDays').textContent = '0';
document.getElementById('locationsVisited').textContent = '0';
}
// Get achievement condition value for progress display
function getAchievementConditionValue(achievementName) {
const conditionMap = {