From 69e3985af3c920952654dd283de9dff0359793f4 Mon Sep 17 00:00:00 2001 From: Carsten Graf Date: Tue, 16 Sep 2025 01:40:40 +0200 Subject: [PATCH] Statistiken und Analytics im dashboard gefixed --- public/js/dashboard.js | 198 +++++++++++++++++++++++++++++++++++------ routes/api.js | 20 ++--- 2 files changed, 182 insertions(+), 36 deletions(-) diff --git a/public/js/dashboard.js b/public/js/dashboard.js index 512393f..44597c4 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -46,6 +46,9 @@ async function initDashboard() { // Load times section checkLinkStatusAndLoadTimes(); + + // Update analytics and statistics cards + updateAnalyticsAndStatisticsCards(); } catch (error) { console.error('An unexpected error occurred:', error); @@ -148,6 +151,9 @@ function updateDynamicContent() { // Update achievement progress text updateAchievementProgressText(); + + // Update analytics and statistics cards + updateAnalyticsAndStatisticsCards(); // Reload achievements if they're loaded if (window.allAchievements && window.allAchievements.length > 0) { @@ -175,6 +181,114 @@ function loadLanguagePreference() { } } +// Update analytics and statistics cards based on link status +function updateAnalyticsAndStatisticsCards() { + const analyticsCard = document.getElementById('analyticsCard'); + const statisticsCard = document.getElementById('statisticsCard'); + + if (!currentPlayerId) { + // User not linked - show appropriate message + if (analyticsCard) { + const isGerman = currentLanguage === 'de'; + const message = isGerman ? + 'RFID verknüpfen erforderlich' : + 'RFID linking required'; + const description = isGerman ? + 'Verknüpfe deine RFID-Karte, um Analytics zu sehen.' : + 'Link your RFID card to view analytics.'; + + analyticsCard.innerHTML = ` +

📊 Analytics

+
+
+ ⚠️ + ${message} +
+
+ ${description} +
+
+ + `; + } + + if (statisticsCard) { + const isGerman = currentLanguage === 'de'; + const message = isGerman ? + 'RFID verknüpfen erforderlich' : + 'RFID linking required'; + const description = isGerman ? + 'Verknüpfe deine RFID-Karte, um Statistiken zu sehen.' : + 'Link your RFID card to view statistics.'; + + statisticsCard.innerHTML = ` +

📊 Statistiken

+
+
+ ⚠️ + ${message} +
+
+ ${description} +
+
+ + `; + } + } else { + // User is linked - restore original cards + if (analyticsCard) { + const isGerman = currentLanguage === 'de'; + analyticsCard.innerHTML = ` +

📊 Analytics

+ +

${isGerman ? 'Verfolge deine Leistung und überwache wichtige Metriken.' : 'Track your performance and monitor important metrics.'}

+ + `; + } + + if (statisticsCard) { + const isGerman = currentLanguage === 'de'; + statisticsCard.innerHTML = ` +

📊 Statistiken

+ +

${isGerman ? 'Detaillierte Statistiken zu deinen Läufen - beste Zeiten, Verbesserungen und Vergleiche.' : 'Detailed statistics about your runs - best times, improvements and comparisons.'}

+ + `; + } + } +} + // Update achievement notifications function updateAchievementNotifications() { // This will be called when achievements are displayed @@ -797,6 +911,9 @@ async function loadUserTimesSection(playerData) { // Initialize achievements for this player initializeAchievements(playerData.id); + + // Update analytics and statistics cards (user is now linked) + updateAnalyticsAndStatisticsCards(); } catch (error) { console.error('Error loading user times:', error); @@ -963,6 +1080,11 @@ function convertTimeToSeconds(timeValue) { // Format time interval to readable format function formatTime(interval) { + // Handle numeric values (seconds) + if (typeof interval === 'number') { + return formatSeconds(interval); + } + // Postgres interval format: {"hours":0,"minutes":1,"seconds":23.45} if (typeof interval === 'object') { const { hours = 0, minutes = 0, seconds = 0 } = interval; @@ -1212,9 +1334,10 @@ function showAnalytics() { console.log('Analytics section shown'); } else { console.error('Analytics section not found'); + return; } - // Load analytics data + // Load analytics data (will show fallback if not linked) loadAnalyticsData(); } @@ -1237,30 +1360,33 @@ function showStatistics() { console.log('Statistics section shown'); } else { console.error('Statistics section not found'); + return; } - // Load statistics data + // Load statistics data (will show fallback if not linked) loadStatisticsData(); } async function loadAnalyticsData() { try { if (!currentPlayerId) { - console.error('No player ID available'); + console.error('No player ID available - user not linked'); + // Show fallback data when user is not linked + displayAnalyticsFallback(); return; } // Load analytics data from API - const response = await fetch(`/api/v1/analytics/player/${currentPlayerId}`); + const response = await fetch(`/api/analytics/player/${currentPlayerId}`); if (!response.ok) { throw new Error('Failed to load analytics data'); } const analyticsData = await response.json(); - displayAnalyticsData(analyticsData); + displayAnalyticsData(analyticsData.data); // Update preview in main card - updateAnalyticsPreview(analyticsData); + updateAnalyticsPreview(analyticsData.data); } catch (error) { console.error('Error loading analytics data:', error); @@ -1272,21 +1398,23 @@ async function loadAnalyticsData() { async function loadStatisticsData() { try { if (!currentPlayerId) { - console.error('No player ID available'); + console.error('No player ID available - user not linked'); + // Show fallback data when user is not linked + displayStatisticsFallback(); return; } // Load statistics data from API - const response = await fetch(`/api/v1/statistics/player/${currentPlayerId}`); + const response = await fetch(`/api/statistics/player/${currentPlayerId}`); if (!response.ok) { throw new Error('Failed to load statistics data'); } const statisticsData = await response.json(); - displayStatisticsData(statisticsData); + displayStatisticsData(statisticsData.data); // Update preview in main card - updateStatisticsPreview(statisticsData); + updateStatisticsPreview(statisticsData.data); } catch (error) { console.error('Error loading statistics data:', error); @@ -1299,12 +1427,12 @@ 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 + '%'; + document.getElementById('improvementDetail').textContent = data.performance.improvement.toFixed(2) + '%'; // 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); + document.getElementById('avgRunsPerDay').textContent = data.activity.avgRunsPerDay.toFixed(2); // Location Performance displayLocationPerformance(data.locationPerformance); @@ -1322,7 +1450,7 @@ function displayStatisticsData(data) { // 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 + '%'; + document.getElementById('consistencyScore').textContent = data.consistency.consistencyScore.toFixed(2) + '%'; // Ranking Stats displayRankingStats(data.rankings); @@ -1397,40 +1525,58 @@ function displayRankingStats(rankings) { } 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'; + if (data && data.performance && data.activity) { + document.getElementById('avgTimeThisWeek').textContent = formatTime(data.performance.avgTimeThisWeek); + document.getElementById('improvementThisWeek').textContent = data.performance.improvement.toFixed(2) + '%'; + document.getElementById('runsThisWeek').textContent = data.activity.runsThisWeek; + document.getElementById('analyticsPreview').style.display = 'block'; + } else { + // Hide preview if no data + document.getElementById('analyticsPreview').style.display = 'none'; + } } 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'; + if (data && data.personalRecords && data.progress) { + 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'; + } else { + // Hide preview if no data + document.getElementById('statisticsPreview').style.display = 'none'; + } } function displayAnalyticsFallback() { - // Show fallback data when API fails + // Show fallback data when API fails or user not linked + const notLinkedMessage = currentLanguage === 'de' ? + 'RFID nicht verknüpft - Analytics nicht verfügbar' : + 'RFID not linked - Analytics not available'; + 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 = '

Daten nicht verfügbar

'; + document.getElementById('locationPerformance').innerHTML = `

${notLinkedMessage}

`; 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 = '

Daten nicht verfügbar

'; + // Show fallback data when API fails or user not linked + const notLinkedMessage = currentLanguage === 'de' ? + 'RFID nicht verknüpft - Statistiken nicht verfügbar' : + 'RFID not linked - Statistics not available'; + + document.getElementById('personalRecords').innerHTML = `

${notLinkedMessage}

`; document.getElementById('averageTime').textContent = '--:--'; document.getElementById('timeDeviation').textContent = '--:--'; document.getElementById('consistencyScore').textContent = '0%'; - document.getElementById('rankingStats').innerHTML = '

Daten nicht verfügbar

'; + document.getElementById('rankingStats').innerHTML = `

${notLinkedMessage}

`; document.getElementById('totalRunsStats').textContent = '0'; document.getElementById('activeDays').textContent = '0'; document.getElementById('locationsVisited').textContent = '0'; diff --git a/routes/api.js b/routes/api.js index e26f0ba..6f30afc 100644 --- a/routes/api.js +++ b/routes/api.js @@ -2990,9 +2990,9 @@ async function getPerformanceTrends(playerId) { ((avgTimeLastWeek - avgTimeThisWeek) / avgTimeLastWeek * 100) : 0; return { - avgTimeThisWeek: avgTimeThisWeek, - avgTimeLastWeek: avgTimeLastWeek, - improvement: Math.round(improvement * 10) / 10 + avgTimeThisWeek: Math.round(avgTimeThisWeek * 100) / 100, + avgTimeLastWeek: Math.round(avgTimeLastWeek * 100) / 100, + improvement: Math.round(improvement * 100) / 100 }; } @@ -3028,7 +3028,7 @@ async function getActivityStats(playerId) { return { runsToday: parseInt(todayResult.rows[0].count), runsThisWeek: parseInt(weekResult.rows[0].count), - avgRunsPerDay: parseFloat(avgResult.rows[0].avg_runs) || 0 + avgRunsPerDay: Math.round((parseFloat(avgResult.rows[0].avg_runs) || 0) * 100) / 100 }; } @@ -3048,7 +3048,7 @@ async function getLocationPerformance(playerId) { return result.rows.map(row => ({ name: row.name, - bestTime: convertTimeToSeconds(row.best_time), + bestTime: Math.round(convertTimeToSeconds(row.best_time) * 100) / 100, runs: parseInt(row.runs) })); } @@ -3086,7 +3086,7 @@ async function getMonthlyStats(playerId) { return { runsThisMonth: parseInt(thisMonthResult.rows[0].count), runsLastMonth: parseInt(lastMonthResult.rows[0].count), - bestTimeThisMonth: convertTimeToSeconds(bestTimeResult.rows[0].best_time) || 0 + bestTimeThisMonth: Math.round((convertTimeToSeconds(bestTimeResult.rows[0].best_time) || 0) * 100) / 100 }; } @@ -3103,7 +3103,7 @@ async function getPersonalRecords(playerId) { `, [playerId]); return result.rows.map(row => ({ - time: convertTimeToSeconds(row.recorded_time), + time: Math.round(convertTimeToSeconds(row.recorded_time) * 100) / 100, location: row.location })); } @@ -3125,9 +3125,9 @@ async function getConsistencyMetrics(playerId) { Math.max(0, Math.min(100, (1 - (stddevSeconds / avgSeconds)) * 100)) : 0; return { - averageTime: avgSeconds, - timeDeviation: stddevSeconds, - consistencyScore: Math.round(consistencyScore) + averageTime: Math.round(avgSeconds * 100) / 100, + timeDeviation: Math.round(stddevSeconds * 100) / 100, + consistencyScore: Math.round(consistencyScore * 100) / 100 }; }