All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
246 lines
7.3 KiB
JavaScript
246 lines
7.3 KiB
JavaScript
let map = L.map('map').setView([15.5527, 48.5164], 6);
|
|
|
|
// Define the two tile layers
|
|
const voyager = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
|
|
attribution: '© OpenStreetMap contributors © CARTO',
|
|
subdomains: 'abcd',
|
|
maxZoom: 19
|
|
});
|
|
|
|
const openStreetMap = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: 'Map data © OpenStreetMap contributors',
|
|
maxZoom: 19
|
|
});
|
|
|
|
// Add the default layer (Voyager)
|
|
voyager.addTo(map);
|
|
|
|
// Group the layers for switching
|
|
const baseMaps = {
|
|
"English Map (CartoDB Voyager)": voyager,
|
|
"Original Map (OpenStreetMap)": openStreetMap
|
|
};
|
|
|
|
// Add the layer control to the map
|
|
L.control.layers(baseMaps).addTo(map);
|
|
|
|
async function searchFamily() {
|
|
const familyName = document.getElementById('searchInput').value.trim();
|
|
if (!familyName) {
|
|
alert('Please enter a family name.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/search?family=${encodeURIComponent(familyName)}`);
|
|
const familyResult = await response.json();
|
|
|
|
if (!familyResult.length) {
|
|
alert('No matching family found.');
|
|
return;
|
|
}
|
|
|
|
clearMarkers();
|
|
|
|
familyResult.forEach(record => {
|
|
L.marker([record.lat, record.lng]).addTo(map)
|
|
.bindPopup(`<strong>${record.family}</strong><br>City: ${record.city}`)
|
|
.openPopup();
|
|
});
|
|
|
|
const first = familyResult[0];
|
|
map.setView([first.lat, first.lng], 7);
|
|
|
|
} catch (error) {
|
|
console.error('Search error:', error);
|
|
alert('Something went wrong while searching. Please try again later.');
|
|
}
|
|
}
|
|
|
|
function clearMarkers() {
|
|
map.eachLayer(layer => {
|
|
if (layer instanceof L.Marker) {
|
|
map.removeLayer(layer);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Autocomplete data - loaded from database
|
|
let familyNames = [];
|
|
let fuse;
|
|
|
|
// Load families from database for autocomplete
|
|
async function loadFamiliesForAutocomplete() {
|
|
try {
|
|
console.log('📚 Loading families from database for autocomplete...');
|
|
const response = await fetch('/api/families');
|
|
const families = await response.json();
|
|
|
|
// Extract unique family names
|
|
familyNames = [...new Set(families.map(f => f.family))];
|
|
console.log('✅ Loaded', familyNames.length, 'family names for autocomplete');
|
|
|
|
// Initialize or update Fuse.js
|
|
fuse = new Fuse(familyNames, {
|
|
includeScore: true,
|
|
threshold: 0.4 // Lower = stricter matching
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Failed to load families for autocomplete:', error);
|
|
}
|
|
}
|
|
|
|
const searchInput = document.getElementById('searchInput');
|
|
const suggestionsBox = document.createElement('div');
|
|
suggestionsBox.classList.add('suggestions');
|
|
searchInput.parentNode.style.position = 'relative'; // Make parent relative
|
|
searchInput.parentNode.appendChild(suggestionsBox);
|
|
|
|
searchInput.addEventListener('input', () => {
|
|
const value = searchInput.value.trim();
|
|
suggestionsBox.innerHTML = '';
|
|
|
|
if (!value || !fuse) return;
|
|
|
|
const results = fuse.search(value);
|
|
|
|
results.forEach(result => {
|
|
const option = document.createElement('div');
|
|
option.textContent = result.item;
|
|
option.onclick = () => {
|
|
searchInput.value = result.item;
|
|
suggestionsBox.innerHTML = '';
|
|
};
|
|
suggestionsBox.appendChild(option);
|
|
});
|
|
});
|
|
|
|
L.control.logo = function (opts) {
|
|
return new L.Control.Logo(opts);
|
|
};
|
|
|
|
L.Control.Logo = L.Control.extend({
|
|
onAdd: function () {
|
|
const div = L.DomUtil.create('div', 'custom-logo');
|
|
div.innerHTML = `
|
|
<img src="logo.png" alt="Logo" style="height: 40px; vertical-align: middle;">
|
|
<span style="margin-left: 8px; font-weight: bold; color: #333;">Shevach</span>
|
|
`;
|
|
return div;
|
|
},
|
|
|
|
onRemove: function () {
|
|
// Nothing to clean up
|
|
}
|
|
});
|
|
|
|
L.control.logo({ position: 'bottomleft' }).addTo(map);
|
|
|
|
// Add Family Form Functions
|
|
function toggleAddFamilyForm() {
|
|
const modal = document.getElementById('addFamilyModal');
|
|
const form = document.getElementById('addFamilyForm');
|
|
const message = document.getElementById('formMessage');
|
|
|
|
if (modal.style.display === 'block') {
|
|
modal.style.display = 'none';
|
|
form.reset();
|
|
message.textContent = '';
|
|
message.className = 'form-message';
|
|
} else {
|
|
modal.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
async function addFamily(event) {
|
|
event.preventDefault();
|
|
|
|
const familyName = document.getElementById('familyName').value.trim();
|
|
const cityName = document.getElementById('cityName').value.trim();
|
|
const latitude = parseFloat(document.getElementById('latitude').value);
|
|
const longitude = parseFloat(document.getElementById('longitude').value);
|
|
|
|
const messageEl = document.getElementById('formMessage');
|
|
messageEl.textContent = 'Adding family...';
|
|
messageEl.className = 'form-message info';
|
|
|
|
console.log('📝 Adding family:', { familyName, cityName, latitude, longitude });
|
|
|
|
try {
|
|
console.log('🌐 Sending POST request to /api/families');
|
|
const response = await fetch('/api/families', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
family: familyName,
|
|
city: cityName,
|
|
lat: latitude,
|
|
lng: longitude
|
|
})
|
|
});
|
|
|
|
console.log('📡 Response status:', response.status, response.statusText);
|
|
console.log('📡 Response headers:', Object.fromEntries(response.headers.entries()));
|
|
|
|
const contentType = response.headers.get('content-type');
|
|
let data;
|
|
|
|
if (contentType && contentType.includes('application/json')) {
|
|
data = await response.json();
|
|
console.log('📦 Response data:', data);
|
|
} else {
|
|
const text = await response.text();
|
|
console.error('⚠️ Non-JSON response:', text);
|
|
data = { error: 'Server returned non-JSON response', details: text.substring(0, 200) };
|
|
}
|
|
|
|
if (response.ok) {
|
|
console.log('✅ Family added successfully!');
|
|
messageEl.textContent = '✅ Family added successfully!';
|
|
messageEl.className = 'form-message success';
|
|
|
|
// Reload autocomplete with updated family list
|
|
await loadFamiliesForAutocomplete();
|
|
|
|
// Add marker to map
|
|
L.marker([latitude, longitude]).addTo(map)
|
|
.bindPopup(`<strong>${familyName}</strong><br>City: ${cityName}`)
|
|
.openPopup();
|
|
|
|
map.setView([latitude, longitude], 10);
|
|
|
|
// Reset form after 2 seconds
|
|
setTimeout(() => {
|
|
toggleAddFamilyForm();
|
|
}, 2000);
|
|
} else {
|
|
console.error('❌ Server error:', data);
|
|
messageEl.textContent = `❌ Error (${response.status}): ${data.error || data.message || 'Unknown error'}`;
|
|
messageEl.className = 'form-message error';
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ Add family error:', error);
|
|
messageEl.textContent = `❌ Failed: ${error.message || 'Network error'}`;
|
|
messageEl.className = 'form-message error';
|
|
}
|
|
}
|
|
|
|
// Close modal when clicking outside
|
|
window.onclick = function(event) {
|
|
const modal = document.getElementById('addFamilyModal');
|
|
if (event.target === modal) {
|
|
toggleAddFamilyForm();
|
|
}
|
|
}
|
|
|
|
// Allow Enter key to trigger search
|
|
document.getElementById('searchInput').addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') {
|
|
searchFamily();
|
|
}
|
|
});
|
|
|
|
// Load families for autocomplete when page loads
|
|
loadFamiliesForAutocomplete(); |