// Helper function to escape HTML to prevent XSS function escapeHtml(text) { if (typeof text !== 'string') return text; const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.replace(/[&<>"']/g, function(m) { return map[m]; }); } // Location management (CRUD operations) import { map } from './map-manager.js'; import { showStatus, updateLocationCount } from './utils.js'; import { currentUser } from './auth.js'; import { resetAddressConfirmation } from './ui-controls.js'; export let markers = []; export let currentEditingLocation = null; // Add these variables at the top with other exports export let isMovingMarker = false; export let movingMarker = null; export let originalPosition = null; export let movingLocationData = null; let originalIcon = null; export async function loadLocations() { try { showStatus('Loading locations...', 'info'); const response = await fetch('/api/locations'); // Handle rate limit responses if (response.status === 429) { let errorMessage = 'Too many requests. Please wait a moment.'; try { const errorData = await response.json(); if (errorData.error) { errorMessage = errorData.error; } } catch (e) { // If we can't parse the JSON, use the default message } console.warn('Rate limited:', errorMessage); showStatus(errorMessage, 'warning'); return; // Don't throw an error, just return } const data = await response.json(); if (!data.success) { throw new Error(data.error || 'Failed to load locations'); } // Check if data is limited (temp user) if (data.isLimited && currentUser?.userType === 'temp') { console.log('Map data loaded'); // Generic message for temp users } else { console.log(`Loaded ${data.count} locations from NocoDB`); } displayLocations(data.locations); updateLocationCount(data.locations.length); } catch (error) { console.error('Error loading locations:', error); showStatus('Failed to load locations', 'error'); } } export function displayLocations(locations) { // Clear existing markers markers.forEach(marker => { if (marker && map) { map.removeLayer(marker); } }); markers = []; // Group locations by address to identify multi-unit buildings const addressGroups = new Map(); locations.forEach(location => { if (location.latitude && location.longitude) { const address = location.Address || 'No address'; const addressKey = address.toLowerCase().trim(); if (!addressGroups.has(addressKey)) { addressGroups.set(addressKey, { address: address, locations: [], lat: parseFloat(location.latitude), lng: parseFloat(location.longitude) }); } addressGroups.get(addressKey).locations.push(location); } }); // Create markers for each address group addressGroups.forEach(group => { if (group.locations.length > 1) { // Multi-unit building - create apartment-style marker const marker = createMultiUnitMarker(group); if (marker) { markers.push(marker); } } else { // Single unit - create regular marker const marker = createLocationMarker(group.locations[0]); if (marker) { markers.push(marker); } } }); // Limit console output for temp users if (currentUser?.userType === 'temp') { console.log('Map built successfully'); } else { console.log(`Displayed ${markers.length} location markers (${locations.length} total locations)`); } } function createLocationMarker(location) { if (!map) { console.warn('Map not initialized, skipping marker creation'); return null; } // Try to get coordinates from multiple possible sources let lat, lng; // First try the Geo-Location field if (location['Geo-Location']) { const coords = location['Geo-Location'].split(';'); if (coords.length === 2) { lat = parseFloat(coords[0]); lng = parseFloat(coords[1]); } } // If that didn't work, try latitude/longitude fields if ((!lat || !lng) && location.latitude && location.longitude) { lat = parseFloat(location.latitude); lng = parseFloat(location.longitude); } // Validate coordinates if (!lat || !lng || isNaN(lat) || isNaN(lng)) { console.warn('Invalid coordinates for location:', location); return null; } // Determine marker color based on support level let markerColor = '#3388ff'; // Default blue if (location['Support Level']) { const level = parseInt(location['Support Level']); switch(level) { case 1: markerColor = '#27ae60'; break; // Green case 2: markerColor = '#f1c40f'; break; // Yellow case 3: markerColor = '#e67e22'; break; // Orange case 4: markerColor = '#e74c3c'; break; // Red } } // Create circle marker with explicit styling const marker = L.circleMarker([lat, lng], { radius: 8, fillColor: markerColor, color: '#ffffff', weight: 2, opacity: 1, fillOpacity: 0.8, className: 'location-marker' }); // Add to map marker.addTo(map); // Add custom dragging functionality for circle markers let _draggingEnabled = false; let isDragging = false; let dragStartLatLng = null; marker.dragging = { enabled: function() { return _draggingEnabled; }, enable: function() { _draggingEnabled = true; marker.on('mousedown', startDrag); marker._path.style.cursor = 'move'; }, disable: function() { _draggingEnabled = false; marker.off('mousedown', startDrag); marker.off('mousemove', drag); marker.off('mouseup', endDrag); map.off('mousemove', drag); map.off('mouseup', endDrag); marker._path.style.cursor = 'pointer'; } }; function startDrag(e) { if (!_draggingEnabled) return; isDragging = true; dragStartLatLng = e.latlng; map.dragging.disable(); marker.on('mousemove', drag); map.on('mousemove', drag); marker.on('mouseup', endDrag); map.on('mouseup', endDrag); L.DomEvent.stopPropagation(e); L.DomEvent.preventDefault(e); } function drag(e) { if (!isDragging) return; marker.setLatLng(e.latlng); L.DomEvent.stopPropagation(e); L.DomEvent.preventDefault(e); } function endDrag(e) { if (!isDragging) return; isDragging = false; map.dragging.enable(); marker.off('mousemove', drag); map.off('mousemove', drag); marker.off('mouseup', endDrag); map.off('mouseup', endDrag); L.DomEvent.stopPropagation(e); L.DomEvent.preventDefault(e); } const popupContent = createPopupContent(location); marker.bindPopup(popupContent); marker._locationData = location; // Only log marker creation for non-temp users if (currentUser?.userType !== 'temp') { console.log(`Created marker at ${lat}, ${lng} with color ${markerColor}`); } return marker; } function createPopupContent(location) { const locationId = location.Id || location.id || location.ID || location._id; // If current user is temp, show limited information but allow editing if (currentUser?.userType === 'temp') { 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 ` `; } // Full information for regular users and admins 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'; // Add debugging only for non-temp users if (currentUser?.userType !== 'temp') { console.log('Creating popup for location:', locationId, location); } return ` `; } export async function handleAddLocation(e) { e.preventDefault(); // Check if address is confirmed const { getAddressConfirmationState } = await import('./ui-controls.js'); const { isAddressConfirmed } = getAddressConfirmationState(); if (!isAddressConfirmed) { showStatus('Please confirm the address before saving the location', 'warning'); return; } 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'); } } export function openEditForm(location) { currentEditingLocation = 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; } // Reset address confirmation state resetAddressConfirmation('edit'); // 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'] || ''; // Hide delete button for temp users - use multiple approaches const deleteBtn = document.getElementById('delete-location-btn'); if (deleteBtn) { // Check both currentUser and body class to ensure restriction const isTemp = currentUser?.userType === 'temp' || document.body.classList.contains('temp-user'); if (isTemp) { deleteBtn.style.display = 'none'; deleteBtn.style.visibility = 'hidden'; deleteBtn.disabled = true; deleteBtn.classList.add('temp-hidden'); deleteBtn.setAttribute('hidden', 'true'); // Remove the button from DOM completely for temp users deleteBtn.remove(); } else { deleteBtn.style.display = 'inline-block'; deleteBtn.style.visibility = 'visible'; deleteBtn.disabled = false; deleteBtn.classList.remove('temp-hidden'); deleteBtn.removeAttribute('hidden'); } } // Show edit footer document.getElementById('edit-footer').classList.remove('hidden'); } export function closeEditForm() { document.getElementById('edit-footer').classList.add('hidden'); currentEditingLocation = null; } export async function handleEditLocation(e) { e.preventDefault(); if (!currentEditingLocation) return; // Check if address is confirmed const { getAddressConfirmationState } = await import('./ui-controls.js'); const { isEditAddressConfirmed } = getAddressConfirmationState(); if (!isEditAddressConfirmed) { showStatus('Please confirm the address before saving changes', 'warning'); 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) { 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; 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'); } } export 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'); } } export function closeAddModal() { const modal = document.getElementById('add-modal'); if (modal) { modal.classList.add('hidden'); } // Try to find and reset the form with multiple possible IDs const form = document.getElementById('location-form') || document.getElementById('add-location-form'); if (form) { form.reset(); } } export function openAddModal(lat, lng, performLookup = true) { const modal = document.getElementById('add-modal'); if (!modal) { console.error('Add modal not found'); return; } // Try multiple possible field IDs for coordinates const latInput = document.getElementById('location-lat') || document.getElementById('add-latitude') || document.getElementById('latitude'); const lngInput = document.getElementById('location-lng') || document.getElementById('add-longitude') || document.getElementById('longitude'); const geoInput = document.getElementById('geo-location') || document.getElementById('add-geo-location') || document.getElementById('Geo-Location'); // Reset address confirmation state resetAddressConfirmation('add'); // Set coordinates if input fields exist if (latInput && lngInput) { latInput.value = lat.toFixed(8); lngInput.value = lng.toFixed(8); } if (geoInput) { geoInput.value = `${lat.toFixed(8)};${lng.toFixed(8)}`; } // Try to find and reset the form const form = document.getElementById('location-form') || document.getElementById('add-location-form'); if (form) { // Clear other fields but preserve coordinates const tempLat = lat.toFixed(8); const tempLng = lng.toFixed(8); const tempGeo = `${tempLat};${tempLng}`; form.reset(); // Restore coordinates after reset if (latInput) latInput.value = tempLat; if (lngInput) lngInput.value = tempLng; if (geoInput) geoInput.value = tempGeo; } // Show modal modal.classList.remove('hidden'); // Conditionally perform the auto address lookup if (performLookup) { const autoLookupEvent = new CustomEvent('autoAddressLookup', { detail: { mode: 'add', lat, lng } }); document.dispatchEvent(autoLookupEvent); } } // Replace the startMovingMarker function export function startMovingMarker(location, marker) { console.log('startMovingMarker called with:', location, marker); if (!location) { console.error('Missing location data'); return; } const locationId = location.Id || location.id || location.ID || location._id; if (!locationId) { showStatus('Error: Location ID not found', 'error'); return; } // Store the location data and original position movingLocationData = location; originalPosition = marker ? marker.getLatLng() : null; isMovingMarker = true; // Close any popups map.closePopup(); // Show crosshairs const crosshair = document.getElementById('crosshair'); const crosshairInfo = crosshair.querySelector('.crosshair-info'); crosshairInfo.textContent = 'Click to move location here'; crosshair.classList.remove('hidden'); // Update buttons to show cancel state - use event system document.dispatchEvent(new CustomEvent('updateMoveButtons', { detail: { isMoving: true } })); // Show instructions const name = [location['First Name'], location['Last Name']] .filter(Boolean).join(' ') || 'Location'; showStatus(`Moving "${name}". Click anywhere on the map to set new position.`, 'info'); // Add click handler to map map.on('click', handleMoveMapClick); // Highlight the marker being moved if (marker) { movingMarker = marker; if (marker.setStyle) { // It's a circleMarker marker.setStyle({ fillColor: '#ff7800', fillOpacity: 0.9, radius: 10 }); } else if (marker.setIcon) { // It's a divIcon marker originalIcon = marker.getIcon(); const movingIcon = L.divIcon({ className: 'multi-unit-marker moving', html: `
`, iconSize: [24, 24], iconAnchor: [12, 12] }); marker.setIcon(movingIcon); } } } // Add new function to handle map clicks during move function handleMoveMapClick(e) { if (!isMovingMarker || !movingLocationData) return; const { lat, lng } = e.latlng; // Show confirmation dialog showMoveConfirmation(lat, lng); } // Add function to show move confirmation function showMoveConfirmation(lat, lng) { const name = [movingLocationData['First Name'], movingLocationData['Last Name']] .filter(Boolean).join(' ') || 'Location'; // Create a temporary marker at the new position const tempMarker = L.circleMarker([lat, lng], { radius: 10, fillColor: '#ff7800', color: '#fff', weight: 3, opacity: 1, fillOpacity: 0.5 }).addTo(map); // Create confirmation popup const confirmContent = `

