Add location request
This commit is contained in:
@@ -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 @@
|
||||
<div class="control-panel">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Standort</label>
|
||||
<select class="custom-select" id="locationSelect">
|
||||
<option value="all">🌍 Alle Standorte</option>
|
||||
<!-- Standorte werden dynamisch geladen -->
|
||||
</select>
|
||||
<div class="location-control">
|
||||
<select class="custom-select location-select" id="locationSelect">
|
||||
<option value="all">🌍 Alle Standorte</option>
|
||||
<!-- Standorte werden dynamisch geladen -->
|
||||
</select>
|
||||
<button class="location-btn" id="findLocationBtn" onclick="findNearestLocation()" title="Nächstgelegenen Standort finden">
|
||||
📍 Mein Standort
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
@@ -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 = '<option value="all">🌍 Alle Standorte</option>';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user