// UI interaction handlers
import { showStatus, parseGeoLocation } from './utils.js';
import { map, toggleStartLocationVisibility } from './map-manager.js';
import { loadLocations, handleAddLocation, handleEditLocation, handleDeleteLocation, openEditForm, closeEditForm, closeAddModal, openAddModal } from './location-manager.js';
import { toggleEdmontonParcelsLayer } from './external-layers.js';
export let userLocationMarker = null;
export let isAddingLocation = false;
export let isAddressConfirmed = false;
export let isEditAddressConfirmed = false;
// Add this after the existing imports
let isMoveMode = false;
// Export function to get current confirmation states
export function getAddressConfirmationState() {
return {
isAddressConfirmed,
isEditAddressConfirmed
};
}
/**
* Sets the address confirmation state and updates the UI accordingly.
* @param {'add' | 'edit'} mode - The form mode.
* @param {boolean} confirmed - The confirmation state to set.
*/
export function setAddressConfirmed(mode, confirmed) {
const button = mode === 'add' ?
document.getElementById('confirm-address-add-btn') :
document.getElementById('confirm-address-edit-btn');
const saveButton = mode === 'add' ?
document.getElementById('save-location-btn') :
document.getElementById('save-edit-location-btn');
if (mode === 'add') {
isAddressConfirmed = confirmed;
} else {
isEditAddressConfirmed = confirmed;
}
if (confirmed) {
if (button) {
button.textContent = '✅ Address Confirmed';
button.classList.remove('btn-secondary');
button.classList.add('btn-success');
button.disabled = false;
}
} else {
if (button) {
button.textContent = '📍 Confirm Address';
button.classList.remove('btn-success');
button.classList.add('btn-secondary');
button.disabled = false;
}
}
// Always enable save button - confirmation is now optional
if (saveButton) {
saveButton.disabled = false;
}
}
// Add logout function
async function logout() {
try {
const response = await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include'
});
if (response.ok) {
// Clear any local storage or session storage
localStorage.clear();
sessionStorage.clear();
// Redirect to login page
window.location.href = '/login.html';
} else {
throw new Error('Logout failed');
}
} catch (error) {
console.error('Logout error:', error);
showStatus('Logout failed, redirecting anyway...', 'warning');
// Force redirect even if logout request fails
setTimeout(() => {
window.location.href = '/login.html';
}, 1000);
}
}
// Add logout handler function
function handleLogout(e) {
e.preventDefault();
if (confirm('Are you sure you want to logout?')) {
logout();
}
}
export 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], 19);
// 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('Your Location');
}
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
}
);
}
export function toggleAddLocationMode() {
isAddingLocation = !isAddingLocation;
const crosshair = document.getElementById('crosshair');
const addBtn = document.getElementById('add-location-btn');
const mobileAddBtn = document.getElementById('mobile-add-location-btn');
if (isAddingLocation) {
crosshair.classList.remove('hidden');
// Update desktop button
if (addBtn) {
addBtn.classList.add('active');
addBtn.innerHTML = '✕Cancel';
}
// Update mobile button
if (mobileAddBtn) {
mobileAddBtn.classList.add('active');
mobileAddBtn.innerHTML = '✕';
mobileAddBtn.title = 'Cancel';
}
map.on('click', handleMapClick);
} else {
crosshair.classList.add('hidden');
// Update desktop button
if (addBtn) {
addBtn.classList.remove('active');
addBtn.innerHTML = '➕Add Location Here';
}
// Update mobile button
if (mobileAddBtn) {
mobileAddBtn.classList.remove('active');
mobileAddBtn.innerHTML = '➕';
mobileAddBtn.title = 'Add Location';
}
map.off('click', handleMapClick);
}
}
function handleMapClick(e) {
if (!isAddingLocation) return;
const { lat, lng } = e.latlng;
openAddModal(lat, lng);
toggleAddLocationMode();
}
export function toggleFullscreen() {
const app = document.getElementById('app');
const btn = document.getElementById('fullscreen-btn');
const mobileBtn = document.getElementById('mobile-fullscreen-btn');
if (!document.fullscreenElement) {
app.requestFullscreen().then(() => {
app.classList.add('fullscreen');
// Update desktop button
if (btn) {
btn.innerHTML = '◱Exit Fullscreen';
}
// Update mobile button
if (mobileBtn) {
mobileBtn.innerHTML = '◱';
mobileBtn.title = 'Exit Fullscreen';
}
}).catch(err => {
console.error('Error entering fullscreen:', err);
showStatus('Unable to enter fullscreen', 'error');
});
} else {
document.exitFullscreen().then(() => {
app.classList.remove('fullscreen');
// Update desktop button
if (btn) {
btn.innerHTML = '⛶Fullscreen';
}
// Update mobile button
if (mobileBtn) {
mobileBtn.innerHTML = '⛶';
mobileBtn.title = 'Fullscreen';
}
});
}
}
export async function confirmAddress(mode) {
let latInput, lngInput, addressInput, saveButton;
if (mode === 'add') {
latInput = document.getElementById('location-lat');
lngInput = document.getElementById('location-lng');
addressInput = document.getElementById('location-address');
saveButton = document.getElementById('save-location-btn');
} else if (mode === 'edit') {
latInput = document.getElementById('edit-location-lat');
lngInput = document.getElementById('edit-location-lng');
addressInput = document.getElementById('edit-location-address');
saveButton = document.getElementById('save-edit-location-btn'); // We'll need to add this ID to edit form
} else {
console.error('Invalid confirm 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;
}
// Get the confirm button
const button = mode === 'add' ?
document.getElementById('confirm-address-add-btn') :
document.getElementById('confirm-address-edit-btn');
const originalText = button ? button.textContent : '';
// Check if already confirmed
const currentlyConfirmed = mode === 'add' ? isAddressConfirmed : isEditAddressConfirmed;
// If already confirmed, toggle back to unconfirmed state
if (currentlyConfirmed) {
if (mode === 'add') {
isAddressConfirmed = false;
} else {
isEditAddressConfirmed = false;
}
// Reset button but keep save enabled
if (button) {
button.textContent = '📍 Confirm Address';
button.classList.remove('btn-success');
button.classList.add('btn-secondary');
}
// Save button remains enabled - confirmation is optional
showStatus('Address confirmation removed - you can still save the location', 'info');
return;
}
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;
// Mark as confirmed
if (mode === 'add') {
isAddressConfirmed = true;
} else {
isEditAddressConfirmed = true;
}
// Update button to show confirmed state
if (button) {
button.textContent = '✅ Address Confirmed';
button.classList.remove('btn-secondary');
button.classList.add('btn-success');
}
showStatus('Address confirmed and updated!', '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 not confirmed
const finallyConfirmed = mode === 'add' ? isAddressConfirmed : isEditAddressConfirmed;
if (button && !finallyConfirmed) {
button.disabled = false;
button.textContent = originalText;
}
}
}
export function resetAddressConfirmation(mode) {
if (mode === 'add') {
isAddressConfirmed = false;
const button = document.getElementById('confirm-address-add-btn');
const saveButton = document.getElementById('save-location-btn');
if (button) {
button.textContent = '📍 Confirm Address';
button.classList.remove('btn-success');
button.classList.add('btn-secondary');
button.disabled = false;
}
// Keep save button enabled - confirmation is now optional
if (saveButton) {
saveButton.disabled = false;
}
} else if (mode === 'edit') {
isEditAddressConfirmed = false;
const button = document.getElementById('confirm-address-edit-btn');
const saveButton = document.getElementById('save-edit-location-btn');
if (button) {
button.textContent = '📍 Confirm Address';
button.classList.remove('btn-success');
button.classList.add('btn-secondary');
button.disabled = false;
}
// Keep save button enabled - confirmation is now optional
if (saveButton) {
saveButton.disabled = false;
}
}
}
export 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}`;
}
// Reset address confirmation when coordinates change
resetAddressConfirmation('add');
});
});
addGeoInput.addEventListener('input', () => {
const coords = parseGeoLocation(addGeoInput.value);
if (coords) {
addLatInput.value = coords.lat;
addLngInput.value = coords.lng;
}
// Reset address confirmation when geo-location changes
resetAddressConfirmation('add');
});
}
// 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}`;
}
// Reset address confirmation when coordinates change
resetAddressConfirmation('edit');
});
});
editGeoInput.addEventListener('input', () => {
const coords = parseGeoLocation(editGeoInput.value);
if (coords) {
editLatInput.value = coords.lat;
editLngInput.value = coords.lng;
}
// Reset address confirmation when geo-location changes
resetAddressConfirmation('edit');
});
}
}
export function setupEventListeners() {
// Desktop controls
document.getElementById('refresh-btn')?.addEventListener('click', () => {
loadLocations();
showStatus('Locations refreshed', 'success');
});
document.getElementById('geolocate-btn')?.addEventListener('click', getUserLocation);
document.getElementById('toggle-start-location-btn')?.addEventListener('click', toggleStartLocationVisibility);
document.getElementById('add-location-btn')?.addEventListener('click', toggleAddLocationMode);
document.getElementById('toggle-edmonton-layer-btn')?.addEventListener('click', toggleEdmontonParcelsLayer);
document.getElementById('fullscreen-btn')?.addEventListener('click', toggleFullscreen);
// Add logout button event listeners
document.getElementById('logout-btn')?.addEventListener('click', handleLogout);
document.getElementById('mobile-logout-btn')?.addEventListener('click', handleLogout);
// Mobile controls
document.getElementById('mobile-refresh-btn')?.addEventListener('click', () => {
loadLocations();
showStatus('Locations refreshed', 'success');
});
document.getElementById('mobile-geolocate-btn')?.addEventListener('click', getUserLocation);
document.getElementById('mobile-toggle-start-location-btn')?.addEventListener('click', toggleStartLocationVisibility);
document.getElementById('mobile-add-location-btn')?.addEventListener('click', toggleAddLocationMode);
document.getElementById('mobile-overlay-btn')?.addEventListener('click', () => {
console.log('Mobile overlay button clicked!');
// Call the global function to open mobile overlay modal
if (window.openMobileOverlayModal) {
console.log('openMobileOverlayModal function found - calling it');
window.openMobileOverlayModal();
} else {
console.error('openMobileOverlayModal function not available');
console.log('Available window functions:', Object.keys(window).filter(k => k.includes('overlay') || k.includes('Modal')));
}
});
document.getElementById('mobile-toggle-edmonton-layer-btn')?.addEventListener('click', toggleEdmontonParcelsLayer);
document.getElementById('mobile-fullscreen-btn')?.addEventListener('click', toggleFullscreen);
// Mobile dropdown toggle
document.getElementById('mobile-dropdown-toggle')?.addEventListener('click', (e) => {
e.stopPropagation();
const dropdown = document.getElementById('mobile-dropdown');
dropdown.classList.toggle('active');
});
// Close mobile dropdown when clicking outside
document.addEventListener('click', (e) => {
const dropdown = document.getElementById('mobile-dropdown');
if (!dropdown.contains(e.target)) {
dropdown.classList.remove('active');
}
});
// 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 confirm buttons
document.getElementById('confirm-address-add-btn')?.addEventListener('click', () => {
confirmAddress('add');
});
document.getElementById('confirm-address-edit-btn')?.addEventListener('click', () => {
confirmAddress('edit');
});
// Auto address lookup event listener
document.addEventListener('autoAddressLookup', (e) => {
const { mode } = e.detail;
if (mode === 'add') {
// Add a small delay to ensure the form is fully rendered
setTimeout(() => {
confirmAddress('add');
}, 200);
}
});
// Geo-location field sync
setupGeoLocationSync();
// Add event delegation for popup edit buttons
document.addEventListener('click', (e) => {
if (e.target.classList.contains('edit-location-popup-btn')) {
e.preventDefault();
try {
const locationData = JSON.parse(e.target.getAttribute('data-location'));
openEditForm(locationData);
} catch (error) {
console.error('Error parsing location data:', error);
showStatus('Error opening edit form', 'error');
}
}
// Add handler for move button
if (e.target.classList.contains('move-location-popup-btn')) {
e.preventDefault();
try {
const locationData = JSON.parse(e.target.getAttribute('data-location'));
// Find the marker that corresponds to this location
import('./location-manager.js').then(({ markers, startMovingMarker }) => {
const marker = markers.find(m => {
const markerData = m._locationData;
const locationId = locationData.Id || locationData.id || locationData.ID || locationData._id;
const markerId = markerData?.Id || markerData?.id || markerData?.ID || markerData?._id;
return markerId === locationId;
});
if (marker) {
startMovingMarker(locationData, marker);
} else {
console.error('Could not find marker for location:', locationData);
showStatus('Could not find marker for this location', 'error');
}
});
} catch (error) {
console.error('Error starting move:', error);
showStatus('Failed to start move mode', 'error');
}
}
});
// Improve popup button handling for mobile
map.on('popupopen', function(e) {
const popup = e.popup;
const content = popup.getContent();
// Create a temporary container to parse the HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
// Find buttons in the popup
const editBtn = tempDiv.querySelector('.edit-location-popup-btn');
const moveBtn = tempDiv.querySelector('.move-location-popup-btn');
if (editBtn || moveBtn) {
// Re-render popup with proper event handlers
setTimeout(() => {
const popupNode = popup._contentNode || popup._container;
if (!popupNode) return;
// Edit button
const editButton = popupNode.querySelector('.edit-location-popup-btn');
if (editButton) {
editButton.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
try {
const locationData = JSON.parse(this.getAttribute('data-location'));
openEditForm(locationData);
map.closePopup();
} catch (error) {
console.error('Error parsing location data:', error);
showStatus('Error opening edit form', 'error');
}
});
}
// Move button
const moveButton = popupNode.querySelector('.move-location-popup-btn');
if (moveButton) {
moveButton.addEventListener('click', async function(event) {
event.preventDefault();
event.stopPropagation();
try {
const locationData = JSON.parse(this.getAttribute('data-location'));
const { startMovingMarker } = await import('./location-manager.js');
startMovingMarker(locationData, e.popup._source);
} catch (error) {
console.error('Error starting move:', error);
showStatus('Failed to start move mode', 'error');
}
});
}
}, 0);
}
});
// Add this after the existing event listeners
document.addEventListener('updateMoveButtons', (e) => {
isMoveMode = e.detail.isMoving;
if (isMoveMode) {
// Hide add location buttons during move mode
const addBtn = document.getElementById('add-location-btn');
const mobileAddBtn = document.getElementById('mobile-add-location-btn');
if (addBtn) addBtn.style.display = 'none';
if (mobileAddBtn) mobileAddBtn.style.display = 'none';
// Add cancel move button
addCancelMoveButton();
} else {
// Restore add location buttons
const addBtn = document.getElementById('add-location-btn');
const mobileAddBtn = document.getElementById('mobile-add-location-btn');
if (addBtn) addBtn.style.display = '';
if (mobileAddBtn) mobileAddBtn.style.display = '';
// Remove cancel move button
removeCancelMoveButton();
}
});
}
// Add this to the setupMapEventListeners function
export function setupMapEventListeners(mapInstance) {
// ...existing code...
// Handle move location button clicks in popups
mapInstance.on('popupopen', function(e) {
// ...existing code for edit button...
// Handle move button
const moveBtn = e.popup._contentNode?.querySelector('.move-location-popup-btn');
if (moveBtn) {
moveBtn.addEventListener('click', async function() {
try {
const locationData = JSON.parse(this.getAttribute('data-location'));
const marker = e.popup._source;
const { startMovingMarker } = await import('./location-manager.js');
startMovingMarker(locationData, marker);
} catch (error) {
console.error('Error starting move:', error);
showStatus('Failed to start move mode', 'error');
}
});
}
});
}
function addCancelMoveButton() {
// Desktop button
const mapControls = document.querySelector('.map-controls');
if (mapControls && !document.getElementById('cancel-move-btn')) {
const cancelBtn = document.createElement('button');
cancelBtn.id = 'cancel-move-btn';
cancelBtn.className = 'btn btn-danger';
cancelBtn.innerHTML = '✕Cancel Move';
cancelBtn.addEventListener('click', async () => {
const { cancelMove } = await import('./location-manager.js');
cancelMove();
});
mapControls.insertBefore(cancelBtn, mapControls.firstChild);
}
// Mobile button
const mobileSidebar = document.getElementById('mobile-sidebar');
if (mobileSidebar && !document.getElementById('mobile-cancel-move-btn')) {
const mobileCancelBtn = document.createElement('button');
mobileCancelBtn.id = 'mobile-cancel-move-btn';
mobileCancelBtn.className = 'btn btn-danger';
mobileCancelBtn.title = 'Cancel Move';
mobileCancelBtn.innerHTML = '✕';
mobileCancelBtn.addEventListener('click', async () => {
const { cancelMove } = await import('./location-manager.js');
cancelMove();
});
mobileSidebar.insertBefore(mobileCancelBtn, mobileSidebar.firstChild);
}
}
function removeCancelMoveButton() {
document.getElementById('cancel-move-btn')?.remove();
document.getElementById('mobile-cancel-move-btn')?.remove();
}