Confirm Move

Move "${escapeHtml(name)}" to this location?

`; tempMarker.bindPopup(confirmContent, { closeButton: false, className: 'move-confirm-popup-wrapper' }).openPopup(); // Add event listeners setTimeout(() => { document.getElementById('confirm-move-btn')?.addEventListener('click', async () => { map.removeLayer(tempMarker); await saveMovePosition(lat, lng); }); document.getElementById('cancel-move-btn')?.addEventListener('click', () => { map.removeLayer(tempMarker); // Continue move mode }); }, 100); } // Update saveMovePosition to accept coordinates async function saveMovePosition(lat, lng) { if (!movingLocationData) return; const locationId = movingLocationData.Id || movingLocationData.id || movingLocationData.ID || movingLocationData._id; if (!locationId) { showStatus('Error: Location ID not found', 'error'); cancelMove(); return; } // Prepare update data const updateData = { latitude: lat.toFixed(8), longitude: lng.toFixed(8), 'Geo-Location': `${lat.toFixed(8)};${lng.toFixed(8)}` }; try { const response = await fetch(`/api/locations/${locationId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updateData) }); const result = await response.json(); if (result.success) { showStatus('Location moved successfully!', 'success'); cleanupMoveState(); // Use cleanup instead of cancelMove // Reload locations to update the map await loadLocations(); } else { throw new Error(result.error || 'Failed to update location'); } } catch (error) { console.error('Error moving location:', error); showStatus(`Failed to move location: ${error.message}`, 'error'); cancelMove(); } } // Add new function to clean up move state without showing cancellation message function cleanupMoveState() { // Hide crosshairs const crosshair = document.getElementById('crosshair'); crosshair.classList.add('hidden'); // Remove map click handler map.off('click', handleMoveMapClick); // Reset buttons - use event system document.dispatchEvent(new CustomEvent('updateMoveButtons', { detail: { isMoving: false } })); // Reset marker style if exists if (movingMarker && originalPosition) { if (movingMarker.setStyle) { // It's a circleMarker // Restore original marker style based on support level const location = movingMarker._locationData; let markerColor = '#3388ff'; if (location && location['Support Level']) { const level = parseInt(location['Support Level']); switch(level) { case 1: markerColor = '#27ae60'; break; case 2: markerColor = '#f1c40f'; break; case 3: markerColor = '#e67e22'; break; case 4: markerColor = '#e74c3c'; break; } } movingMarker.setStyle({ fillColor: markerColor, fillOpacity: 0.8, radius: 8 }); } else if (movingMarker.setIcon && originalIcon) { movingMarker.setIcon(originalIcon); } } // Reset state isMovingMarker = false; movingMarker = null; originalPosition = null; movingLocationData = null; originalIcon = null; } function createMultiUnitMarker(group) { if (!map) { console.warn('Map not initialized, skipping multi-unit marker creation'); return null; } const { lat, lng, locations, address } = group; // Validate coordinates if (!lat || !lng || isNaN(lat) || isNaN(lng)) { console.warn('Invalid coordinates for multi-unit location:', group); return null; } // Determine dominant support level for color coding const supportLevelCounts = locations.reduce((acc, loc) => { const level = loc['Support Level']; if (level) { acc[level] = (acc[level] || 0) + 1; } return acc; }, {}); let dominantSupportLevel = null; let maxCount = 0; for (const level in supportLevelCounts) { if (supportLevelCounts[level] > maxCount) { maxCount = supportLevelCounts[level]; dominantSupportLevel = level; } } // Set marker color based on dominant support level let markerColor = '#3388ff'; // Default blue, same as single markers if (dominantSupportLevel) { const level = parseInt(dominantSupportLevel); switch(level) { case 1: markerColor = '#27ae60'; break; // Green case 2: markerColor = '#f1c40f'; break; // Yellow case 3: markerColor = '#e67e22'; break; // Orange case 4: markerColor = '#e74c3c'; break; // Red } } // Create a square marker using DivIcon for apartment buildings const icon = L.divIcon({ className: 'multi-unit-marker', html: `
`, iconSize: [20, 20], iconAnchor: [10, 10], popupAnchor: [0, -10] }); const marker = L.marker([lat, lng], { icon: icon }); // Add to map marker.addTo(map); // Create apartment-style popup content const popupContent = createMultiUnitPopupContent(group); const popup = L.popup({ maxWidth: 320, minWidth: 280, closeButton: true, className: 'apartment-popup app-data' }).setContent(popupContent); marker.bindPopup(popup); marker._locationData = group; // Add event listener for when popup opens marker.on('popupopen', function(e) { setupAppApartmentPopupListeners(group); }); // Only log multi-unit marker creation for non-temp users if (currentUser?.userType !== 'temp') { console.log(`Created multi-unit marker at ${lat}, ${lng} with ${locations.length} units`); } return marker; } function createMultiUnitPopupContent(group) { const { address, locations } = group; const totalUnits = locations.length; // Sort locations by unit number if available const sortedLocations = locations.sort((a, b) => { const aUnit = a['Unit Number'] || ''; const bUnit = b['Unit Number'] || ''; // Extract numeric part for sorting const aNum = aUnit ? parseInt(aUnit.replace(/\D/g, '')) || 0 : 0; const bNum = bUnit ? parseInt(bUnit.replace(/\D/g, '')) || 0 : 0; return aNum - bNum; }); const popupId = `app-apartment-popup-${Math.random().toString(36).substr(2, 9)}`; // Truncate address if too long for mobile const displayAddress = address.length > 30 ? address.substring(0, 30) + '...' : address; const popupContent = `
🏢 ${escapeHtml(displayAddress)}
${totalUnits} contacts
Multi-Unit
${createUnitDetailsHTML(sortedLocations[0], 0)}
Your Campaign Database
`; return popupContent; } function createUnitDetailsHTML(location, index) { const locationId = location.Id || location.id || location.ID || location._id; const name = [location['First Name'], location['Last Name']].filter(Boolean).join(' ') || 'Unknown'; const unit = location['Unit Number'] || `Unit ${index + 1}`; const supportLevel = location['Support Level'] ? `Level ${location['Support Level']}` : 'Not specified'; // For temp users, hide sensitive contact information const isTemp = currentUser?.userType === 'temp'; const email = !isTemp ? (location.Email || '') : ''; const phone = !isTemp ? (location.Phone || '') : ''; // Truncate long values for mobile const truncatedEmail = email.length > 25 ? email.substring(0, 25) + '...' : email; const truncatedPhone = phone.length > 15 ? phone.substring(0, 15) + '...' : phone; const truncatedNotes = location.Notes && location.Notes.length > 50 ? location.Notes.substring(0, 50) + '...' : location.Notes; return `
${escapeHtml(unit)} - ${escapeHtml(name)}
Support: ${escapeHtml(supportLevel)}
${email ? `
📧 ${escapeHtml(truncatedEmail)}
` : ''} ${phone ? `
📞 ${escapeHtml(truncatedPhone)}
` : ''} ${location.Sign ? '
🏁 Has sign
' : ''} ${location.Notes ? `
${escapeHtml(truncatedNotes)}
` : ''} ${currentUser ? `
${currentUser.userType !== 'temp' ? ` ` : ''}
` : ''}
ID: ${locationId || 'Unknown'}
`; } function setupAppApartmentPopupListeners(group) { const { locations } = group; // Sort locations same as in popup creation const sortedLocations = locations.sort((a, b) => { const aUnit = a['Unit Number'] || ''; const bUnit = b['Unit Number'] || ''; const aNum = aUnit ? parseInt(aUnit.replace(/\D/g, '')) || 0 : 0; const bNum = bUnit ? parseInt(bUnit.replace(/\D/g, '')) || 0 : 0; return aNum - bNum; }); // Find the popup container in the DOM const container = document.querySelector('.apartment-building-popup.app-data'); if (!container) { console.log('App apartment popup container not found'); return; } const unitSelector = container.querySelector('.unit-selector'); const unitDetails = container.querySelector('.unit-details'); if (!unitSelector || !unitDetails) { console.log('App apartment popup elements not found'); return; } function updateUnitDisplay(selectedIndex) { const location = sortedLocations[selectedIndex]; unitDetails.innerHTML = createUnitDetailsHTML(location, selectedIndex); // Re-attach event listeners for the new buttons setTimeout(() => { attachUnitButtonListeners(); }, 10); } function attachUnitButtonListeners() { const editBtn = container.querySelector('.edit-unit-btn'); const moveBtn = container.querySelector('.move-unit-btn'); if (editBtn) { editBtn.addEventListener('click', (e) => { e.stopPropagation(); const locationData = JSON.parse(editBtn.getAttribute('data-location')); map.closePopup(); openEditForm(locationData); }); } if (moveBtn) { moveBtn.addEventListener('click', (e) => { e.stopPropagation(); const locationData = JSON.parse(moveBtn.getAttribute('data-location')); map.closePopup(); // Find the marker for this location const marker = markers.find(m => m._locationData && (m._locationData.locations ? m._locationData.locations.some(loc => (loc.Id || loc.id || loc.ID || loc._id) === (locationData.Id || locationData.id || locationData.ID || locationData._id) ) : (m._locationData.Id || m._locationData.id || m._locationData.ID || m._locationData._id) === (locationData.Id || locationData.id || locationData.ID || locationData._id) ) ); if (marker) { startMovingMarker(locationData, marker); } }); } } // Set up unit selector dropdown event listener unitSelector.addEventListener('change', (e) => { e.stopPropagation(); const selectedIndex = parseInt(e.target.value); console.log('Unit selected:', selectedIndex); updateUnitDisplay(selectedIndex); }); // Initialize with first unit selected and attach initial listeners updateUnitDisplay(0); console.log(`App apartment popup unit selector set up for ${sortedLocations.length} units`); }