200 lines
6.1 KiB
JavaScript
200 lines
6.1 KiB
JavaScript
/**
|
|
* Map Search Module
|
|
* Handles address search functionality for the map
|
|
*/
|
|
|
|
import { map } from './map-manager.js';
|
|
import { openAddModal } from './location-manager.js';
|
|
|
|
export class MapSearch {
|
|
constructor() {
|
|
this.searchCache = new Map();
|
|
this.tempMarker = null;
|
|
}
|
|
|
|
/**
|
|
* Search for addresses using the geocoding API
|
|
* @param {string} query - The search query
|
|
* @returns {Promise<Array>} Array of search results
|
|
*/
|
|
async search(query) {
|
|
if (!query || query.trim().length < 2) {
|
|
return [];
|
|
}
|
|
|
|
const trimmedQuery = query.trim();
|
|
|
|
// Check cache first
|
|
if (this.searchCache.has(trimmedQuery)) {
|
|
return this.searchCache.get(trimmedQuery);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/geocode/search?query=${encodeURIComponent(trimmedQuery)}&limit=5`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Search failed: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.error || 'Search failed');
|
|
}
|
|
|
|
const results = data.data || [];
|
|
|
|
// Cache the results
|
|
this.searchCache.set(trimmedQuery, results);
|
|
|
|
// Clean up cache if it gets too large
|
|
if (this.searchCache.size > 100) {
|
|
const firstKey = this.searchCache.keys().next().value;
|
|
this.searchCache.delete(firstKey);
|
|
}
|
|
|
|
return results;
|
|
|
|
} catch (error) {
|
|
console.error('Map search error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create HTML for a search result
|
|
* @param {Object} result - Search result object
|
|
* @returns {HTMLElement} Result element
|
|
*/
|
|
createResultElement(result) {
|
|
const resultEl = document.createElement('div');
|
|
resultEl.className = 'search-result-item search-result-map';
|
|
|
|
// Handle both coordinate formats for compatibility
|
|
const lat = result.coordinates?.lat || result.latitude || 0;
|
|
const lng = result.coordinates?.lng || result.longitude || 0;
|
|
|
|
// Debugging - log result structure if coordinates are missing
|
|
if (!lat && !lng) {
|
|
console.warn('Search result missing coordinates:', result);
|
|
}
|
|
|
|
resultEl.innerHTML = `
|
|
<div class="result-address">${result.formattedAddress || 'Unknown Address'}</div>
|
|
<div class="result-full-address">${result.fullAddress || ''}</div>
|
|
<div class="result-coordinates">${lat.toFixed(6)}, ${lng.toFixed(6)}</div>
|
|
`;
|
|
|
|
resultEl.addEventListener('click', () => {
|
|
this.selectResult(result);
|
|
});
|
|
|
|
return resultEl;
|
|
}
|
|
|
|
/**
|
|
* Handle selection of a search result
|
|
* @param {Object} result - Selected result
|
|
*/
|
|
selectResult(result) {
|
|
if (!map) {
|
|
console.error('Map not initialized');
|
|
return;
|
|
}
|
|
|
|
// Handle both coordinate formats for compatibility
|
|
const lat = parseFloat(result.coordinates?.lat || result.latitude || 0);
|
|
const lng = parseFloat(result.coordinates?.lng || result.longitude || 0);
|
|
|
|
if (isNaN(lat) || isNaN(lng)) {
|
|
console.error('Invalid coordinates:', result);
|
|
return;
|
|
}
|
|
|
|
// Pan and zoom to the location
|
|
map.setView([lat, lng], 16);
|
|
|
|
// Remove any existing temporary marker
|
|
this.clearTempMarker();
|
|
|
|
// Add a temporary marker
|
|
this.tempMarker = L.marker([lat, lng], {
|
|
icon: L.divIcon({
|
|
className: 'temp-search-marker',
|
|
html: '<div class="marker-pin"></div>',
|
|
iconSize: [30, 30],
|
|
iconAnchor: [15, 30]
|
|
})
|
|
}).addTo(map);
|
|
|
|
// 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();
|
|
|
|
// Add event listener after popup is opened
|
|
setTimeout(() => {
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Open the add location modal at specified coordinates
|
|
* @param {number} lat - Latitude
|
|
* @param {number} lng - Longitude
|
|
*/
|
|
openAddLocationModal(lat, lng) {
|
|
this.clearTempMarker();
|
|
|
|
if (typeof openAddModal === 'function') {
|
|
openAddModal(lat, lng);
|
|
} else {
|
|
// Fallback: trigger the add location button click
|
|
const addBtn = document.getElementById('add-location-btn');
|
|
if (addBtn) {
|
|
// Set a temporary flag for the coordinates
|
|
window.tempSearchCoordinates = { lat, lng };
|
|
addBtn.click();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear the temporary search marker
|
|
*/
|
|
clearTempMarker() {
|
|
if (this.tempMarker && map) {
|
|
map.removeLayer(this.tempMarker);
|
|
this.tempMarker = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear the search cache
|
|
*/
|
|
clearCache() {
|
|
this.searchCache.clear();
|
|
}
|
|
}
|
|
|
|
// Create a global instance for use in popup buttons
|
|
window.mapSearchInstance = new MapSearch();
|
|
|
|
export default window.mapSearchInstance;
|