Pushing Cuts to repo. Still bugs however decently stable.

This commit is contained in:
2025-08-06 13:47:51 -06:00
parent 677fcf8f4e
commit 81d132afe3
35 changed files with 7155 additions and 67 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -55,6 +55,8 @@ document.addEventListener('DOMContentLoaded', () => {
checkAndLoadWalkSheetConfig();
} else if (hash === '#convert-data') {
showSection('convert-data');
} else if (hash === '#cuts') {
showSection('cuts');
} else {
// Default to dashboard
showSection('dashboard');
@@ -479,6 +481,25 @@ function showSection(sectionId) {
}
}, 100);
}
// Special handling for cuts section
if (sectionId === 'cuts') {
// Initialize admin cuts manager when section is shown
setTimeout(() => {
if (typeof window.adminCutsManager === 'object' && window.adminCutsManager.initialize) {
if (!window.adminCutsManager.isInitialized) {
console.log('Initializing admin cuts manager from showSection...');
window.adminCutsManager.initialize().catch(error => {
console.error('Failed to initialize cuts manager:', error);
});
} else {
console.log('Admin cuts manager already initialized');
}
} else {
console.error('adminCutsManager not found in showSection');
}
}, 100);
}
}
// Update map from input fields

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,336 @@
/**
* Cut Drawing Module
* Handles polygon drawing functionality for creating map cuts
*/
export class CutDrawing {
constructor(map, options = {}) {
this.map = map;
this.vertices = [];
this.markers = [];
this.polyline = null;
this.previewPolygon = null; // Add preview polygon
this.isDrawing = false;
this.onComplete = options.onComplete || null;
}
/**
* Start drawing mode
*/
startDrawing(onFinish, onCancel) {
if (this.isDrawing) {
this.cancelDrawing();
}
this.isDrawing = true;
this.onFinishCallback = onFinish;
this.onCancelCallback = onCancel;
this.vertices = [];
this.markers = [];
// Change cursor and add click listener
this.map.getContainer().style.cursor = 'crosshair';
this.map.on('click', this.onMapClick.bind(this));
// Disable double-click zoom while drawing
this.map.doubleClickZoom.disable();
console.log('Cut drawing started - click to add points');
}
/**
* Handle map clicks to add vertices
*/
onMapClick(e) {
if (!this.isDrawing) return;
// Add vertex marker
const marker = L.marker(e.latlng, {
icon: L.divIcon({
className: 'cut-vertex-marker',
html: '<div class="vertex-point"></div>',
iconSize: [12, 12],
iconAnchor: [6, 6]
}),
draggable: false
}).addTo(this.map);
this.vertices.push(e.latlng);
this.markers.push(marker);
// Update the polyline
this.updatePolyline();
// Call update callback if available
if (this.onUpdate) {
this.onUpdate();
}
console.log(`Added vertex ${this.vertices.length} at`, e.latlng);
}
/**
* Update the polyline connecting vertices
*/
updatePolyline() {
// Remove existing polyline
if (this.polyline) {
this.map.removeLayer(this.polyline);
}
if (this.vertices.length > 1) {
// Create polyline connecting all vertices
this.polyline = L.polyline(this.vertices, {
color: '#3388ff',
weight: 2,
dashArray: '5, 5',
opacity: 0.8
}).addTo(this.map);
}
}
/**
* Finish drawing and create polygon
*/
finishDrawing() {
if (this.vertices.length < 3) {
alert('A cut must have at least 3 points');
return;
}
// Create the polygon
const latlngs = this.vertices.map(v => v.getLatLng());
// Close the polygon
latlngs.push(latlngs[0]);
// Generate GeoJSON
const geojson = {
type: 'Polygon',
coordinates: [latlngs.map(ll => [ll.lng, ll.lat])]
};
// Calculate bounds
const bounds = {
north: Math.max(...latlngs.map(ll => ll.lat)),
south: Math.min(...latlngs.map(ll => ll.lat)),
east: Math.max(...latlngs.map(ll => ll.lng)),
west: Math.min(...latlngs.map(ll => ll.lng))
};
console.log('Cut drawing finished with', this.vertices.length, 'vertices');
// Show preview before clearing drawing
const color = document.getElementById('cut-color')?.value || '#3388ff';
const opacity = parseFloat(document.getElementById('cut-opacity')?.value) || 0.3;
this.showPreview(geojson, color, opacity);
// Clean up drawing elements
this.clearDrawing();
// Call completion callback with the data
if (this.onComplete && typeof this.onComplete === 'function') {
console.log('Calling completion callback with geojson and bounds');
this.onComplete(geojson, bounds);
} else {
console.error('No completion callback defined');
}
// Reset state
this.isDrawing = false;
this.updateToolbar();
}
/**
* Cancel drawing
*/
cancelDrawing() {
if (!this.isDrawing) return;
console.log('Cut drawing cancelled');
this.cleanup();
if (this.onCancelCallback) {
this.onCancelCallback();
}
}
/**
* Remove the last added vertex
*/
undoLastVertex() {
if (!this.isDrawing || this.vertices.length === 0) return;
// Remove last vertex and marker
this.vertices.pop();
const lastMarker = this.markers.pop();
if (lastMarker) {
this.map.removeLayer(lastMarker);
}
// Update polyline
this.updatePolyline();
// Call update callback if available
if (this.onUpdate) {
this.onUpdate();
}
console.log('Removed last vertex, remaining:', this.vertices.length);
}
/**
* Clear all vertices and start over
*/
clearVertices() {
if (!this.isDrawing) return;
// Remove all markers
this.markers.forEach(marker => {
this.map.removeLayer(marker);
});
// Remove polyline
if (this.polyline) {
this.map.removeLayer(this.polyline);
this.polyline = null;
}
// Reset arrays
this.vertices = [];
this.markers = [];
// Call update callback if available
if (this.onUpdate) {
this.onUpdate();
}
console.log('Cleared all vertices');
}
/**
* Cleanup drawing state
*/
cleanup() {
// Remove all markers
this.markers.forEach(marker => {
this.map.removeLayer(marker);
});
// Remove polyline
if (this.polyline) {
this.map.removeLayer(this.polyline);
}
// Reset cursor
this.map.getContainer().style.cursor = '';
// Remove event listeners
this.map.off('click', this.onMapClick);
// Re-enable double-click zoom
this.map.doubleClickZoom.enable();
// Reset state
this.isDrawing = false;
this.vertices = [];
this.markers = [];
this.polyline = null;
this.onFinishCallback = null;
this.onCancelCallback = null;
}
/**
* Get current drawing state
*/
getState() {
return {
isDrawing: this.isDrawing,
vertexCount: this.vertices.length,
canFinish: this.vertices.length >= 3
};
}
/**
* Preview polygon without finishing
*/
showPreview(geojson, color = '#3388ff', opacity = 0.3) {
this.clearPreview();
if (!geojson) return;
try {
const coordinates = geojson.coordinates[0];
const latlngs = coordinates.map(coord => L.latLng(coord[1], coord[0]));
this.previewPolygon = L.polygon(latlngs, {
color: color,
weight: 2,
opacity: 0.8,
fillColor: color,
fillOpacity: opacity,
className: 'cut-preview-polygon'
}).addTo(this.map);
// Add CSS class for opacity control
const pathElement = this.previewPolygon.getElement();
if (pathElement) {
pathElement.classList.add('cut-polygon');
console.log('Added cut-polygon class to preview polygon');
}
console.log('Preview polygon shown with opacity:', opacity);
} catch (error) {
console.error('Error showing preview polygon:', error);
}
}
/**
* Update preview polygon style without recreating it
*/
updatePreview(color = '#3388ff', opacity = 0.3) {
if (this.previewPolygon) {
this.previewPolygon.setStyle({
color: color,
weight: 2,
opacity: 0.8,
fillColor: color,
fillOpacity: opacity
});
// Ensure CSS class is still present
const pathElement = this.previewPolygon.getElement();
if (pathElement) {
pathElement.classList.add('cut-polygon');
}
console.log('Preview polygon style updated with opacity:', opacity);
}
}
clearPreview() {
if (this.previewPolygon) {
this.map.removeLayer(this.previewPolygon);
this.previewPolygon = null;
}
}
/**
* Update drawing style (called from admin cuts manager)
*/
updateDrawingStyle(color = '#3388ff', opacity = 0.3) {
// Update the polyline connecting vertices if it exists
if (this.polyline) {
this.polyline.setStyle({
color: color,
weight: 2,
opacity: 0.8
});
}
// Update preview polygon if it exists
this.updatePreview(color, opacity);
console.log('Cut drawing style updated with color:', color, 'opacity:', opacity);
}
}

