Add location request
This commit is contained in:
@@ -100,6 +100,79 @@
|
|||||||
box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.1);
|
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 */
|
/* Horizontal Time Tabs */
|
||||||
.time-tabs {
|
.time-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -656,10 +729,15 @@
|
|||||||
<div class="control-panel">
|
<div class="control-panel">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">Standort</label>
|
<label class="control-label">Standort</label>
|
||||||
<select class="custom-select" id="locationSelect">
|
<div class="location-control">
|
||||||
|
<select class="custom-select location-select" id="locationSelect">
|
||||||
<option value="all">🌍 Alle Standorte</option>
|
<option value="all">🌍 Alle Standorte</option>
|
||||||
<!-- Standorte werden dynamisch geladen -->
|
<!-- Standorte werden dynamisch geladen -->
|
||||||
</select>
|
</select>
|
||||||
|
<button class="location-btn" id="findLocationBtn" onclick="findNearestLocation()" title="Nächstgelegenen Standort finden">
|
||||||
|
📍 Mein Standort
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
@@ -831,6 +909,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global variable to store locations with coordinates
|
||||||
|
let locationsData = [];
|
||||||
|
|
||||||
// Load locations from database
|
// Load locations from database
|
||||||
async function loadLocations() {
|
async function loadLocations() {
|
||||||
try {
|
try {
|
||||||
@@ -843,6 +924,9 @@
|
|||||||
const locations = responseData.data || responseData; // Handle both formats
|
const locations = responseData.data || responseData; // Handle both formats
|
||||||
const locationSelect = document.getElementById('locationSelect');
|
const locationSelect = document.getElementById('locationSelect');
|
||||||
|
|
||||||
|
// Store locations globally for distance calculations
|
||||||
|
locationsData = locations;
|
||||||
|
|
||||||
// Clear existing options except "Alle Standorte"
|
// Clear existing options except "Alle Standorte"
|
||||||
locationSelect.innerHTML = '<option value="all">🌍 Alle Standorte</option>';
|
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
|
// Load data from local database via MCP
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user