From 0cfac629b51345361b0bdf1432a0b12d67b5fc5e Mon Sep 17 00:00:00 2001 From: Carsten Graf Date: Thu, 4 Sep 2025 13:16:42 +0200 Subject: [PATCH] Add location request --- public/index.html | 242 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 238 insertions(+), 4 deletions(-) diff --git a/public/index.html b/public/index.html index b99dbc7..b59c9bb 100644 --- a/public/index.html +++ b/public/index.html @@ -100,6 +100,79 @@ box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.1); } + /* Location Control Layout */ + .location-control { + display: flex; + gap: 0.75rem; + align-items: center; + } + + .location-select { + flex: 1; + } + + .location-btn { + padding: 1rem 1.5rem; + background: linear-gradient(135deg, #10b981, #059669); + border: none; + border-radius: 0.75rem; + color: white; + font-weight: 600; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + min-width: 140px; + } + + .location-btn:hover { + background: linear-gradient(135deg, #059669, #047857); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3); + } + + .location-btn:disabled { + background: #374151; + color: #9ca3af; + cursor: not-allowed; + transform: none; + box-shadow: none; + } + + .location-btn.loading { + background: linear-gradient(135deg, #6366f1, #4f46e5); + position: relative; + overflow: hidden; + } + + .location-btn.loading::after { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); + animation: loading-sweep 1.5s infinite; + } + + @keyframes loading-sweep { + 0% { left: -100%; } + 100% { left: 100%; } + } + + @media (max-width: 768px) { + .location-control { + flex-direction: column; + gap: 0.5rem; + } + + .location-btn { + width: 100%; + min-width: auto; + } + } + /* Horizontal Time Tabs */ .time-tabs { display: flex; @@ -656,10 +729,15 @@
- +
+ + +
@@ -831,6 +909,9 @@ } } + // Global variable to store locations with coordinates + let locationsData = []; + // Load locations from database async function loadLocations() { try { @@ -843,6 +924,9 @@ const locations = responseData.data || responseData; // Handle both formats const locationSelect = document.getElementById('locationSelect'); + // Store locations globally for distance calculations + locationsData = locations; + // Clear existing options except "Alle Standorte" locationSelect.innerHTML = ''; @@ -859,6 +943,156 @@ } } + // Calculate distance between two points using Haversine formula + function calculateDistance(lat1, lon1, lat2, lon2) { + const R = 6371; // Earth's radius in kilometers + const dLat = toRadians(lat2 - lat1); + const dLon = toRadians(lon2 - lon1); + + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + const distance = R * c; // Distance in kilometers + + return distance; + } + + function toRadians(degrees) { + return degrees * (Math.PI / 180); + } + + // Find nearest location based on user's current position + async function findNearestLocation() { + const btn = document.getElementById('findLocationBtn'); + const locationSelect = document.getElementById('locationSelect'); + + // Check if geolocation is supported + if (!navigator.geolocation) { + showLocationError('Geolocation wird von diesem Browser nicht unterstützt.'); + return; + } + + // Update button state to loading + btn.disabled = true; + btn.classList.add('loading'); + btn.textContent = '🔍 Suche...'; + + try { + // Get user's current position + const position = await new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition( + resolve, + reject, + { + enableHighAccuracy: true, + timeout: 10000, + maximumAge: 300000 // 5 minutes + } + ); + }); + + const userLat = position.coords.latitude; + const userLon = position.coords.longitude; + + // Calculate distances to all locations + const locationsWithDistance = locationsData.map(location => ({ + ...location, + distance: calculateDistance( + userLat, + userLon, + parseFloat(location.latitude), + parseFloat(location.longitude) + ) + })); + + // Find the nearest location + const nearestLocation = locationsWithDistance.reduce((nearest, current) => { + return current.distance < nearest.distance ? current : nearest; + }); + + // Select the nearest location in the dropdown + locationSelect.value = nearestLocation.name; + + // Trigger change event to update the leaderboard + locationSelect.dispatchEvent(new Event('change')); + + // Show success notification + showLocationSuccess(nearestLocation.name, nearestLocation.distance); + + } catch (error) { + console.error('Error getting location:', error); + let errorMessage = 'Standort konnte nicht ermittelt werden.'; + + if (error.code) { + switch(error.code) { + case error.PERMISSION_DENIED: + errorMessage = 'Standortzugriff wurde verweigert. Bitte erlaube den Standortzugriff in den Browser-Einstellungen.'; + break; + case error.POSITION_UNAVAILABLE: + errorMessage = 'Standortinformationen sind nicht verfügbar.'; + break; + case error.TIMEOUT: + errorMessage = 'Zeitüberschreitung beim Abrufen des Standorts.'; + break; + } + } + + showLocationError(errorMessage); + } finally { + // Reset button state + btn.disabled = false; + btn.classList.remove('loading'); + btn.textContent = '📍 Mein Standort'; + } + } + + // Show success notification for location finding + function showLocationSuccess(locationName, distance) { + const notificationBubble = document.getElementById('notificationBubble'); + const notificationTitle = document.getElementById('notificationTitle'); + const notificationSubtitle = document.getElementById('notificationSubtitle'); + + // Update notification content + notificationTitle.textContent = `📍 Standort gefunden!`; + notificationSubtitle.textContent = `${locationName} (${distance.toFixed(1)} km entfernt)`; + + // Show notification + notificationBubble.classList.remove('hide'); + notificationBubble.classList.add('show'); + + // Auto-hide after 4 seconds + setTimeout(() => { + hideNotification(); + }, 4000); + } + + // Show error notification for location finding + function showLocationError(message) { + const notificationBubble = document.getElementById('notificationBubble'); + const notificationTitle = document.getElementById('notificationTitle'); + const notificationSubtitle = document.getElementById('notificationSubtitle'); + + // Change notification style to error + notificationBubble.style.background = 'linear-gradient(135deg, #dc3545, #c82333)'; + + // Update notification content + notificationTitle.textContent = '❌ Fehler'; + notificationSubtitle.textContent = message; + + // Show notification + notificationBubble.classList.remove('hide'); + notificationBubble.classList.add('show'); + + // Auto-hide after 6 seconds + setTimeout(() => { + hideNotification(); + // Reset notification style + notificationBubble.style.background = 'linear-gradient(135deg, #00d4ff, #0891b2)'; + }, 6000); + } + // Load data from local database via MCP async function loadData() { try {