873 lines
28 KiB
JavaScript
873 lines
28 KiB
JavaScript
// Global configuration
|
||
const CONFIG = {
|
||
DEFAULT_LAT: parseFloat(document.querySelector('meta[name="default-lat"]')?.content) || 53.5461,
|
||
DEFAULT_LNG: parseFloat(document.querySelector('meta[name="default-lng"]')?.content) || -113.4938,
|
||
DEFAULT_ZOOM: parseInt(document.querySelector('meta[name="default-zoom"]')?.content) || 11,
|
||
REFRESH_INTERVAL: 30000, // 30 seconds
|
||
MAX_ZOOM: 19,
|
||
MIN_ZOOM: 2
|
||
};
|
||
|
||
// Application state
|
||
let map = null;
|
||
let markers = [];
|
||
let userLocationMarker = null;
|
||
let isAddingLocation = false;
|
||
let refreshInterval = null;
|
||
let currentEditingLocation = null;
|
||
let currentUser = null;
|
||
let startLocationMarker = null;
|
||
let isStartLocationVisible = true;
|
||
|
||
// Initialize the application
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
console.log('DOM loaded, initializing application...');
|
||
checkAuth();
|
||
initializeMap();
|
||
loadLocations();
|
||
setupEventListeners();
|
||
setupAutoRefresh();
|
||
hideLoading();
|
||
});
|
||
|
||
// Check authentication
|
||
async function checkAuth() {
|
||
try {
|
||
const response = await fetch('/api/auth/check');
|
||
const data = await response.json();
|
||
|
||
if (!data.authenticated) {
|
||
window.location.href = '/login.html';
|
||
return;
|
||
}
|
||
|
||
currentUser = data.user;
|
||
updateUserInterface();
|
||
|
||
} catch (error) {
|
||
console.error('Auth check failed:', error);
|
||
window.location.href = '/login.html';
|
||
}
|
||
}
|
||
|
||
// Update UI based on user
|
||
function updateUserInterface() {
|
||
if (!currentUser) return;
|
||
|
||
// Add user info and admin link to header if admin
|
||
const headerActions = document.querySelector('.header-actions');
|
||
if (currentUser.isAdmin && headerActions) {
|
||
const adminLink = document.createElement('a');
|
||
adminLink.href = '/admin.html';
|
||
adminLink.className = 'btn btn-secondary';
|
||
adminLink.textContent = '⚙️ Admin';
|
||
headerActions.insertBefore(adminLink, headerActions.firstChild);
|
||
}
|
||
}
|
||
|
||
// Initialize the map
|
||
async function initializeMap() {
|
||
try {
|
||
// Get start location from server
|
||
const response = await fetch('/api/admin/start-location');
|
||
const data = await response.json();
|
||
|
||
let startLat = CONFIG.DEFAULT_LAT;
|
||
let startLng = CONFIG.DEFAULT_LNG;
|
||
let startZoom = CONFIG.DEFAULT_ZOOM;
|
||
|
||
if (data.success && data.location) {
|
||
startLat = data.location.latitude;
|
||
startLng = data.location.longitude;
|
||
startZoom = data.location.zoom;
|
||
}
|
||
|
||
// Initialize map
|
||
map = L.map('map').setView([startLat, startLng], startZoom);
|
||
|
||
// Add tile layer
|
||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||
maxZoom: CONFIG.MAX_ZOOM,
|
||
minZoom: CONFIG.MIN_ZOOM
|
||
}).addTo(map);
|
||
|
||
// Add start location marker
|
||
addStartLocationMarker(startLat, startLng);
|
||
|
||
console.log('Map initialized successfully');
|
||
|
||
} catch (error) {
|
||
console.error('Failed to initialize map:', error);
|
||
showStatus('Failed to initialize map', 'error');
|
||
}
|
||
}
|
||
|
||
// Add start location marker function
|
||
function addStartLocationMarker(lat, lng) {
|
||
console.log(`Adding start location marker at: ${lat}, ${lng}`);
|
||
|
||
// Remove existing start location marker if it exists
|
||
if (startLocationMarker) {
|
||
map.removeLayer(startLocationMarker);
|
||
}
|
||
|
||
// Create a very distinctive custom icon
|
||
const startIcon = L.divIcon({
|
||
html: `
|
||
<div class="start-location-marker-wrapper">
|
||
<div class="start-location-marker-pin">
|
||
<div class="start-location-marker-inner">
|
||
<svg width="24" height="24" viewBox="0 0 24 24" fill="white">
|
||
<path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="start-location-marker-pulse"></div>
|
||
</div>
|
||
`,
|
||
className: 'start-location-custom-marker',
|
||
iconSize: [48, 48],
|
||
iconAnchor: [24, 48],
|
||
popupAnchor: [0, -48]
|
||
});
|
||
|
||
// Create the marker
|
||
startLocationMarker = L.marker([lat, lng], {
|
||
icon: startIcon,
|
||
zIndexOffset: 1000
|
||
}).addTo(map);
|
||
|
||
// Add popup
|
||
startLocationMarker.bindPopup(`
|
||
<div class="popup-content start-location-popup-enhanced">
|
||
<h3>📍 Map Start Location</h3>
|
||
<p>This is todays starting location!</p>
|
||
${currentUser?.isAdmin ? '<p><a href="/admin.html">Edit in Admin Panel</a></p>' : ''}
|
||
</div>
|
||
`);
|
||
}
|
||
|
||
// Toggle start location visibility
|
||
function toggleStartLocationVisibility() {
|
||
if (!startLocationMarker) return;
|
||
|
||
isStartLocationVisible = !isStartLocationVisible;
|
||
|
||
if (isStartLocationVisible) {
|
||
map.addLayer(startLocationMarker);
|
||
document.querySelector('#toggle-start-location-btn .btn-text').textContent = 'Hide Start Location';
|
||
} else {
|
||
map.removeLayer(startLocationMarker);
|
||
document.querySelector('#toggle-start-location-btn .btn-text').textContent = 'Show Start Location';
|
||
}
|
||
}
|
||
|
||
// Load locations from the API
|
||
async function loadLocations() {
|
||
try {
|
||
const response = await fetch('/api/locations');
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
displayLocations(data.locations);
|
||
updateLocationCount(data.locations.length);
|
||
} else {
|
||
throw new Error(data.error || 'Failed to load locations');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading locations:', error);
|
||
showStatus('Failed to load locations', 'error');
|
||
}
|
||
}
|
||
|
||
// Display locations on the map
|
||
function displayLocations(locations) {
|
||
// Clear existing markers
|
||
markers.forEach(marker => map.removeLayer(marker));
|
||
markers = [];
|
||
|
||
// Add new markers
|
||
locations.forEach(location => {
|
||
if (location.latitude && location.longitude) {
|
||
const marker = createLocationMarker(location);
|
||
markers.push(marker);
|
||
}
|
||
});
|
||
|
||
console.log(`Displayed ${markers.length} locations`);
|
||
}
|
||
|
||
// Create a location marker
|
||
function createLocationMarker(location) {
|
||
const lat = parseFloat(location.latitude);
|
||
const lng = parseFloat(location.longitude);
|
||
|
||
// Determine marker color based on support level
|
||
let markerColor = 'blue';
|
||
if (location['Support Level']) {
|
||
const level = parseInt(location['Support Level']);
|
||
switch(level) {
|
||
case 1: markerColor = 'green'; break;
|
||
case 2: markerColor = 'yellow'; break;
|
||
case 3: markerColor = 'orange'; break;
|
||
case 4: markerColor = 'red'; break;
|
||
}
|
||
}
|
||
|
||
const marker = L.circleMarker([lat, lng], {
|
||
radius: 8,
|
||
fillColor: markerColor,
|
||
color: '#fff',
|
||
weight: 2,
|
||
opacity: 1,
|
||
fillOpacity: 0.8
|
||
}).addTo(map);
|
||
|
||
// Create popup content
|
||
const popupContent = createPopupContent(location);
|
||
marker.bindPopup(popupContent);
|
||
|
||
// Add click handler for editing - Store location data on marker
|
||
marker._locationData = location;
|
||
marker.on('click', () => {
|
||
if (currentUser) {
|
||
// Debug: Log the location object to see its structure
|
||
console.log('Location clicked:', location);
|
||
console.log('Available fields:', Object.keys(location));
|
||
setTimeout(() => openEditForm(location), 100);
|
||
}
|
||
});
|
||
|
||
return marker;
|
||
}
|
||
|
||
// Create popup content
|
||
function createPopupContent(location) {
|
||
// Try to find the ID field
|
||
const locationId = location.Id || location.id || location.ID || location._id;
|
||
|
||
const name = [location['First Name'], location['Last Name']]
|
||
.filter(Boolean).join(' ') || 'Unknown';
|
||
const address = location.Address || 'No address';
|
||
const supportLevel = location['Support Level'] ?
|
||
`Level ${location['Support Level']}` : 'Not specified';
|
||
|
||
return `
|
||
<div class="popup-content">
|
||
<h3>${escapeHtml(name)}</h3>
|
||
<p><strong>Address:</strong> ${escapeHtml(address)}</p>
|
||
<p><strong>Support:</strong> ${escapeHtml(supportLevel)}</p>
|
||
${location.Sign ? '<p>🏁 Has campaign sign</p>' : ''}
|
||
${location.Notes ? `<p><strong>Notes:</strong> ${escapeHtml(location.Notes)}</p>` : ''}
|
||
<div class="popup-meta">
|
||
<p>ID: ${locationId || 'Unknown'}</p>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Setup event listeners
|
||
function setupEventListeners() {
|
||
// Refresh button
|
||
document.getElementById('refresh-btn')?.addEventListener('click', () => {
|
||
loadLocations();
|
||
showStatus('Locations refreshed', 'success');
|
||
});
|
||
|
||
// Geolocate button
|
||
document.getElementById('geolocate-btn')?.addEventListener('click', getUserLocation);
|
||
|
||
// Toggle start location button
|
||
document.getElementById('toggle-start-location-btn')?.addEventListener('click', toggleStartLocationVisibility);
|
||
|
||
// Add location button
|
||
document.getElementById('add-location-btn')?.addEventListener('click', toggleAddLocationMode);
|
||
|
||
// Fullscreen button
|
||
document.getElementById('fullscreen-btn')?.addEventListener('click', toggleFullscreen);
|
||
|
||
// Modal controls
|
||
document.getElementById('close-modal-btn')?.addEventListener('click', closeAddModal);
|
||
document.getElementById('cancel-modal-btn')?.addEventListener('click', closeAddModal);
|
||
|
||
// Edit footer controls
|
||
document.getElementById('close-edit-footer-btn')?.addEventListener('click', closeEditForm);
|
||
|
||
// Forms
|
||
document.getElementById('location-form')?.addEventListener('submit', handleAddLocation);
|
||
document.getElementById('edit-location-form')?.addEventListener('submit', handleEditLocation);
|
||
|
||
// Delete button
|
||
document.getElementById('delete-location-btn')?.addEventListener('click', handleDeleteLocation);
|
||
|
||
// Address lookup buttons
|
||
document.getElementById('lookup-address-add-btn')?.addEventListener('click', () => {
|
||
lookupAddress('add');
|
||
});
|
||
|
||
document.getElementById('lookup-address-edit-btn')?.addEventListener('click', () => {
|
||
lookupAddress('edit');
|
||
});
|
||
|
||
// Geo-location field sync
|
||
setupGeoLocationSync();
|
||
}
|
||
|
||
// Setup geo-location field synchronization
|
||
function setupGeoLocationSync() {
|
||
// For add form
|
||
const addLatInput = document.getElementById('location-lat');
|
||
const addLngInput = document.getElementById('location-lng');
|
||
const addGeoInput = document.getElementById('geo-location');
|
||
|
||
if (addLatInput && addLngInput && addGeoInput) {
|
||
[addLatInput, addLngInput].forEach(input => {
|
||
input.addEventListener('input', () => {
|
||
const lat = addLatInput.value;
|
||
const lng = addLngInput.value;
|
||
if (lat && lng) {
|
||
addGeoInput.value = `${lat};${lng}`;
|
||
}
|
||
});
|
||
});
|
||
|
||
addGeoInput.addEventListener('input', () => {
|
||
const coords = parseGeoLocation(addGeoInput.value);
|
||
if (coords) {
|
||
addLatInput.value = coords.lat;
|
||
addLngInput.value = coords.lng;
|
||
}
|
||
});
|
||
}
|
||
|
||
// For edit form
|
||
const editLatInput = document.getElementById('edit-location-lat');
|
||
const editLngInput = document.getElementById('edit-location-lng');
|
||
const editGeoInput = document.getElementById('edit-geo-location');
|
||
|
||
if (editLatInput && editLngInput && editGeoInput) {
|
||
[editLatInput, editLngInput].forEach(input => {
|
||
input.addEventListener('input', () => {
|
||
const lat = editLatInput.value;
|
||
const lng = editLngInput.value;
|
||
if (lat && lng) {
|
||
editGeoInput.value = `${lat};${lng}`;
|
||
}
|
||
});
|
||
});
|
||
|
||
editGeoInput.addEventListener('input', () => {
|
||
const coords = parseGeoLocation(editGeoInput.value);
|
||
if (coords) {
|
||
editLatInput.value = coords.lat;
|
||
editLngInput.value = coords.lng;
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// Parse geo-location string
|
||
function parseGeoLocation(value) {
|
||
if (!value) return null;
|
||
|
||
// Try semicolon separator first
|
||
let parts = value.split(';');
|
||
if (parts.length !== 2) {
|
||
// Try comma separator
|
||
parts = value.split(',');
|
||
}
|
||
|
||
if (parts.length === 2) {
|
||
const lat = parseFloat(parts[0].trim());
|
||
const lng = parseFloat(parts[1].trim());
|
||
|
||
if (!isNaN(lat) && !isNaN(lng)) {
|
||
return { lat, lng };
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
// Get user location
|
||
function getUserLocation() {
|
||
if (!navigator.geolocation) {
|
||
showStatus('Geolocation is not supported by your browser', 'error');
|
||
return;
|
||
}
|
||
|
||
showStatus('Getting your location...', 'info');
|
||
|
||
navigator.geolocation.getCurrentPosition(
|
||
(position) => {
|
||
const lat = position.coords.latitude;
|
||
const lng = position.coords.longitude;
|
||
|
||
// Center map on user location
|
||
map.setView([lat, lng], 15);
|
||
|
||
// Add or update user location marker
|
||
if (userLocationMarker) {
|
||
userLocationMarker.setLatLng([lat, lng]);
|
||
} else {
|
||
userLocationMarker = L.circleMarker([lat, lng], {
|
||
radius: 10,
|
||
fillColor: '#2196F3',
|
||
color: '#fff',
|
||
weight: 3,
|
||
opacity: 1,
|
||
fillOpacity: 0.8
|
||
}).addTo(map);
|
||
|
||
userLocationMarker.bindPopup('<strong>Your Location</strong>');
|
||
}
|
||
|
||
showStatus('Location found!', 'success');
|
||
},
|
||
(error) => {
|
||
let message = 'Unable to get your location';
|
||
switch(error.code) {
|
||
case error.PERMISSION_DENIED:
|
||
message = 'Location permission denied';
|
||
break;
|
||
case error.POSITION_UNAVAILABLE:
|
||
message = 'Location information unavailable';
|
||
break;
|
||
case error.TIMEOUT:
|
||
message = 'Location request timed out';
|
||
break;
|
||
}
|
||
showStatus(message, 'error');
|
||
},
|
||
{
|
||
enableHighAccuracy: true,
|
||
timeout: 10000,
|
||
maximumAge: 0
|
||
}
|
||
);
|
||
}
|
||
|
||
// Toggle add location mode
|
||
function toggleAddLocationMode() {
|
||
isAddingLocation = !isAddingLocation;
|
||
|
||
const crosshair = document.getElementById('crosshair');
|
||
const addBtn = document.getElementById('add-location-btn');
|
||
|
||
if (isAddingLocation) {
|
||
crosshair.classList.remove('hidden');
|
||
addBtn.classList.add('active');
|
||
addBtn.innerHTML = '<span class="btn-icon">✕</span><span class="btn-text">Cancel</span>';
|
||
map.on('click', handleMapClick);
|
||
} else {
|
||
crosshair.classList.add('hidden');
|
||
addBtn.classList.remove('active');
|
||
addBtn.innerHTML = '<span class="btn-icon">➕</span><span class="btn-text">Add Location Here</span>';
|
||
map.off('click', handleMapClick);
|
||
}
|
||
}
|
||
|
||
// Handle map click in add mode
|
||
function handleMapClick(e) {
|
||
if (!isAddingLocation) return;
|
||
|
||
const { lat, lng } = e.latlng;
|
||
openAddModal(lat, lng);
|
||
toggleAddLocationMode();
|
||
}
|
||
|
||
// Open add location modal
|
||
function openAddModal(lat, lng) {
|
||
const modal = document.getElementById('add-modal');
|
||
const latInput = document.getElementById('location-lat');
|
||
const lngInput = document.getElementById('location-lng');
|
||
const geoInput = document.getElementById('geo-location');
|
||
|
||
// Set coordinates
|
||
latInput.value = lat.toFixed(8);
|
||
lngInput.value = lng.toFixed(8);
|
||
geoInput.value = `${lat.toFixed(8)};${lng.toFixed(8)}`;
|
||
|
||
// Clear other fields
|
||
document.getElementById('location-form').reset();
|
||
latInput.value = lat.toFixed(8);
|
||
lngInput.value = lng.toFixed(8);
|
||
geoInput.value = `${lat.toFixed(8)};${lng.toFixed(8)}`;
|
||
|
||
// Show modal
|
||
modal.classList.remove('hidden');
|
||
}
|
||
|
||
// Close add modal
|
||
function closeAddModal() {
|
||
const modal = document.getElementById('add-modal');
|
||
modal.classList.add('hidden');
|
||
document.getElementById('location-form').reset();
|
||
}
|
||
|
||
// Handle add location form submission
|
||
async function handleAddLocation(e) {
|
||
e.preventDefault();
|
||
|
||
const formData = new FormData(e.target);
|
||
const data = {};
|
||
|
||
// Convert form data to object
|
||
for (let [key, value] of formData.entries()) {
|
||
// Map form field names to NocoDB column names
|
||
if (key === 'latitude') data.latitude = value.trim();
|
||
else if (key === 'longitude') data.longitude = value.trim();
|
||
else if (key === 'Geo-Location') data['Geo-Location'] = value.trim();
|
||
else if (value.trim() !== '') {
|
||
data[key] = value.trim();
|
||
}
|
||
}
|
||
|
||
// Ensure geo-location is set
|
||
if (data.latitude && data.longitude) {
|
||
data['Geo-Location'] = `${data.latitude};${data.longitude}`;
|
||
}
|
||
|
||
// Handle checkbox
|
||
data.Sign = document.getElementById('sign').checked;
|
||
|
||
try {
|
||
const response = await fetch('/api/locations', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
showStatus('Location added successfully!', 'success');
|
||
closeAddModal();
|
||
loadLocations();
|
||
} else {
|
||
throw new Error(result.error || 'Failed to add location');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error adding location:', error);
|
||
showStatus(error.message || 'Failed to add location', 'error');
|
||
}
|
||
}
|
||
|
||
// Open edit form
|
||
function openEditForm(location) {
|
||
currentEditingLocation = location;
|
||
|
||
// Debug: Log all possible ID fields
|
||
console.log('Opening edit form for location:', {
|
||
'Id': location.Id,
|
||
'id': location.id,
|
||
'ID': location.ID,
|
||
'_id': location._id,
|
||
'all_keys': Object.keys(location)
|
||
});
|
||
|
||
// Extract ID - check multiple possible field names
|
||
const locationId = location.Id || location.id || location.ID || location._id;
|
||
|
||
if (!locationId) {
|
||
console.error('No ID found in location object. Available fields:', Object.keys(location));
|
||
showStatus('Error: Location ID not found. Check console for details.', 'error');
|
||
return;
|
||
}
|
||
|
||
// Store the ID in a data attribute for later use
|
||
document.getElementById('edit-location-id').value = locationId;
|
||
document.getElementById('edit-location-id').setAttribute('data-location-id', locationId);
|
||
|
||
// Populate form fields
|
||
document.getElementById('edit-first-name').value = location['First Name'] || '';
|
||
document.getElementById('edit-last-name').value = location['Last Name'] || '';
|
||
document.getElementById('edit-location-email').value = location.Email || '';
|
||
document.getElementById('edit-location-phone').value = location.Phone || '';
|
||
document.getElementById('edit-location-unit').value = location['Unit Number'] || '';
|
||
document.getElementById('edit-support-level').value = location['Support Level'] || '';
|
||
document.getElementById('edit-location-address').value = location.Address || '';
|
||
document.getElementById('edit-sign').checked = location.Sign === true || location.Sign === 'true' || location.Sign === 1;
|
||
document.getElementById('edit-sign-size').value = location['Sign Size'] || '';
|
||
document.getElementById('edit-location-notes').value = location.Notes || '';
|
||
document.getElementById('edit-location-lat').value = location.latitude || '';
|
||
document.getElementById('edit-location-lng').value = location.longitude || '';
|
||
document.getElementById('edit-geo-location').value = location['Geo-Location'] || '';
|
||
|
||
// Show edit footer
|
||
document.getElementById('edit-footer').classList.remove('hidden');
|
||
}
|
||
|
||
// Close edit form
|
||
function closeEditForm() {
|
||
document.getElementById('edit-footer').classList.add('hidden');
|
||
currentEditingLocation = null;
|
||
}
|
||
|
||
// Handle edit location form submission
|
||
async function handleEditLocation(e) {
|
||
e.preventDefault();
|
||
|
||
if (!currentEditingLocation) return;
|
||
|
||
// Get the stored location ID
|
||
const locationIdElement = document.getElementById('edit-location-id');
|
||
const locationId = locationIdElement.getAttribute('data-location-id') || locationIdElement.value;
|
||
|
||
if (!locationId || locationId === 'undefined') {
|
||
showStatus('Error: Location ID not found', 'error');
|
||
return;
|
||
}
|
||
|
||
const formData = new FormData(e.target);
|
||
const data = {};
|
||
|
||
// Convert form data to object
|
||
for (let [key, value] of formData.entries()) {
|
||
// Skip the ID field
|
||
if (key === 'id' || key === 'Id' || key === 'ID') continue;
|
||
|
||
if (value !== null && value !== undefined) {
|
||
// Don't skip empty strings - they may be intentional field clearing
|
||
data[key] = value.trim();
|
||
}
|
||
}
|
||
|
||
// Ensure geo-location is set
|
||
if (data.latitude && data.longitude) {
|
||
data['Geo-Location'] = `${data.latitude};${data.longitude}`;
|
||
}
|
||
|
||
// Handle checkbox
|
||
data.Sign = document.getElementById('edit-sign').checked;
|
||
|
||
// Add debugging
|
||
console.log('Sending update data for ID:', locationId);
|
||
console.log('Update data:', data);
|
||
|
||
try {
|
||
const response = await fetch(`/api/locations/${locationId}`, {
|
||
method: 'PUT',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
const responseText = await response.text();
|
||
let result;
|
||
|
||
try {
|
||
result = JSON.parse(responseText);
|
||
} catch (e) {
|
||
console.error('Failed to parse response:', responseText);
|
||
throw new Error(`Server response error: ${response.status} ${response.statusText}`);
|
||
}
|
||
|
||
if (result.success) {
|
||
showStatus('Location updated successfully!', 'success');
|
||
closeEditForm();
|
||
loadLocations();
|
||
} else {
|
||
throw new Error(result.error || 'Failed to update location');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error updating location:', error);
|
||
showStatus(`Update failed: ${error.message}`, 'error');
|
||
}
|
||
}
|
||
|
||
// Handle delete location
|
||
async function handleDeleteLocation() {
|
||
if (!currentEditingLocation) return;
|
||
|
||
// Get the stored location ID
|
||
const locationIdElement = document.getElementById('edit-location-id');
|
||
const locationId = locationIdElement.getAttribute('data-location-id') || locationIdElement.value;
|
||
|
||
if (!locationId || locationId === 'undefined') {
|
||
showStatus('Error: Location ID not found', 'error');
|
||
return;
|
||
}
|
||
|
||
if (!confirm('Are you sure you want to delete this location?')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`/api/locations/${locationId}`, {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.success) {
|
||
showStatus('Location deleted successfully!', 'success');
|
||
closeEditForm();
|
||
loadLocations();
|
||
} else {
|
||
throw new Error(result.error || 'Failed to delete location');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error deleting location:', error);
|
||
showStatus(error.message || 'Failed to delete location', 'error');
|
||
}
|
||
}
|
||
|
||
// Lookup address based on current coordinates
|
||
async function lookupAddress(mode) {
|
||
let latInput, lngInput, addressInput;
|
||
|
||
if (mode === 'add') {
|
||
latInput = document.getElementById('location-lat');
|
||
lngInput = document.getElementById('location-lng');
|
||
addressInput = document.getElementById('location-address');
|
||
} else if (mode === 'edit') {
|
||
latInput = document.getElementById('edit-location-lat');
|
||
lngInput = document.getElementById('edit-location-lng');
|
||
addressInput = document.getElementById('edit-location-address');
|
||
} else {
|
||
console.error('Invalid lookup mode:', mode);
|
||
return;
|
||
}
|
||
|
||
if (!latInput || !lngInput || !addressInput) {
|
||
showStatus('Form elements not found', 'error');
|
||
return;
|
||
}
|
||
|
||
const lat = parseFloat(latInput.value);
|
||
const lng = parseFloat(lngInput.value);
|
||
|
||
if (isNaN(lat) || isNaN(lng)) {
|
||
showStatus('Please enter valid coordinates first', 'warning');
|
||
return;
|
||
}
|
||
|
||
// Show loading state
|
||
const button = mode === 'add' ?
|
||
document.getElementById('lookup-address-add-btn') :
|
||
document.getElementById('lookup-address-edit-btn');
|
||
|
||
const originalText = button ? button.textContent : '';
|
||
if (button) {
|
||
button.disabled = true;
|
||
button.textContent = 'Looking up...';
|
||
}
|
||
|
||
try {
|
||
console.log(`Looking up address for: ${lat}, ${lng}`);
|
||
const response = await fetch(`/api/geocode/reverse?lat=${lat}&lng=${lng}`);
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text();
|
||
throw new Error(`Geocoding failed: ${response.status} ${errorText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success && data.data) {
|
||
// Use the formatted address or full address
|
||
const address = data.data.formattedAddress || data.data.fullAddress;
|
||
if (address) {
|
||
addressInput.value = address;
|
||
showStatus('Address found!', 'success');
|
||
} else {
|
||
showStatus('No address found for these coordinates', 'warning');
|
||
}
|
||
} else {
|
||
showStatus('Address lookup failed', 'warning');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('Address lookup error:', error);
|
||
showStatus(`Address lookup failed: ${error.message}`, 'error');
|
||
} finally {
|
||
// Restore button state
|
||
if (button) {
|
||
button.disabled = false;
|
||
button.textContent = originalText;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Toggle fullscreen
|
||
function toggleFullscreen() {
|
||
const app = document.getElementById('app');
|
||
const btn = document.getElementById('fullscreen-btn');
|
||
|
||
if (!document.fullscreenElement) {
|
||
app.requestFullscreen().then(() => {
|
||
app.classList.add('fullscreen');
|
||
btn.innerHTML = '<span class="btn-icon">◱</span><span class="btn-text">Exit Fullscreen</span>';
|
||
}).catch(err => {
|
||
console.error('Error entering fullscreen:', err);
|
||
showStatus('Unable to enter fullscreen', 'error');
|
||
});
|
||
} else {
|
||
document.exitFullscreen().then(() => {
|
||
app.classList.remove('fullscreen');
|
||
btn.innerHTML = '<span class="btn-icon">⛶</span><span class="btn-text">Fullscreen</span>';
|
||
});
|
||
}
|
||
}
|
||
|
||
// Update location count
|
||
function updateLocationCount(count) {
|
||
const countElement = document.getElementById('location-count');
|
||
if (countElement) {
|
||
countElement.textContent = `${count} location${count !== 1 ? 's' : ''}`;
|
||
}
|
||
}
|
||
|
||
// Setup auto-refresh
|
||
function setupAutoRefresh() {
|
||
refreshInterval = setInterval(() => {
|
||
loadLocations();
|
||
}, CONFIG.REFRESH_INTERVAL);
|
||
}
|
||
|
||
// Show status message
|
||
function showStatus(message, type = 'info') {
|
||
const container = document.getElementById('status-container');
|
||
|
||
const messageDiv = document.createElement('div');
|
||
messageDiv.className = `status-message ${type}`;
|
||
messageDiv.textContent = message;
|
||
|
||
container.appendChild(messageDiv);
|
||
|
||
// Auto-remove after 5 seconds
|
||
setTimeout(() => {
|
||
messageDiv.remove();
|
||
}, 5000);
|
||
}
|
||
|
||
// Hide loading overlay
|
||
function hideLoading() {
|
||
const loading = document.getElementById('loading');
|
||
if (loading) {
|
||
loading.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
// Escape HTML for security
|
||
function escapeHtml(text) {
|
||
if (text === null || text === undefined) {
|
||
return '';
|
||
}
|
||
const div = document.createElement('div');
|
||
div.textContent = String(text);
|
||
return div.innerHTML;
|
||
}
|
||
|
||
// Clean up on page unload
|
||
window.addEventListener('beforeunload', () => {
|
||
if (refreshInterval) {
|
||
clearInterval(refreshInterval);
|
||
}
|
||
});
|