All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Integrated MongoDB 7.0 with Mongoose ODM - Added CRUD API endpoints (GET, POST, PUT, DELETE) - Created Family model with validation - Added database seeding script with initial data - Implemented Add Family modal form in frontend - Updated docker-compose with MongoDB service - Updated Helm chart to v0.3.0 with MongoDB StatefulSet - Updated documentation with MongoDB setup instructions
209 lines
5.8 KiB
JavaScript
209 lines
5.8 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);
|
||
}
|
||
});
|
||
}
|
||
|
||
const familyNames = [
|
||
"Kafe (קאפח)", "Shiheb (שחב-שבח)", "Eraki (עראקי)", "Salumi (סלומי-שלומי)",
|
||
"Afgin (עפג'ין)", "Uzeyri (עזירי-עוזרי)"
|
||
// Add more families here if you like
|
||
];
|
||
|
||
// Initialize Fuse.js
|
||
const fuse = new Fuse(familyNames, {
|
||
includeScore: true,
|
||
threshold: 0.4 // Lower = stricter matching
|
||
});
|
||
|
||
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) 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';
|
||
|
||
try {
|
||
const response = await fetch('/api/families', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
family: familyName,
|
||
city: cityName,
|
||
lat: latitude,
|
||
lng: longitude
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (response.ok) {
|
||
messageEl.textContent = '✅ Family added successfully!';
|
||
messageEl.className = 'form-message success';
|
||
|
||
// 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 {
|
||
messageEl.textContent = `❌ Error: ${data.error || data.message}`;
|
||
messageEl.className = 'form-message error';
|
||
}
|
||
} catch (error) {
|
||
console.error('Add family error:', error);
|
||
messageEl.textContent = '❌ Failed to add family. Please try again.';
|
||
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();
|
||
}
|
||
}); |