From 119770331381221bd6575eb3811b7dfa5aa030e2 Mon Sep 17 00:00:00 2001 From: Dvorinka Date: Fri, 20 Jun 2025 09:24:02 +0200 Subject: [PATCH] test2 --- achievements.js | 109 ++++++++++++++++++++++++++++++++++---- admin-dashboard.html | 104 +++++++++++++++++++++++++++++++++++- main.go | 123 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 320 insertions(+), 16 deletions(-) diff --git a/achievements.js b/achievements.js index 7cdacf8..d28daa9 100644 --- a/achievements.js +++ b/achievements.js @@ -4,7 +4,13 @@ const ACHIEVEMENTS = { name: "Nováček", description: "První návštěva na portálu", icon: "fa-star", - color: "text-yellow-500" + color: "text-yellow-500", + theme: { + backgroundColor: "bg-yellow-50", + textColor: "text-yellow-700", + borderColor: "border-yellow-200", + hoverColor: "hover:bg-yellow-100" + } }, "frequent_visitor": { name: "Pravidelný návštěvník", @@ -12,7 +18,13 @@ const ACHIEVEMENTS = { icon: "fa-clock-rotate-left", color: "text-blue-500", threshold: 10, - period: "monthly" + period: "monthly", + theme: { + backgroundColor: "bg-blue-50", + textColor: "text-blue-700", + borderColor: "border-blue-200", + hoverColor: "hover:bg-blue-100" + } }, "power_user": { name: "Power User", @@ -20,7 +32,13 @@ const ACHIEVEMENTS = { icon: "fa-rocket", color: "text-purple-500", threshold: 50, - period: "monthly" + period: "monthly", + theme: { + backgroundColor: "bg-purple-50", + textColor: "text-purple-700", + borderColor: "border-purple-200", + hoverColor: "hover:bg-purple-100" + } }, "super_fan": { name: "Super Fan", @@ -28,10 +46,36 @@ const ACHIEVEMENTS = { icon: "fa-award", color: "text-gold", threshold: 100, - period: "monthly" + period: "monthly", + theme: { + backgroundColor: "bg-yellow-50", + textColor: "text-yellow-700", + borderColor: "border-yellow-200", + hoverColor: "hover:bg-yellow-100" + } } }; +// Track unlocked achievements +let unlockedAchievements = new Set(); + +// Store current theme +let currentTheme = { + backgroundColor: "bg-white", + textColor: "text-gray-800", + borderColor: "border-gray-200", + hoverColor: "hover:bg-gray-50" +}; + +// Apply theme to all cards +function applyTheme() { + const cards = document.querySelectorAll('.card'); + cards.forEach(card => { + card.className = card.className.split(' ').filter(cls => !cls.startsWith('bg-') && !cls.startsWith('text-') && !cls.startsWith('border-') && !cls.startsWith('hover:')).join(' '); + card.className += ` ${currentTheme.backgroundColor} ${currentTheme.textColor} ${currentTheme.borderColor} ${currentTheme.hoverColor}`; + }); +} + let achievementsEnabled = false; // Hidden toggle for achievements @@ -56,19 +100,69 @@ async function checkAchievements() { // Check for monthly achievements Object.values(ACHIEVEMENTS).forEach(achievement => { if (achievement.period === "monthly" && stats.monthly_visits >= achievement.threshold) { - showAchievementToast(achievement); + unlockAchievement(achievement); } }); // First visit achievement if (stats.total_visits === 1) { - showAchievementToast(ACHIEVEMENTS.first_visit); + unlockAchievement(ACHIEVEMENTS.first_visit); + } + + // Apply highest unlocked achievement theme + const unlocked = Array.from(unlockedAchievements); + if (unlocked.length > 0) { + const highestAchievement = unlocked[unlocked.length - 1]; + currentTheme = ACHIEVEMENTS[highestAchievement].theme; + applyTheme(); } } catch (error) { console.error('Error checking achievements:', error); } } +// Unlock achievement and show toast +function unlockAchievement(achievement) { + const achievementId = Object.keys(ACHIEVEMENTS).find(key => + ACHIEVEMENTS[key].name === achievement.name + ); + + if (!unlockedAchievements.has(achievementId)) { + unlockedAchievements.add(achievementId); + showAchievementToast(achievement); + } +} + +// Show only unlocked achievements +function showAchievements() { + const achievementsDisplay = document.getElementById('achievementsDisplay'); + if (achievementsDisplay) { + // Clear existing achievements + achievementsDisplay.innerHTML = ''; + + // Show only unlocked achievements + Array.from(unlockedAchievements).forEach(achievementId => { + const achievement = ACHIEVEMENTS[achievementId]; + const achievementItem = document.createElement('div'); + achievementItem.className = 'achievement-item flex items-center p-3 rounded-lg mb-2'; + achievementItem.style.backgroundColor = achievement.theme.backgroundColor; + achievementItem.style.color = achievement.theme.textColor; + + achievementItem.innerHTML = ` + +
+

${achievement.name}

+

${achievement.description}

+
+ `; + + achievementsDisplay.appendChild(achievementItem); + }); + + achievementsDisplay.style.display = 'block'; + } +} + // Show achievement toast function showAchievementToast(achievement) { const toast = document.createElement('div'); @@ -144,9 +238,6 @@ function initializeAchievements() { `; document.body.appendChild(achievementToast); - // Play achievement sound - const audio = new Audio('https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'); - audio.play(); // Enable achievements toggleAchievements(); diff --git a/admin-dashboard.html b/admin-dashboard.html index 17c12a4..1ccb2ea 100644 --- a/admin-dashboard.html +++ b/admin-dashboard.html @@ -1068,8 +1068,10 @@

Vítejte v administraci

-
+

Statistiky návštěvností

+ +

Celkové návštěvy

@@ -1088,6 +1090,44 @@

0

+ + +
+

Podrobné statistiky

+ + +
+
+
Prohlížeče
+
+
+ + +
+
Operační systémy
+
+
+
+ + +
+
+
Nejaktivnější hodiny
+
+
+ +
+
Nejaktivnější dny
+
+
+
+ + +
+
Unikátní návštěvníci
+
+
+
@@ -4318,10 +4358,72 @@ document.addEventListener('DOMContentLoaded', function() { fetch('/api/visitor-stats') .then(response => response.json()) .then(stats => { + // Basic stats document.getElementById('totalVisits').textContent = stats.total_visits; document.getElementById('todayVisits').textContent = stats.today_visits; document.getElementById('weeklyVisits').textContent = stats.weekly_visits; document.getElementById('monthlyVisits').textContent = stats.monthly_visits; + + // Browser stats + const browserStats = document.getElementById('browserStats'); + browserStats.innerHTML = Object.entries(stats.browser_stats) + .sort((a, b) => b[1] - a[1]) + .map(([browser, count]) => ` +
+ ${browser} + ${count} +
+ `).join(''); + + // OS stats + const osStats = document.getElementById('osStats'); + osStats.innerHTML = Object.entries(stats.os_stats) + .sort((a, b) => b[1] - a[1]) + .map(([os, count]) => ` +
+ ${os} + ${count} +
+ `).join(''); + + // Active hours + const activeHours = document.getElementById('activeHours'); + activeHours.innerHTML = stats.most_active_hours + .sort((a, b) => b.count - a.count) + .map(hour => ` +
+ ${hour.hour}:00 + ${hour.count} +
+ `).join(''); + + // Active days + const activeDays = document.getElementById('activeDays'); + activeDays.innerHTML = stats.most_active_days + .sort((a, b) => b.count - a.count) + .map(day => ` +
+ ${day.day} + ${day.count} +
+ `).join(''); + + // Unique visitors + const uniqueVisitors = document.getElementById('uniqueVisitors'); + uniqueVisitors.innerHTML = Object.entries(stats.unique_visitors) + .map(([id, visitor]) => ` +
+
+
${visitor.ip}
+
${visitor.user_agent}
+
+
+ ${visitor.visits} návštěv + | + ${visitor.last_visit.toLocaleString()} +
+
+ `).join(''); }) .catch(error => { console.error('Error loading visitor stats:', error); diff --git a/main.go b/main.go index b569208..2883953 100644 --- a/main.go +++ b/main.go @@ -20,12 +20,30 @@ import ( ) type VisitorStats struct { - TotalVisits int `json:"total_visits"` - TodayVisits int `json:"today_visits"` - LastVisit time.Time `json:"last_visit"` - MonthlyVisits int `json:"monthly_visits"` - WeeklyVisits int `json:"weekly_visits"` - LastUpdated time.Time `json:"last_updated"` + TotalVisits int `json:"total_visits"` + TodayVisits int `json:"today_visits"` + LastVisit time.Time `json:"last_visit"` + MonthlyVisits int `json:"monthly_visits"` + WeeklyVisits int `json:"weekly_visits"` + LastUpdated time.Time `json:"last_updated"` + UniqueVisitors map[string]struct { + FirstVisit time.Time `json:"first_visit"` + LastVisit time.Time `json:"last_visit"` + Visits int `json:"visits"` + IP string `json:"ip"` + UserAgent string `json:"user_agent"` + } `json:"unique_visitors"` + MostActiveHours []struct { + Hour int `json:"hour"` + Count int `json:"count"` + } `json:"most_active_hours"` + MostActiveDays []struct { + Day string `json:"day"` + Count int `json:"count"` + } `json:"most_active_days"` + BrowserStats map[string]int `json:"browser_stats"` + OSStats map[string]int `json:"os_stats"` + ReferrerStats map[string]int `json:"referrer_stats"` } const visitorStatsFile = "data/visitor_stats.json" @@ -69,6 +87,86 @@ func trackVisit(w http.ResponseWriter, r *http.Request) { return } + // Get visitor ID (using IP and User-Agent) + visitorID := fmt.Sprintf("%s-%s", r.RemoteAddr, r.UserAgent()) + + // Track unique visitor + if stats.UniqueVisitors == nil { + stats.UniqueVisitors = make(map[string]struct { + FirstVisit time.Time + LastVisit time.Time + Visits int + IP string + UserAgent string + }) + } + + visitor := stats.UniqueVisitors[visitorID] + if visitor.Visits == 0 { + visitor.FirstVisit = time.Now() + } + visitor.LastVisit = time.Now() + visitor.Visits++ + visitor.IP = r.RemoteAddr + visitor.UserAgent = r.UserAgent() + stats.UniqueVisitors[visitorID] = visitor + + // Track browser + if stats.BrowserStats == nil { + stats.BrowserStats = make(map[string]int) + } + browser := r.Header.Get("User-Agent") + stats.BrowserStats[browser]++ + + // Track OS + if stats.OSStats == nil { + stats.OSStats = make(map[string]int) + } + os := getOSFromUserAgent(r.UserAgent()) + stats.OSStats[os]++ + + // Track referrer + if stats.ReferrerStats == nil { + stats.ReferrerStats = make(map[string]int) + } + referrer := r.Header.Get("Referer") + stats.ReferrerStats[referrer]++ + + // Track active hours + hour := time.Now().Hour() + foundHour := false + for i := range stats.MostActiveHours { + if stats.MostActiveHours[i].Hour == hour { + stats.MostActiveHours[i].Count++ + foundHour = true + break + } + } + if !foundHour { + stats.MostActiveHours = append(stats.MostActiveHours, struct { + Hour int `json:"hour"` + Count int `json:"count"` + }{Hour: hour, Count: 1}) + } + + // Track active days + day := time.Now().Weekday().String() + foundDay := false + for i := range stats.MostActiveDays { + if stats.MostActiveDays[i].Day == day { + stats.MostActiveDays[i].Count++ + foundDay = true + break + } + } + if !foundDay { + stats.MostActiveDays = append(stats.MostActiveDays, struct { + Day string `json:"day"` + Count int `json:"count"` + }{Day: day, Count: 1}) + } + + // Update basic stats stats.TotalVisits++ stats.LastVisit = time.Now() @@ -102,6 +200,19 @@ func trackVisit(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } +// Helper function to extract OS from User-Agent +func getOSFromUserAgent(userAgent string) string { + if strings.Contains(userAgent, "Windows") { + return "Windows" + } else if strings.Contains(userAgent, "Mac") { + return "MacOS" + } else if strings.Contains(userAgent, "Linux") { + return "Linux" + } else { + return "Unknown" + } +} + func getVisitorStats(w http.ResponseWriter, r *http.Request) { stats, err := loadVisitorStats() if err != nil {