PestPedia / templates /index.html
sikeaditya's picture
Update templates/index.html
df6c891 verified
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="{{ current_language }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agricultural Pests & Diseases in India</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root {
--primary-color: #38b000;
--secondary-color: #007bff;
--accent-color: #fb8500;
--light-bg: #f8f9fa;
--dark-text: #212529;
--pest-color: #dc3545;
--disease-color: #6f42c1;
}
body {
background-color: var(--light-bg);
padding: 20px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.page-header {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
padding: 2rem 0;
border-radius: 10px;
margin-bottom: 2rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.language-selector select {
background-color: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.language-selector select option {
background-color: white;
color: var(--dark-text);
}
.filter-buttons {
margin-bottom: 20px;
}
.filter-btn {
margin-right: 10px;
border-radius: 20px;
font-weight: 500;
padding: 8px 16px;
transition: all 0.3s ease;
}
.filter-btn:hover {
transform: translateY(-2px);
}
.filter-btn.active {
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.card {
transition: all 0.3s ease;
height: 100%;
cursor: pointer;
border-radius: 10px;
border: none;
box-shadow: 0 4px 8px rgba(0,0,0,0.05);
overflow: hidden;
}
.card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0,0,0,0.15);
}
.card-img-top {
height: 200px;
object-fit: cover;
transition: all 0.5s ease;
}
.card:hover .card-img-top {
transform: scale(1.05);
}
.card-body {
padding: 1.5rem;
}
.card-title {
font-weight: 600;
margin-bottom: 10px;
}
.loading {
display: none;
text-align: center;
padding: 20px;
}
.modal-body {
max-height: 70vh;
overflow-y: auto;
}
.badge.pest {
background-color: var(--pest-color);
padding: 0.5em 0.8em;
font-size: 0.8rem;
}
.badge.disease {
background-color: var(--disease-color);
padding: 0.5em 0.8em;
font-size: 0.8rem;
}
.modal-content {
border-radius: 15px;
border: none;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.modal-header {
border-bottom: 1px solid rgba(0,0,0,0.1);
background-color: var(--light-bg);
}
.modal-title {
font-weight: 600;
}
.section-title {
margin-top: 1.5rem;
margin-bottom: 0.75rem;
font-weight: 600;
color: var(--dark-text);
border-bottom: 2px solid var(--primary-color);
padding-bottom: 0.5rem;
display: inline-block;
}
#backToTop {
position: fixed;
bottom: 20px;
right: 20px;
display: none;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
text-align: center;
font-size: 20px;
line-height: 50px;
cursor: pointer;
z-index: 99;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
}
.crop-badge {
background-color: #17a2b8;
color: white;
font-size: 0.8rem;
font-weight: normal;
margin-right: 4px;
}
.no-results {
text-align: center;
padding: 40px;
font-size: 1.2rem;
color: #6c757d;
}
/* Spinner */
.spinner {
width: 40px;
height: 40px;
margin: 20px auto;
border: 4px solid rgba(0, 0, 0, 0.1);
border-left-color: var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<header class="page-header text-center mb-4">
<h1 class="display-4">Agricultural Pests & Diseases in India</h1>
<p class="lead">Explore common agricultural threats and learn how to manage them</p>
<!-- Language selector -->
<div class="language-selector mt-3">
<form id="languageForm" class="d-flex justify-content-center align-items-center">
<label for="languageSelect" class="me-2">
<i class="fas fa-language"></i> Language:
</label>
<select id="languageSelect" class="form-select form-select-sm" style="max-width: 200px;">
{% for code, name in languages.items() %}
<option value="{{ code }}" {% if code == current_language %}selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
</form>
</div>
</header>
<!-- Filter buttons -->
<div class="filter-buttons text-center mb-4">
<button class="btn btn-outline-primary filter-btn active" data-filter="all">All Items</button>
<button class="btn btn-outline-danger filter-btn" data-filter="pest">Pests Only</button>
<button class="btn btn-outline-purple filter-btn" data-filter="disease" style="border-color: #6f42c1; color: #6f42c1;">Diseases Only</button>
<div class="input-group mt-3 w-50 mx-auto">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" class="form-control" id="searchInput" placeholder="Search by name or crop...">
</div>
</div>
<div class="row row-cols-1 row-cols-md-3 g-4" id="itemsContainer">
{% for item in pests_diseases %}
<div class="col item-card" data-type="{{ item.type }}" data-name="{{ item.name.lower() }}" data-crop="{{ item.crop.lower() }}">
<div class="card h-100" onclick="showDetails({{ item.id }})">
<div class="position-relative overflow-hidden">
<img data-src="{{ item.image_url }}" class="card-img-top lazy-load" alt="{{ item.name }}">
<span class="position-absolute top-0 end-0 m-2 badge {{ item.type }}">
{{ item.type|capitalize }}
</span>
</div>
<div class="card-body">
<h5 class="card-title">{{ item.name }}</h5>
<p class="card-text">
<strong>Affects:</strong><br>
{% for crop in item.crop.split(', ') %}
<span class="badge crop-badge">{{ crop }}</span>
{% endfor %}
</p>
</div>
</div>
</div>
{% endfor %}
</div>
<div id="noResults" class="no-results" style="display: none;">
<i class="fas fa-search fa-3x mb-3 text-muted"></i>
<p>No matching items found. Try another search term.</p>
</div>
<!-- Loading indicator -->
<div id="loading" class="loading mt-4">
<div class="spinner"></div>
<p class="mt-2">Fetching detailed information...</p>
</div>
<!-- Detail Modal -->
<div class="modal fade" id="detailModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalTitle"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="modalBody">
<!-- Content will be loaded here -->
<div class="text-center p-5" id="modalLoading">
<div class="spinner"></div>
<p class="mt-3">Loading content...</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Back to top button -->
<button id="backToTop" title="Back to top">
<i class="fas fa-chevron-up"></i>
</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Initialize modal
const detailModal = new bootstrap.Modal(document.getElementById('detailModal'));
let loadedDetails = {};
// Language selector
document.getElementById('languageSelect').addEventListener('change', function() {
const selectedLanguage = this.value;
// Show loading spinner
const loadingElement = document.createElement('div');
loadingElement.id = 'pageLoading';
loadingElement.className = 'position-fixed top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-white bg-opacity-75';
loadingElement.style.zIndex = '9999';
loadingElement.innerHTML = '<div class="spinner"></div><p class="mt-3">Changing language...</p>';
document.body.appendChild(loadingElement);
// Send request to change language
fetch('/set_language', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `language=${selectedLanguage}`,
credentials: 'same-origin' // Ensure cookies are sent with the request
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Clear loaded details cache
loadedDetails = {};
// Store language in localStorage as a fallback
localStorage.setItem('preferredLanguage', selectedLanguage);
// Refresh the page
window.location.reload();
}
})
.catch(error => {
console.error('Error:', error);
document.body.removeChild(loadingElement);
alert('Error changing language. Please try again.');
});
});
// Check if we need to restore language preference from localStorage
document.addEventListener('DOMContentLoaded', function() {
const storedLanguage = localStorage.getItem('preferredLanguage');
const currentLanguage = document.getElementById('languageSelect').value;
if (storedLanguage && storedLanguage !== currentLanguage) {
// If the stored language differs from the current one, set it
document.getElementById('languageSelect').value = storedLanguage;
// Trigger the change event
document.getElementById('languageSelect').dispatchEvent(new Event('change'));
}
});
// Lazy load images
document.addEventListener("DOMContentLoaded", function() {
const lazyImages = document.querySelectorAll(".lazy-load");
if ("IntersectionObserver" in window) {
const imageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove("lazy-load");
imageObserver.unobserve(img);
}
});
});
lazyImages.forEach(function(image) {
imageObserver.observe(image);
});
} else {
// Fallback for browsers without intersection observer
lazyImages.forEach(function(img) {
img.src = img.dataset.src;
});
}
});
// Filter functionality
document.querySelectorAll('.filter-btn').forEach(button => {
button.addEventListener('click', function() {
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.remove('active');
});
this.classList.add('active');
const filterValue = this.getAttribute('data-filter');
filterItems(filterValue, document.getElementById('searchInput').value);
});
});
// Search functionality
document.getElementById('searchInput').addEventListener('input', function() {
const activeFilter = document.querySelector('.filter-btn.active').getAttribute('data-filter');
filterItems(activeFilter, this.value);
});
function filterItems(typeFilter, searchTerm) {
searchTerm = searchTerm.toLowerCase();
let visibleCount = 0;
document.querySelectorAll('.item-card').forEach(item => {
const type = item.getAttribute('data-type');
const name = item.getAttribute('data-name');
const crop = item.getAttribute('data-crop');
const typeMatch = typeFilter === 'all' || type === typeFilter;
const searchMatch = !searchTerm ||
name.includes(searchTerm) ||
crop.includes(searchTerm);
if (typeMatch && searchMatch) {
item.style.display = 'block';
visibleCount++;
} else {
item.style.display = 'none';
}
});
// Show/hide no results message
document.getElementById('noResults').style.display = visibleCount === 0 ? 'block' : 'none';
}
// Function to show details
function showDetails(id) {
// Clear previous content and show loading
document.getElementById('modalTitle').textContent = "";
document.getElementById('modalBody').innerHTML = `
<div class="text-center p-5" id="modalLoading">
<div class="spinner"></div>
<p class="mt-3">Loading content...</p>
</div>
`;
// Show modal immediately with loading indicator
detailModal.show();
// Check if we already have the details cached
if (loadedDetails[id]) {
displayDetails(loadedDetails[id]);
return;
}
// Fetch details from the backend
fetch(`/get_details/${id}`, {
credentials: 'same-origin' // Ensure cookies are sent with the request
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
// Cache the result
loadedDetails[id] = data;
displayDetails(data);
})
.catch(error => {
console.error('Error:', error);
document.getElementById('modalBody').innerHTML = `
<div class="alert alert-danger" role="alert">
<i class="fas fa-exclamation-circle"></i>
Error loading information. Please try again.
</div>
`;
});
}
function displayDetails(data) {
// Update modal title
document.getElementById('modalTitle').textContent = data.name;
// Format the details
const details = data.details;
let content = `
<div class="text-center mb-4">
<img src="${data.image_url}" alt="${data.name}" class="img-fluid rounded" style="max-height: 300px;">
<div class="mt-2">
<span class="badge ${data.type} mb-2">${data.type.charAt(0).toUpperCase() + data.type.slice(1)}</span>
<div>
<strong>Affects:</strong>
${data.crop.split(', ').map(crop => `<span class="badge crop-badge">${crop}</span>`).join(' ')}
</div>
</div>
</div>
`;
// Add each section
const sections = [
{key: 'description', icon: 'info-circle', title: details.description.title || 'Description and Identification'},
{key: 'lifecycle', icon: 'sync', title: details.lifecycle.title || 'Lifecycle and Spread'},
{key: 'symptoms', icon: 'exclamation-triangle', title: details.symptoms.title || 'Damage Symptoms'},
{key: 'impact', icon: 'chart-line', title: details.impact.title || 'Economic Impact'},
{key: 'management', icon: 'tasks', title: details.management.title || 'Management and Control'},
{key: 'prevention', icon: 'shield-alt', title: details.prevention.title || 'Prevention'}
];
sections.forEach(section => {
if (details[section.key] && details[section.key].text) {
content += `
<h4 class="section-title"><i class="fas fa-${section.icon} me-2"></i>${section.title}</h4>
<p>${details[section.key].text}</p>
`;
}
});
document.getElementById('modalBody').innerHTML = content;
}
// Back to top button functionality
window.onscroll = function() {
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
document.getElementById("backToTop").style.display = "block";
} else {
document.getElementById("backToTop").style.display = "none";
}
};
document.getElementById("backToTop").addEventListener("click", function() {
window.scrollTo({top: 0, behavior: 'smooth'});
});
</script>
</body>
</html>