Add autocomplete functionality for parks: implement URLs, views, and templates for real-time suggestions

This commit is contained in:
pacnpal
2025-02-23 13:07:27 -05:00
parent 8e9b6b6a15
commit 1876af46d9
26 changed files with 862 additions and 191 deletions

View File

@@ -1,44 +1,37 @@
/* Alert Styles */
.alert {
@apply fixed z-50 px-4 py-3 transition-all duration-500 transform rounded-lg shadow-lg right-4 top-4;
animation: slideIn 0.5s ease-out forwards;
padding: 1rem;
margin-bottom: 1rem;
border-radius: 0.375rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
opacity: 1;
transition: opacity 0.3s ease-in-out;
cursor: pointer;
}
.alert-success {
@apply text-white bg-green-500;
background-color: #E8F5E9;
border: 1px solid #A5D6A7;
color: #2E7D32;
}
.alert-error {
@apply text-white bg-red-500;
}
.alert-info {
@apply text-white bg-blue-500;
background-color: #FFEBEE;
border: 1px solid #FFCDD2;
color: #C62828;
}
.alert-warning {
@apply text-white bg-yellow-500;
background-color: #FFF3E0;
border: 1px solid #FFCC80;
color: #EF6C00;
}
/* Animation keyframes */
@keyframes slideIn {
0% {
transform: translateX(100%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
.alert-info {
background-color: #E3F2FD;
border: 1px solid #90CAF9;
color: #1565C0;
}
@keyframes slideOut {
0% {
transform: translateX(0);
opacity: 1;
}
100% {
transform: translateX(100%);
opacity: 0;
}
.alert.fade-out {
opacity: 0;
}

21
static/js/alerts.js Normal file
View File

@@ -0,0 +1,21 @@
document.addEventListener('DOMContentLoaded', () => {
const alerts = document.querySelectorAll('.alert');
alerts.forEach(alert => {
// Auto-hide alerts after 5 seconds
setTimeout(() => {
alert.classList.add('fade-out');
setTimeout(() => {
alert.remove();
}, 300); // Match CSS transition duration
}, 5000);
// Add click-to-dismiss functionality
alert.addEventListener('click', () => {
alert.classList.add('fade-out');
setTimeout(() => {
alert.remove();
}, 300);
});
});
});

View File

@@ -0,0 +1,50 @@
document.addEventListener('DOMContentLoaded', () => {
const countryInput = document.querySelector('[name="country"]');
const regionInput = document.querySelector('[name="region"]');
const cityInput = document.querySelector('[name="city"]');
if (!countryInput || !regionInput || !cityInput) return;
// Update regions when country changes
countryInput.addEventListener('change', () => {
const country = countryInput.value;
if (country) {
updateRegions(country);
// Clear city when country changes
cityInput.innerHTML = '<option value="">Select a city</option>';
}
});
// Update cities when region changes
regionInput.addEventListener('change', () => {
const country = countryInput.value;
const region = regionInput.value;
if (country && region) {
updateCities(country, region);
}
});
function updateRegions(country) {
fetch(`/location/regions/?country=${encodeURIComponent(country)}`)
.then(response => response.json())
.then(data => {
regionInput.innerHTML = '<option value="">Select a region</option>';
data.regions.forEach(region => {
const option = new Option(region, region);
regionInput.add(option);
});
});
}
function updateCities(country, region) {
fetch(`/location/cities/?country=${encodeURIComponent(country)}&region=${encodeURIComponent(region)}`)
.then(response => response.json())
.then(data => {
cityInput.innerHTML = '<option value="">Select a city</option>';
data.cities.forEach(city => {
const option = new Option(city, city);
cityInput.add(option);
});
});
}
});

40
static/js/main.js Normal file
View File

@@ -0,0 +1,40 @@
// Theme Toggle
document.addEventListener('DOMContentLoaded', () => {
const themeToggle = document.getElementById('theme-toggle');
const themeIcon = themeToggle.nextElementSibling.querySelector('i');
// Set initial icon
updateThemeIcon();
themeToggle.addEventListener('change', () => {
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
} else {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
}
updateThemeIcon();
});
function updateThemeIcon() {
const isDark = document.documentElement.classList.contains('dark');
themeIcon.classList.remove('fa-sun', 'fa-moon');
themeIcon.classList.add(isDark ? 'fa-sun' : 'fa-moon');
}
// Mobile Menu Toggle
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
const mobileMenu = document.getElementById('mobileMenu');
const menuIcon = mobileMenuBtn.querySelector('i');
mobileMenu.style.display = 'none';
let isMenuOpen = false;
mobileMenuBtn.addEventListener('click', () => {
isMenuOpen = !isMenuOpen;
mobileMenu.style.display = isMenuOpen ? 'block' : 'none';
menuIcon.classList.remove('fa-bars', 'fa-times');
menuIcon.classList.add(isMenuOpen ? 'fa-times' : 'fa-bars');
});
});