// Location search functionality for the map
class LocationSearch {
constructor(map, apiKey) {
this.map = map;
this.apiKey = apiKey;
this.searchResults = [];
this.searchMarkersLayer = null;
this.currentSearchQuery = '';
this.searchTimeout = null;
this.suggestionsVisible = false;
this.currentSuggestionIndex = -1;
this.initializeSearchBar();
}
initializeSearchBar() {
// Create search toggle button using Leaflet control (positioned below settings button)
const SearchToggleControl = L.Control.extend({
onAdd: function(map) {
const button = L.DomUtil.create('button', 'location-search-toggle');
button.innerHTML = '🔍';
button.style.width = '48px';
button.style.height = '48px';
button.style.border = 'none';
button.style.cursor = 'pointer';
button.style.boxShadow = '0 1px 4px rgba(0,0,0,0.3)';
button.style.backgroundColor = 'white';
button.style.borderRadius = '4px';
button.style.padding = '0';
button.style.fontSize = '18px';
button.style.marginTop = '10px'; // Space below settings button
button.title = 'Search locations';
button.id = 'location-search-toggle';
return button;
}
});
// Add the search toggle control to the map
this.map.addControl(new SearchToggleControl({ position: 'topleft' }));
// Use setTimeout to ensure the DOM element is available
setTimeout(() => {
// Get reference to the created button
const toggleButton = document.getElementById('location-search-toggle');
if (toggleButton) {
// Create inline search bar
this.createInlineSearchBar();
// Store references
this.toggleButton = toggleButton;
this.searchVisible = false;
// Bind events
this.bindSearchEvents();
console.log('LocationSearch: Search button initialized successfully');
} else {
console.error('LocationSearch: Could not find search toggle button');
}
}, 100);
}
createInlineSearchBar() {
// Create inline search bar that appears next to the search button
const searchBar = document.createElement('div');
searchBar.className = 'location-search-bar absolute bg-white border border-gray-300 rounded-lg shadow-lg';
searchBar.id = 'location-search-bar';
searchBar.style.width = '300px';
searchBar.style.padding = '8px';
searchBar.style.display = 'none'; // Start hidden with inline style instead of class
searchBar.style.zIndex = '9999'; // Very high z-index to ensure visibility
searchBar.innerHTML = `
Suggestions
Results
`;
// Add search bar to the map container
this.map.getContainer().appendChild(searchBar);
// Store references
this.searchBar = searchBar;
this.searchInput = document.getElementById('location-search-input');
this.searchButton = document.getElementById('location-search-submit');
this.closeButton = document.getElementById('location-search-close');
this.suggestionsContainer = document.getElementById('location-search-suggestions');
this.suggestionsPanel = document.getElementById('location-search-suggestions-panel');
this.resultsContainer = document.getElementById('location-search-results');
this.resultsPanel = document.getElementById('location-search-results-panel');
// No clear button or default panel in inline mode
this.clearButton = null;
this.defaultPanel = null;
}
bindSearchEvents() {
// Toggle search bar visibility
this.toggleButton.addEventListener('click', (e) => {
console.log('Search button clicked!');
e.preventDefault();
e.stopPropagation();
this.showSearchBar();
});
// Close search bar
this.closeButton.addEventListener('click', () => {
this.hideSearchBar();
});
// Search on button click
this.searchButton.addEventListener('click', () => {
this.performSearch();
});
// Search on Enter key
this.searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
if (this.suggestionsVisible && this.currentSuggestionIndex >= 0) {
this.selectSuggestion(this.currentSuggestionIndex);
} else {
this.performSearch();
}
}
});
// Clear search (no clear button in inline mode, handled by close button)
// Handle real-time suggestions
this.searchInput.addEventListener('input', (e) => {
const query = e.target.value.trim();
if (query.length > 0) {
this.debouncedSuggestionSearch(query);
} else {
this.hideSuggestions();
this.showDefaultState();
}
});
// Handle keyboard navigation for suggestions
this.searchInput.addEventListener('keydown', (e) => {
if (this.suggestionsVisible) {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
this.navigateSuggestions(1);
break;
case 'ArrowUp':
e.preventDefault();
this.navigateSuggestions(-1);
break;
case 'Escape':
this.hideSuggestions();
this.showDefaultState();
break;
}
}
});
// Close sidepanel on Escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.searchVisible) {
this.hideSearchBar();
}
});
// Close search bar when clicking outside
document.addEventListener('click', (e) => {
if (this.searchVisible &&
!e.target.closest('.location-search-bar') &&
!e.target.closest('#location-search-toggle')) {
this.hideSearchBar();
}
});
}
async performSearch() {
const query = this.searchInput.value.trim();
if (!query) return;
this.currentSearchQuery = query;
this.showLoading();
try {
const response = await fetch(`/api/v1/locations?q=${encodeURIComponent(query)}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Search failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
this.displaySearchResults(data);
} catch (error) {
console.error('Location search error:', error);
this.showError('Failed to search locations. Please try again.');
}
}
showLoading() {
// Hide other panels and show results with loading
this.suggestionsPanel.classList.add('hidden');
this.resultsPanel.classList.remove('hidden');
this.resultsContainer.innerHTML = `
Searching for "${this.currentSearchQuery}"...
`;
}
showError(message) {
// Hide other panels and show results with error
this.suggestionsPanel.classList.add('hidden');
this.resultsPanel.classList.remove('hidden');
this.resultsContainer.innerHTML = `
⚠️
Search Failed
${message}
`;
}
displaySearchResults(data) {
// Hide other panels and show results
this.suggestionsPanel.classList.add('hidden');
this.resultsPanel.classList.remove('hidden');
if (!data.locations || data.locations.length === 0) {
this.resultsContainer.innerHTML = `