View File

@@ -0,0 +1,502 @@
/**
* Cut Manager Module
* Handles cut CRUD operations and display functionality
*/
import { showStatus } from './utils.js';
export class CutManager {
constructor() {
this.cuts = [];
this.currentCut = null;
this.currentCutLayer = null;
this.map = null;
this.isInitialized = false;
// Add support for multiple cuts
this.displayedCuts = new Map(); // Track multiple displayed cuts
this.cutLayers = new Map(); // Track cut layers by ID
}
/**
* Initialize the cut manager
*/
async initialize(map) {
this.map = map;
this.isInitialized = true;
// Load public cuts for display
await this.loadPublicCuts();
console.log('Cut manager initialized');
}
/**
* Load all cuts (admin) or public cuts (users)
*/
async loadCuts(adminMode = false) {
try {
const endpoint = adminMode ? '/api/cuts' : '/api/cuts/public';
const response = await fetch(endpoint, {
credentials: 'include'
});
if (!response.ok) {
throw new Error(`Failed to load cuts: ${response.statusText}`);
}
const data = await response.json();
this.cuts = data.list || [];
console.log(`Loaded ${this.cuts.length} cuts`);
return this.cuts;
} catch (error) {
console.error('Error loading cuts:', error);
showStatus('Failed to load cuts', 'error');
return [];
}
}
/**
* Load public cuts for map display
*/
async loadPublicCuts() {
return await this.loadCuts(false);
}
/**
* Get single cut by ID
*/
async getCut(id) {
try {
const response = await fetch(`/api/cuts/${id}`, {
credentials: 'include'
});
if (!response.ok) {
throw new Error(`Failed to load cut: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Error loading cut:', error);
showStatus('Failed to load cut', 'error');
return null;
}
}
/**
* Create new cut
*/
async createCut(cutData) {
try {
const response = await fetch('/api/cuts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(cutData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `Failed to create cut: ${response.statusText}`);
}
const result = await response.json();
showStatus('Cut created successfully', 'success');
// Reload cuts
await this.loadCuts(true);
return result;
} catch (error) {
console.error('Error creating cut:', error);
showStatus(error.message, 'error');
return null;
}
}
/**
* Update existing cut
*/
async updateCut(id, cutData) {
try {
const response = await fetch(`/api/cuts/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(cutData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `Failed to update cut: ${response.statusText}`);
}
const result = await response.json();
showStatus('Cut updated successfully', 'success');
// Reload cuts
await this.loadCuts(true);
return result;
} catch (error) {
console.error('Error updating cut:', error);
showStatus(error.message, 'error');
return null;
}
}
/**
* Delete cut
*/
async deleteCut(id) {
try {
const response = await fetch(`/api/cuts/${id}`, {
method: 'DELETE',
credentials: 'include'
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `Failed to delete cut: ${response.statusText}`);
}
showStatus('Cut deleted successfully', 'success');
// If this was the currently displayed cut, hide it
if (this.currentCut && this.currentCut.id === id) {
this.hideCut();
}
// Reload cuts
await this.loadCuts(true);
return true;
} catch (error) {
console.error('Error deleting cut:', error);
showStatus(error.message, 'error');
return false;
}
}
/**
* Display a cut on the map (enhanced to support multiple cuts)
*/
/**
* Display a cut on the map (enhanced to support multiple cuts)
*/
displayCut(cutData, autoDisplayed = false) {
if (!this.map) {
console.error('Map not initialized');
return false;
}
// Normalize field names for consistent access
const normalizedCut = {
...cutData,
id: cutData.id || cutData.Id || cutData.ID,
name: cutData.name || cutData.Name,
description: cutData.description || cutData.Description,
color: cutData.color || cutData.Color,
opacity: cutData.opacity || cutData.Opacity,
category: cutData.category || cutData.Category,
geojson: cutData.geojson || cutData.GeoJSON || cutData['GeoJSON Data'],
is_public: cutData.is_public || cutData['Public Visibility'],
is_official: cutData.is_official || cutData['Official Cut'],
autoDisplayed: autoDisplayed // Track if this was auto-displayed
};
// Check if already displayed
if (this.cutLayers.has(normalizedCut.id)) {
console.log(`Cut already displayed: ${normalizedCut.name}`);
return true;
}
if (!normalizedCut.geojson) {
console.error('Cut has no GeoJSON data');
return false;
}
try {
const geojsonData = typeof normalizedCut.geojson === 'string' ?
JSON.parse(normalizedCut.geojson) : normalizedCut.geojson;
const cutLayer = L.geoJSON(geojsonData, {
style: {
color: normalizedCut.color || '#3388ff',
fillColor: normalizedCut.color || '#3388ff',
fillOpacity: parseFloat(normalizedCut.opacity) || 0.3,
weight: 2,
opacity: 1,
className: 'cut-polygon'
}
});
// Add popup with cut info
cutLayer.bindPopup(`
<div class="cut-popup">
<h3>${normalizedCut.name}</h3>
${normalizedCut.description ? `<p>${normalizedCut.description}</p>` : ''}
${normalizedCut.category ? `<p><strong>Category:</strong> ${normalizedCut.category}</p>` : ''}
${normalizedCut.is_official ? '<span class="badge official">Official Cut</span>' : ''}
</div>
`);
cutLayer.addTo(this.map);
// Store in both tracking systems
this.cutLayers.set(normalizedCut.id, cutLayer);
this.displayedCuts.set(normalizedCut.id, normalizedCut);
// Update current cut reference (for legacy compatibility)
this.currentCut = normalizedCut;
this.currentCutLayer = cutLayer;
console.log(`Displayed cut: ${normalizedCut.name} (ID: ${normalizedCut.id})`);
return true;
} catch (error) {
console.error('Error displaying cut:', error);
return false;
}
}
/**
* Hide the currently displayed cut (legacy method - now hides all cuts)
*/
hideCut() {
this.hideAllCuts();
}
/**
* Hide specific cut by ID
*/
hideCutById(cutId) {
// Try different ID formats to handle type mismatches
let layer = this.cutLayers.get(cutId);
let actualKey = cutId;
if (!layer) {
// Try as string
const stringId = String(cutId);
layer = this.cutLayers.get(stringId);
if (layer) actualKey = stringId;
}
if (!layer) {
// Try as number
const numberId = Number(cutId);
if (!isNaN(numberId)) {
layer = this.cutLayers.get(numberId);
if (layer) actualKey = numberId;
}
}
if (layer && this.map) {
this.map.removeLayer(layer);
this.cutLayers.delete(actualKey);
this.displayedCuts.delete(actualKey);
console.log(`Successfully hidden cut ID: ${actualKey} (original: ${cutId})`);
return true;
}
console.warn(`Failed to hide cut ID: ${cutId} - not found in layers`);
return false;
}
/**
* Hide all displayed cuts
*/
hideAllCuts() {
// Hide all cuts using the new system
Array.from(this.cutLayers.keys()).forEach(cutId => {
this.hideCutById(cutId);
});
// Legacy cleanup
if (this.currentCutLayer && this.map) {
this.map.removeLayer(this.currentCutLayer);
this.currentCutLayer = null;
this.currentCut = null;
}
console.log('All cuts hidden');
}
/**
* Toggle cut visibility
*/
toggleCut(cutData) {
if (this.currentCut && this.currentCut.id === cutData.id) {
this.hideCut();
return false; // Hidden
} else {
this.displayCut(cutData);
return true; // Shown
}
}
/**
* Get currently displayed cut
*/
getCurrentCut() {
return this.currentCut;
}
/**
* Check if a cut is currently displayed
*/
isCutDisplayed(cutId) {
// Try different ID types to handle string/number mismatches
const hasInMap = this.displayedCuts.has(cutId);
const hasInMapAsString = this.displayedCuts.has(String(cutId));
const hasInMapAsNumber = this.displayedCuts.has(Number(cutId));
const currentCutMatch = this.currentCut && this.currentCut.id === cutId;
return hasInMap || hasInMapAsString || hasInMapAsNumber || currentCutMatch;
}
/**
* Get all displayed cuts
*/
getDisplayedCuts() {
return Array.from(this.displayedCuts.values());
}
/**
* Get all available cuts
*/
getCuts() {
return this.cuts;
}
/**
* Get cuts by category
*/
getCutsByCategory(category) {
return this.cuts.filter(cut => {
const cutCategory = cut.category || cut.Category || 'Other';
return cutCategory === category;
});
}
/**
* Search cuts by name
*/
searchCuts(query) {
if (!query) return this.cuts;
const searchTerm = query.toLowerCase();
return this.cuts.filter(cut => {
// Handle different possible field names
const name = cut.name || cut.Name || '';
const description = cut.description || cut.Description || '';
return name.toLowerCase().includes(searchTerm) ||
description.toLowerCase().includes(searchTerm);
});
}
/**
* Export cuts as JSON
*/
exportCuts(cutsToExport = null) {
const cuts = cutsToExport || this.cuts;
const exportData = {
version: '1.0',
timestamp: new Date().toISOString(),
cuts: cuts.map(cut => ({
name: cut.name,
description: cut.description,
color: cut.color,
opacity: cut.opacity,
category: cut.category,
is_official: cut.is_official,
geojson: cut.geojson,
bounds: cut.bounds
}))
};
return JSON.stringify(exportData, null, 2);
}
/**
* Validate cut data for import
*/
validateCutData(cutData) {
const errors = [];
if (!cutData.name || typeof cutData.name !== 'string') {
errors.push('Name is required and must be a string');
}
if (!cutData.geojson) {
errors.push('GeoJSON data is required');
} else {
try {
const geojson = JSON.parse(cutData.geojson);
if (!geojson.type || !['Polygon', 'MultiPolygon'].includes(geojson.type)) {
errors.push('GeoJSON must be a Polygon or MultiPolygon');
}
} catch (e) {
errors.push('Invalid GeoJSON format');
}
}
if (cutData.opacity !== undefined) {
const opacity = parseFloat(cutData.opacity);
if (isNaN(opacity) || opacity < 0 || opacity > 1) {
errors.push('Opacity must be a number between 0 and 1');
}
}
return errors;
}
/**
* Get cut statistics
*/
getStatistics() {
const stats = {
total: this.cuts.length,
public: this.cuts.filter(cut => {
const isPublic = cut.is_public || cut['Public Visibility'];
return isPublic === true || isPublic === 1 || isPublic === '1';
}).length,
private: this.cuts.filter(cut => {
const isPublic = cut.is_public || cut['Public Visibility'];
return !(isPublic === true || isPublic === 1 || isPublic === '1');
}).length,
official: this.cuts.filter(cut => {
const isOfficial = cut.is_official || cut['Official Cut'];
return isOfficial === true || isOfficial === 1 || isOfficial === '1';
}).length,
byCategory: {}
};
// Count by category
this.cuts.forEach(cut => {
const category = cut.category || cut.Category || 'Uncategorized';
stats.byCategory[category] = (stats.byCategory[category] || 0) + 1;
});
return stats;
}
/**
* Hide all displayed cuts
*/
/**
* Get displayed cut data by ID
*/
getDisplayedCut(cutId) {
return this.displayedCuts.get(cutId);
}
}
// Create global instance
export const cutManager = new CutManager();

View File

@@ -545,29 +545,67 @@ export async function handleDeleteLocation() {
export function closeAddModal() {
const modal = document.getElementById('add-modal');
modal.classList.add('hidden');
document.getElementById('location-form').reset();
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');
const latInput = document.getElementById('location-lat');
const lngInput = document.getElementById('location-lng');
const geoInput = document.getElementById('geo-location');
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
latInput.value = lat.toFixed(8);
lngInput.value = lng.toFixed(8);
geoInput.value = `${lat.toFixed(8)};${lng.toFixed(8)}`;
// Set coordinates if input fields exist
if (latInput && lngInput) {
latInput.value = lat.toFixed(8);
lngInput.value = 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)}`;
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');

View File

@@ -2,10 +2,12 @@
import { CONFIG, loadDomainConfig } from './config.js';
import { hideLoading, showStatus, setViewportDimensions } from './utils.js';
import { checkAuth } from './auth.js';
import { initializeMap } from './map-manager.js';
import { initializeMap, getMap } from './map-manager.js';
import { loadLocations } from './location-manager.js';
import { setupEventListeners } from './ui-controls.js';
import { UnifiedSearchManager } from './search-manager.js';
import { cutManager } from './cut-manager.js';
import { initializeCutControls } from './cut-controls.js';
// Application state
let refreshInterval = null;
@@ -36,6 +38,12 @@ document.addEventListener('DOMContentLoaded', async () => {
// Then initialize the map
await initializeMap();
// Initialize cut manager after map is ready
await cutManager.initialize(getMap());
// Initialize cut controls for public map
await initializeCutControls();
// Only load locations after map is ready
await loadLocations();

View File

@@ -7,6 +7,11 @@ export let map = null;
export let startLocationMarker = null;
export let isStartLocationVisible = true;
// Function to get the map instance
export function getMap() {
return map;
}
export async function initializeMap() {
try {
// Get start location from PUBLIC endpoint (not admin endpoint)

View File

@@ -98,7 +98,7 @@ export class MapSearch {
*/
selectResult(result) {
if (!map) {
console.error('Map not available');
console.error('Map not initialized');
return;
}
@@ -107,7 +107,7 @@ export class MapSearch {
const lng = parseFloat(result.coordinates?.lng || result.longitude || 0);
if (isNaN(lat) || isNaN(lng)) {
console.error('Invalid coordinates in result:', result);
console.error('Invalid coordinates:', result);
return;
}
@@ -121,34 +121,37 @@ export class MapSearch {
this.tempMarker = L.marker([lat, lng], {
icon: L.divIcon({
className: 'temp-search-marker',
html: '📍',
html: '<div class="marker-pin"></div>',
iconSize: [30, 30],
iconAnchor: [15, 30]
})
}).addTo(map);
// Create popup with add location option
const popupContent = `
<div class="search-result-popup">
<h3>${result.formattedAddress || 'Search Result'}</h3>
<p>${result.fullAddress || ''}</p>
<div class="popup-actions">
<button class="btn btn-success btn-sm" onclick="mapSearchInstance.openAddLocationModal(${lat}, ${lng})">
Add Location Here
</button>
<button class="btn btn-secondary btn-sm" onclick="mapSearchInstance.clearTempMarker()">
✕ Clear
</button>
</div>
</div>
// Create popup content without inline handlers
const popupContent = document.createElement('div');
popupContent.className = 'search-result-popup';
popupContent.innerHTML = `
<h3>${result.formattedAddress || 'Search Result'}</h3>
<p>${result.fullAddress || ''}</p>
<button class="btn btn-primary search-add-location-btn" data-lat="${lat}" data-lng="${lng}">
Add Location Here
</button>
`;
// Bind the popup
this.tempMarker.bindPopup(popupContent).openPopup();
// Auto-clear the marker after 30 seconds
// Add event listener after popup is opened
setTimeout(() => {
this.clearTempMarker();
}, 30000);
const addBtn = document.querySelector('.search-add-location-btn');
if (addBtn) {
addBtn.addEventListener('click', (e) => {
const btnLat = parseFloat(e.target.dataset.lat);
const btnLng = parseFloat(e.target.dataset.lng);
this.openAddLocationModal(btnLat, btnLng);
});
}
}, 100);
}
/**

View File

@@ -492,6 +492,17 @@ export function setupEventListeners() {
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);