sikeaditya commited on
Commit
df6c891
·
verified ·
1 Parent(s): a4f944b

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +528 -508
templates/index.html CHANGED
@@ -1,509 +1,529 @@
1
- <!-- templates/index.html -->
2
- <!DOCTYPE html>
3
- <html lang="{{ current_language }}">
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Agricultural Pests & Diseases in India</title>
8
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
9
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
10
- <style>
11
- :root {
12
- --primary-color: #38b000;
13
- --secondary-color: #007bff;
14
- --accent-color: #fb8500;
15
- --light-bg: #f8f9fa;
16
- --dark-text: #212529;
17
- --pest-color: #dc3545;
18
- --disease-color: #6f42c1;
19
- }
20
-
21
- body {
22
- background-color: var(--light-bg);
23
- padding: 20px;
24
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
25
- }
26
-
27
- .page-header {
28
- background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
29
- color: white;
30
- padding: 2rem 0;
31
- border-radius: 10px;
32
- margin-bottom: 2rem;
33
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
34
- }
35
-
36
- .language-selector select {
37
- background-color: rgba(255, 255, 255, 0.2);
38
- color: white;
39
- border: 1px solid rgba(255, 255, 255, 0.3);
40
- }
41
-
42
- .language-selector select option {
43
- background-color: white;
44
- color: var(--dark-text);
45
- }
46
-
47
- .filter-buttons {
48
- margin-bottom: 20px;
49
- }
50
-
51
- .filter-btn {
52
- margin-right: 10px;
53
- border-radius: 20px;
54
- font-weight: 500;
55
- padding: 8px 16px;
56
- transition: all 0.3s ease;
57
- }
58
-
59
- .filter-btn:hover {
60
- transform: translateY(-2px);
61
- }
62
-
63
- .filter-btn.active {
64
- box-shadow: 0 4px 8px rgba(0,0,0,0.15);
65
- }
66
-
67
- .card {
68
- transition: all 0.3s ease;
69
- height: 100%;
70
- cursor: pointer;
71
- border-radius: 10px;
72
- border: none;
73
- box-shadow: 0 4px 8px rgba(0,0,0,0.05);
74
- overflow: hidden;
75
- }
76
-
77
- .card:hover {
78
- transform: translateY(-8px);
79
- box-shadow: 0 12px 24px rgba(0,0,0,0.15);
80
- }
81
-
82
- .card-img-top {
83
- height: 200px;
84
- object-fit: cover;
85
- transition: all 0.5s ease;
86
- }
87
-
88
- .card:hover .card-img-top {
89
- transform: scale(1.05);
90
- }
91
-
92
- .card-body {
93
- padding: 1.5rem;
94
- }
95
-
96
- .card-title {
97
- font-weight: 600;
98
- margin-bottom: 10px;
99
- }
100
-
101
- .loading {
102
- display: none;
103
- text-align: center;
104
- padding: 20px;
105
- }
106
-
107
- .modal-body {
108
- max-height: 70vh;
109
- overflow-y: auto;
110
- }
111
-
112
- .badge.pest {
113
- background-color: var(--pest-color);
114
- padding: 0.5em 0.8em;
115
- font-size: 0.8rem;
116
- }
117
-
118
- .badge.disease {
119
- background-color: var(--disease-color);
120
- padding: 0.5em 0.8em;
121
- font-size: 0.8rem;
122
- }
123
-
124
- .modal-content {
125
- border-radius: 15px;
126
- border: none;
127
- box-shadow: 0 10px 30px rgba(0,0,0,0.2);
128
- }
129
-
130
- .modal-header {
131
- border-bottom: 1px solid rgba(0,0,0,0.1);
132
- background-color: var(--light-bg);
133
- }
134
-
135
- .modal-title {
136
- font-weight: 600;
137
- }
138
-
139
- .section-title {
140
- margin-top: 1.5rem;
141
- margin-bottom: 0.75rem;
142
- font-weight: 600;
143
- color: var(--dark-text);
144
- border-bottom: 2px solid var(--primary-color);
145
- padding-bottom: 0.5rem;
146
- display: inline-block;
147
- }
148
-
149
- #backToTop {
150
- position: fixed;
151
- bottom: 20px;
152
- right: 20px;
153
- display: none;
154
- background-color: var(--primary-color);
155
- color: white;
156
- border: none;
157
- border-radius: 50%;
158
- width: 50px;
159
- height: 50px;
160
- text-align: center;
161
- font-size: 20px;
162
- line-height: 50px;
163
- cursor: pointer;
164
- z-index: 99;
165
- box-shadow: 0 4px 10px rgba(0,0,0,0.2);
166
- }
167
-
168
- .crop-badge {
169
- background-color: #17a2b8;
170
- color: white;
171
- font-size: 0.8rem;
172
- font-weight: normal;
173
- margin-right: 4px;
174
- }
175
-
176
- .no-results {
177
- text-align: center;
178
- padding: 40px;
179
- font-size: 1.2rem;
180
- color: #6c757d;
181
- }
182
-
183
- /* Spinner */
184
- .spinner {
185
- width: 40px;
186
- height: 40px;
187
- margin: 20px auto;
188
- border: 4px solid rgba(0, 0, 0, 0.1);
189
- border-left-color: var(--primary-color);
190
- border-radius: 50%;
191
- animation: spin 1s linear infinite;
192
- }
193
-
194
- @keyframes spin {
195
- 0% { transform: rotate(0deg); }
196
- 100% { transform: rotate(360deg); }
197
- }
198
- </style>
199
- </head>
200
- <body>
201
- <div class="container">
202
- <header class="page-header text-center mb-4">
203
- <h1 class="display-4">Agricultural Pests & Diseases in India</h1>
204
- <p class="lead">Explore common agricultural threats and learn how to manage them</p>
205
-
206
- <!-- Language selector -->
207
- <div class="language-selector mt-3">
208
- <form id="languageForm" class="d-flex justify-content-center align-items-center">
209
- <label for="languageSelect" class="me-2">
210
- <i class="fas fa-language"></i> Language:
211
- </label>
212
- <select id="languageSelect" class="form-select form-select-sm" style="max-width: 200px;">
213
- {% for code, name in languages.items() %}
214
- <option value="{{ code }}" {% if code == current_language %}selected{% endif %}>{{ name }}</option>
215
- {% endfor %}
216
- </select>
217
- </form>
218
- </div>
219
- </header>
220
-
221
- <!-- Filter buttons -->
222
- <div class="filter-buttons text-center mb-4">
223
- <button class="btn btn-outline-primary filter-btn active" data-filter="all">All Items</button>
224
- <button class="btn btn-outline-danger filter-btn" data-filter="pest">Pests Only</button>
225
- <button class="btn btn-outline-purple filter-btn" data-filter="disease" style="border-color: #6f42c1; color: #6f42c1;">Diseases Only</button>
226
- <div class="input-group mt-3 w-50 mx-auto">
227
- <span class="input-group-text"><i class="fas fa-search"></i></span>
228
- <input type="text" class="form-control" id="searchInput" placeholder="Search by name or crop...">
229
- </div>
230
- </div>
231
-
232
- <div class="row row-cols-1 row-cols-md-3 g-4" id="itemsContainer">
233
- {% for item in pests_diseases %}
234
- <div class="col item-card" data-type="{{ item.type }}" data-name="{{ item.name.lower() }}" data-crop="{{ item.crop.lower() }}">
235
- <div class="card h-100" onclick="showDetails({{ item.id }})">
236
- <div class="position-relative overflow-hidden">
237
- <img data-src="{{ item.image_url }}" class="card-img-top lazy-load" alt="{{ item.name }}">
238
- <span class="position-absolute top-0 end-0 m-2 badge {{ item.type }}">
239
- {{ item.type|capitalize }}
240
- </span>
241
- </div>
242
- <div class="card-body">
243
- <h5 class="card-title">{{ item.name }}</h5>
244
- <p class="card-text">
245
- <strong>Affects:</strong><br>
246
- {% for crop in item.crop.split(', ') %}
247
- <span class="badge crop-badge">{{ crop }}</span>
248
- {% endfor %}
249
- </p>
250
- </div>
251
- </div>
252
- </div>
253
- {% endfor %}
254
- </div>
255
-
256
- <div id="noResults" class="no-results" style="display: none;">
257
- <i class="fas fa-search fa-3x mb-3 text-muted"></i>
258
- <p>No matching items found. Try another search term.</p>
259
- </div>
260
-
261
- <!-- Loading indicator -->
262
- <div id="loading" class="loading mt-4">
263
- <div class="spinner"></div>
264
- <p class="mt-2">Fetching detailed information...</p>
265
- </div>
266
-
267
- <!-- Detail Modal -->
268
- <div class="modal fade" id="detailModal" tabindex="-1" aria-hidden="true">
269
- <div class="modal-dialog modal-lg">
270
- <div class="modal-content">
271
- <div class="modal-header">
272
- <h5 class="modal-title" id="modalTitle"></h5>
273
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
274
- </div>
275
- <div class="modal-body" id="modalBody">
276
- <!-- Content will be loaded here -->
277
- <div class="text-center p-5" id="modalLoading">
278
- <div class="spinner"></div>
279
- <p class="mt-3">Loading content...</p>
280
- </div>
281
- </div>
282
- <div class="modal-footer">
283
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
284
- </div>
285
- </div>
286
- </div>
287
- </div>
288
-
289
- <!-- Back to top button -->
290
- <button id="backToTop" title="Back to top">
291
- <i class="fas fa-chevron-up"></i>
292
- </button>
293
- </div>
294
-
295
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
296
- <script>
297
- // Initialize modal
298
- const detailModal = new bootstrap.Modal(document.getElementById('detailModal'));
299
- let loadedDetails = {};
300
-
301
- // Language selector
302
- document.getElementById('languageSelect').addEventListener('change', function() {
303
- const selectedLanguage = this.value;
304
-
305
- // Show loading spinner
306
- const loadingElement = document.createElement('div');
307
- loadingElement.id = 'pageLoading';
308
- 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';
309
- loadingElement.style.zIndex = '9999';
310
- loadingElement.innerHTML = '<div class="spinner"></div><p class="mt-3">Changing language...</p>';
311
- document.body.appendChild(loadingElement);
312
-
313
- // Send request to change language
314
- fetch('/set_language', {
315
- method: 'POST',
316
- headers: {
317
- 'Content-Type': 'application/x-www-form-urlencoded',
318
- },
319
- body: `language=${selectedLanguage}`
320
- })
321
- .then(response => response.json())
322
- .then(data => {
323
- if (data.success) {
324
- // Clear loaded details cache
325
- loadedDetails = {};
326
- // Refresh the page
327
- window.location.reload();
328
- }
329
- })
330
- .catch(error => {
331
- console.error('Error:', error);
332
- document.body.removeChild(loadingElement);
333
- alert('Error changing language. Please try again.');
334
- });
335
- });
336
-
337
- // Lazy load images
338
- document.addEventListener("DOMContentLoaded", function() {
339
- const lazyImages = document.querySelectorAll(".lazy-load");
340
-
341
- if ("IntersectionObserver" in window) {
342
- const imageObserver = new IntersectionObserver(function(entries, observer) {
343
- entries.forEach(function(entry) {
344
- if (entry.isIntersecting) {
345
- const img = entry.target;
346
- img.src = img.dataset.src;
347
- img.classList.remove("lazy-load");
348
- imageObserver.unobserve(img);
349
- }
350
- });
351
- });
352
-
353
- lazyImages.forEach(function(image) {
354
- imageObserver.observe(image);
355
- });
356
- } else {
357
- // Fallback for browsers without intersection observer
358
- lazyImages.forEach(function(img) {
359
- img.src = img.dataset.src;
360
- });
361
- }
362
- });
363
-
364
- // Filter functionality
365
- document.querySelectorAll('.filter-btn').forEach(button => {
366
- button.addEventListener('click', function() {
367
- document.querySelectorAll('.filter-btn').forEach(btn => {
368
- btn.classList.remove('active');
369
- });
370
-
371
- this.classList.add('active');
372
-
373
- const filterValue = this.getAttribute('data-filter');
374
- filterItems(filterValue, document.getElementById('searchInput').value);
375
- });
376
- });
377
-
378
- // Search functionality
379
- document.getElementById('searchInput').addEventListener('input', function() {
380
- const activeFilter = document.querySelector('.filter-btn.active').getAttribute('data-filter');
381
- filterItems(activeFilter, this.value);
382
- });
383
-
384
- function filterItems(typeFilter, searchTerm) {
385
- searchTerm = searchTerm.toLowerCase();
386
- let visibleCount = 0;
387
-
388
- document.querySelectorAll('.item-card').forEach(item => {
389
- const type = item.getAttribute('data-type');
390
- const name = item.getAttribute('data-name');
391
- const crop = item.getAttribute('data-crop');
392
-
393
- const typeMatch = typeFilter === 'all' || type === typeFilter;
394
- const searchMatch = !searchTerm ||
395
- name.includes(searchTerm) ||
396
- crop.includes(searchTerm);
397
-
398
- if (typeMatch && searchMatch) {
399
- item.style.display = 'block';
400
- visibleCount++;
401
- } else {
402
- item.style.display = 'none';
403
- }
404
- });
405
-
406
- // Show/hide no results message
407
- document.getElementById('noResults').style.display = visibleCount === 0 ? 'block' : 'none';
408
- }
409
-
410
- // Function to show details
411
- function showDetails(id) {
412
- // Clear previous content and show loading
413
- document.getElementById('modalTitle').textContent = "";
414
- document.getElementById('modalBody').innerHTML = `
415
- <div class="text-center p-5" id="modalLoading">
416
- <div class="spinner"></div>
417
- <p class="mt-3">Loading content...</p>
418
- </div>
419
- `;
420
-
421
- // Show modal immediately with loading indicator
422
- detailModal.show();
423
-
424
- // Check if we already have the details cached
425
- if (loadedDetails[id]) {
426
- displayDetails(loadedDetails[id]);
427
- return;
428
- }
429
-
430
- // Fetch details from the backend
431
- fetch(`/get_details/${id}`)
432
- .then(response => {
433
- if (!response.ok) {
434
- throw new Error('Network response was not ok');
435
- }
436
- return response.json();
437
- })
438
- .then(data => {
439
- // Cache the result
440
- loadedDetails[id] = data;
441
- displayDetails(data);
442
- })
443
- .catch(error => {
444
- console.error('Error:', error);
445
- document.getElementById('modalBody').innerHTML = `
446
- <div class="alert alert-danger" role="alert">
447
- <i class="fas fa-exclamation-circle"></i>
448
- Error loading information. Please try again.
449
- </div>
450
- `;
451
- });
452
- }
453
-
454
- function displayDetails(data) {
455
- // Update modal title
456
- document.getElementById('modalTitle').textContent = data.name;
457
-
458
- // Format the details
459
- const details = data.details;
460
- let content = `
461
- <div class="text-center mb-4">
462
- <img src="${data.image_url}" alt="${data.name}" class="img-fluid rounded" style="max-height: 300px;">
463
- <div class="mt-2">
464
- <span class="badge ${data.type} mb-2">${data.type.charAt(0).toUpperCase() + data.type.slice(1)}</span>
465
- <div>
466
- <strong>Affects:</strong>
467
- ${data.crop.split(', ').map(crop => `<span class="badge crop-badge">${crop}</span>`).join(' ')}
468
- </div>
469
- </div>
470
- </div>
471
- `;
472
-
473
- // Add each section
474
- const sections = [
475
- {key: 'description', icon: 'info-circle', title: details.description.title || 'Description and Identification'},
476
- {key: 'lifecycle', icon: 'sync', title: details.lifecycle.title || 'Lifecycle and Spread'},
477
- {key: 'symptoms', icon: 'exclamation-triangle', title: details.symptoms.title || 'Damage Symptoms'},
478
- {key: 'impact', icon: 'chart-line', title: details.impact.title || 'Economic Impact'},
479
- {key: 'management', icon: 'tasks', title: details.management.title || 'Management and Control'},
480
- {key: 'prevention', icon: 'shield-alt', title: details.prevention.title || 'Prevention'}
481
- ];
482
-
483
- sections.forEach(section => {
484
- if (details[section.key] && details[section.key].text) {
485
- content += `
486
- <h4 class="section-title"><i class="fas fa-${section.icon} me-2"></i>${section.title}</h4>
487
- <p>${details[section.key].text}</p>
488
- `;
489
- }
490
- });
491
-
492
- document.getElementById('modalBody').innerHTML = content;
493
- }
494
-
495
- // Back to top button functionality
496
- window.onscroll = function() {
497
- if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
498
- document.getElementById("backToTop").style.display = "block";
499
- } else {
500
- document.getElementById("backToTop").style.display = "none";
501
- }
502
- };
503
-
504
- document.getElementById("backToTop").addEventListener("click", function() {
505
- window.scrollTo({top: 0, behavior: 'smooth'});
506
- });
507
- </script>
508
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
  </html>
 
1
+ <!-- templates/index.html -->
2
+ <!DOCTYPE html>
3
+ <html lang="{{ current_language }}">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Agricultural Pests & Diseases in India</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
10
+ <style>
11
+ :root {
12
+ --primary-color: #38b000;
13
+ --secondary-color: #007bff;
14
+ --accent-color: #fb8500;
15
+ --light-bg: #f8f9fa;
16
+ --dark-text: #212529;
17
+ --pest-color: #dc3545;
18
+ --disease-color: #6f42c1;
19
+ }
20
+
21
+ body {
22
+ background-color: var(--light-bg);
23
+ padding: 20px;
24
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
25
+ }
26
+
27
+ .page-header {
28
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
29
+ color: white;
30
+ padding: 2rem 0;
31
+ border-radius: 10px;
32
+ margin-bottom: 2rem;
33
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
34
+ }
35
+
36
+ .language-selector select {
37
+ background-color: rgba(255, 255, 255, 0.2);
38
+ color: white;
39
+ border: 1px solid rgba(255, 255, 255, 0.3);
40
+ }
41
+
42
+ .language-selector select option {
43
+ background-color: white;
44
+ color: var(--dark-text);
45
+ }
46
+
47
+ .filter-buttons {
48
+ margin-bottom: 20px;
49
+ }
50
+
51
+ .filter-btn {
52
+ margin-right: 10px;
53
+ border-radius: 20px;
54
+ font-weight: 500;
55
+ padding: 8px 16px;
56
+ transition: all 0.3s ease;
57
+ }
58
+
59
+ .filter-btn:hover {
60
+ transform: translateY(-2px);
61
+ }
62
+
63
+ .filter-btn.active {
64
+ box-shadow: 0 4px 8px rgba(0,0,0,0.15);
65
+ }
66
+
67
+ .card {
68
+ transition: all 0.3s ease;
69
+ height: 100%;
70
+ cursor: pointer;
71
+ border-radius: 10px;
72
+ border: none;
73
+ box-shadow: 0 4px 8px rgba(0,0,0,0.05);
74
+ overflow: hidden;
75
+ }
76
+
77
+ .card:hover {
78
+ transform: translateY(-8px);
79
+ box-shadow: 0 12px 24px rgba(0,0,0,0.15);
80
+ }
81
+
82
+ .card-img-top {
83
+ height: 200px;
84
+ object-fit: cover;
85
+ transition: all 0.5s ease;
86
+ }
87
+
88
+ .card:hover .card-img-top {
89
+ transform: scale(1.05);
90
+ }
91
+
92
+ .card-body {
93
+ padding: 1.5rem;
94
+ }
95
+
96
+ .card-title {
97
+ font-weight: 600;
98
+ margin-bottom: 10px;
99
+ }
100
+
101
+ .loading {
102
+ display: none;
103
+ text-align: center;
104
+ padding: 20px;
105
+ }
106
+
107
+ .modal-body {
108
+ max-height: 70vh;
109
+ overflow-y: auto;
110
+ }
111
+
112
+ .badge.pest {
113
+ background-color: var(--pest-color);
114
+ padding: 0.5em 0.8em;
115
+ font-size: 0.8rem;
116
+ }
117
+
118
+ .badge.disease {
119
+ background-color: var(--disease-color);
120
+ padding: 0.5em 0.8em;
121
+ font-size: 0.8rem;
122
+ }
123
+
124
+ .modal-content {
125
+ border-radius: 15px;
126
+ border: none;
127
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
128
+ }
129
+
130
+ .modal-header {
131
+ border-bottom: 1px solid rgba(0,0,0,0.1);
132
+ background-color: var(--light-bg);
133
+ }
134
+
135
+ .modal-title {
136
+ font-weight: 600;
137
+ }
138
+
139
+ .section-title {
140
+ margin-top: 1.5rem;
141
+ margin-bottom: 0.75rem;
142
+ font-weight: 600;
143
+ color: var(--dark-text);
144
+ border-bottom: 2px solid var(--primary-color);
145
+ padding-bottom: 0.5rem;
146
+ display: inline-block;
147
+ }
148
+
149
+ #backToTop {
150
+ position: fixed;
151
+ bottom: 20px;
152
+ right: 20px;
153
+ display: none;
154
+ background-color: var(--primary-color);
155
+ color: white;
156
+ border: none;
157
+ border-radius: 50%;
158
+ width: 50px;
159
+ height: 50px;
160
+ text-align: center;
161
+ font-size: 20px;
162
+ line-height: 50px;
163
+ cursor: pointer;
164
+ z-index: 99;
165
+ box-shadow: 0 4px 10px rgba(0,0,0,0.2);
166
+ }
167
+
168
+ .crop-badge {
169
+ background-color: #17a2b8;
170
+ color: white;
171
+ font-size: 0.8rem;
172
+ font-weight: normal;
173
+ margin-right: 4px;
174
+ }
175
+
176
+ .no-results {
177
+ text-align: center;
178
+ padding: 40px;
179
+ font-size: 1.2rem;
180
+ color: #6c757d;
181
+ }
182
+
183
+ /* Spinner */
184
+ .spinner {
185
+ width: 40px;
186
+ height: 40px;
187
+ margin: 20px auto;
188
+ border: 4px solid rgba(0, 0, 0, 0.1);
189
+ border-left-color: var(--primary-color);
190
+ border-radius: 50%;
191
+ animation: spin 1s linear infinite;
192
+ }
193
+
194
+ @keyframes spin {
195
+ 0% { transform: rotate(0deg); }
196
+ 100% { transform: rotate(360deg); }
197
+ }
198
+ </style>
199
+ </head>
200
+ <body>
201
+ <div class="container">
202
+ <header class="page-header text-center mb-4">
203
+ <h1 class="display-4">Agricultural Pests & Diseases in India</h1>
204
+ <p class="lead">Explore common agricultural threats and learn how to manage them</p>
205
+
206
+ <!-- Language selector -->
207
+ <div class="language-selector mt-3">
208
+ <form id="languageForm" class="d-flex justify-content-center align-items-center">
209
+ <label for="languageSelect" class="me-2">
210
+ <i class="fas fa-language"></i> Language:
211
+ </label>
212
+ <select id="languageSelect" class="form-select form-select-sm" style="max-width: 200px;">
213
+ {% for code, name in languages.items() %}
214
+ <option value="{{ code }}" {% if code == current_language %}selected{% endif %}>{{ name }}</option>
215
+ {% endfor %}
216
+ </select>
217
+ </form>
218
+ </div>
219
+ </header>
220
+
221
+ <!-- Filter buttons -->
222
+ <div class="filter-buttons text-center mb-4">
223
+ <button class="btn btn-outline-primary filter-btn active" data-filter="all">All Items</button>
224
+ <button class="btn btn-outline-danger filter-btn" data-filter="pest">Pests Only</button>
225
+ <button class="btn btn-outline-purple filter-btn" data-filter="disease" style="border-color: #6f42c1; color: #6f42c1;">Diseases Only</button>
226
+ <div class="input-group mt-3 w-50 mx-auto">
227
+ <span class="input-group-text"><i class="fas fa-search"></i></span>
228
+ <input type="text" class="form-control" id="searchInput" placeholder="Search by name or crop...">
229
+ </div>
230
+ </div>
231
+
232
+ <div class="row row-cols-1 row-cols-md-3 g-4" id="itemsContainer">
233
+ {% for item in pests_diseases %}
234
+ <div class="col item-card" data-type="{{ item.type }}" data-name="{{ item.name.lower() }}" data-crop="{{ item.crop.lower() }}">
235
+ <div class="card h-100" onclick="showDetails({{ item.id }})">
236
+ <div class="position-relative overflow-hidden">
237
+ <img data-src="{{ item.image_url }}" class="card-img-top lazy-load" alt="{{ item.name }}">
238
+ <span class="position-absolute top-0 end-0 m-2 badge {{ item.type }}">
239
+ {{ item.type|capitalize }}
240
+ </span>
241
+ </div>
242
+ <div class="card-body">
243
+ <h5 class="card-title">{{ item.name }}</h5>
244
+ <p class="card-text">
245
+ <strong>Affects:</strong><br>
246
+ {% for crop in item.crop.split(', ') %}
247
+ <span class="badge crop-badge">{{ crop }}</span>
248
+ {% endfor %}
249
+ </p>
250
+ </div>
251
+ </div>
252
+ </div>
253
+ {% endfor %}
254
+ </div>
255
+
256
+ <div id="noResults" class="no-results" style="display: none;">
257
+ <i class="fas fa-search fa-3x mb-3 text-muted"></i>
258
+ <p>No matching items found. Try another search term.</p>
259
+ </div>
260
+
261
+ <!-- Loading indicator -->
262
+ <div id="loading" class="loading mt-4">
263
+ <div class="spinner"></div>
264
+ <p class="mt-2">Fetching detailed information...</p>
265
+ </div>
266
+
267
+ <!-- Detail Modal -->
268
+ <div class="modal fade" id="detailModal" tabindex="-1" aria-hidden="true">
269
+ <div class="modal-dialog modal-lg">
270
+ <div class="modal-content">
271
+ <div class="modal-header">
272
+ <h5 class="modal-title" id="modalTitle"></h5>
273
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
274
+ </div>
275
+ <div class="modal-body" id="modalBody">
276
+ <!-- Content will be loaded here -->
277
+ <div class="text-center p-5" id="modalLoading">
278
+ <div class="spinner"></div>
279
+ <p class="mt-3">Loading content...</p>
280
+ </div>
281
+ </div>
282
+ <div class="modal-footer">
283
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ </div>
288
+
289
+ <!-- Back to top button -->
290
+ <button id="backToTop" title="Back to top">
291
+ <i class="fas fa-chevron-up"></i>
292
+ </button>
293
+ </div>
294
+
295
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
296
+ <script>
297
+ // Initialize modal
298
+ const detailModal = new bootstrap.Modal(document.getElementById('detailModal'));
299
+ let loadedDetails = {};
300
+
301
+ // Language selector
302
+ document.getElementById('languageSelect').addEventListener('change', function() {
303
+ const selectedLanguage = this.value;
304
+
305
+ // Show loading spinner
306
+ const loadingElement = document.createElement('div');
307
+ loadingElement.id = 'pageLoading';
308
+ 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';
309
+ loadingElement.style.zIndex = '9999';
310
+ loadingElement.innerHTML = '<div class="spinner"></div><p class="mt-3">Changing language...</p>';
311
+ document.body.appendChild(loadingElement);
312
+
313
+ // Send request to change language
314
+ fetch('/set_language', {
315
+ method: 'POST',
316
+ headers: {
317
+ 'Content-Type': 'application/x-www-form-urlencoded',
318
+ },
319
+ body: `language=${selectedLanguage}`,
320
+ credentials: 'same-origin' // Ensure cookies are sent with the request
321
+ })
322
+ .then(response => response.json())
323
+ .then(data => {
324
+ if (data.success) {
325
+ // Clear loaded details cache
326
+ loadedDetails = {};
327
+
328
+ // Store language in localStorage as a fallback
329
+ localStorage.setItem('preferredLanguage', selectedLanguage);
330
+
331
+ // Refresh the page
332
+ window.location.reload();
333
+ }
334
+ })
335
+ .catch(error => {
336
+ console.error('Error:', error);
337
+ document.body.removeChild(loadingElement);
338
+ alert('Error changing language. Please try again.');
339
+ });
340
+ });
341
+
342
+ // Check if we need to restore language preference from localStorage
343
+ document.addEventListener('DOMContentLoaded', function() {
344
+ const storedLanguage = localStorage.getItem('preferredLanguage');
345
+ const currentLanguage = document.getElementById('languageSelect').value;
346
+
347
+ if (storedLanguage && storedLanguage !== currentLanguage) {
348
+ // If the stored language differs from the current one, set it
349
+ document.getElementById('languageSelect').value = storedLanguage;
350
+ // Trigger the change event
351
+ document.getElementById('languageSelect').dispatchEvent(new Event('change'));
352
+ }
353
+ });
354
+
355
+ // Lazy load images
356
+ document.addEventListener("DOMContentLoaded", function() {
357
+ const lazyImages = document.querySelectorAll(".lazy-load");
358
+
359
+ if ("IntersectionObserver" in window) {
360
+ const imageObserver = new IntersectionObserver(function(entries, observer) {
361
+ entries.forEach(function(entry) {
362
+ if (entry.isIntersecting) {
363
+ const img = entry.target;
364
+ img.src = img.dataset.src;
365
+ img.classList.remove("lazy-load");
366
+ imageObserver.unobserve(img);
367
+ }
368
+ });
369
+ });
370
+
371
+ lazyImages.forEach(function(image) {
372
+ imageObserver.observe(image);
373
+ });
374
+ } else {
375
+ // Fallback for browsers without intersection observer
376
+ lazyImages.forEach(function(img) {
377
+ img.src = img.dataset.src;
378
+ });
379
+ }
380
+ });
381
+
382
+ // Filter functionality
383
+ document.querySelectorAll('.filter-btn').forEach(button => {
384
+ button.addEventListener('click', function() {
385
+ document.querySelectorAll('.filter-btn').forEach(btn => {
386
+ btn.classList.remove('active');
387
+ });
388
+
389
+ this.classList.add('active');
390
+
391
+ const filterValue = this.getAttribute('data-filter');
392
+ filterItems(filterValue, document.getElementById('searchInput').value);
393
+ });
394
+ });
395
+
396
+ // Search functionality
397
+ document.getElementById('searchInput').addEventListener('input', function() {
398
+ const activeFilter = document.querySelector('.filter-btn.active').getAttribute('data-filter');
399
+ filterItems(activeFilter, this.value);
400
+ });
401
+
402
+ function filterItems(typeFilter, searchTerm) {
403
+ searchTerm = searchTerm.toLowerCase();
404
+ let visibleCount = 0;
405
+
406
+ document.querySelectorAll('.item-card').forEach(item => {
407
+ const type = item.getAttribute('data-type');
408
+ const name = item.getAttribute('data-name');
409
+ const crop = item.getAttribute('data-crop');
410
+
411
+ const typeMatch = typeFilter === 'all' || type === typeFilter;
412
+ const searchMatch = !searchTerm ||
413
+ name.includes(searchTerm) ||
414
+ crop.includes(searchTerm);
415
+
416
+ if (typeMatch && searchMatch) {
417
+ item.style.display = 'block';
418
+ visibleCount++;
419
+ } else {
420
+ item.style.display = 'none';
421
+ }
422
+ });
423
+
424
+ // Show/hide no results message
425
+ document.getElementById('noResults').style.display = visibleCount === 0 ? 'block' : 'none';
426
+ }
427
+
428
+ // Function to show details
429
+ function showDetails(id) {
430
+ // Clear previous content and show loading
431
+ document.getElementById('modalTitle').textContent = "";
432
+ document.getElementById('modalBody').innerHTML = `
433
+ <div class="text-center p-5" id="modalLoading">
434
+ <div class="spinner"></div>
435
+ <p class="mt-3">Loading content...</p>
436
+ </div>
437
+ `;
438
+
439
+ // Show modal immediately with loading indicator
440
+ detailModal.show();
441
+
442
+ // Check if we already have the details cached
443
+ if (loadedDetails[id]) {
444
+ displayDetails(loadedDetails[id]);
445
+ return;
446
+ }
447
+
448
+ // Fetch details from the backend
449
+ fetch(`/get_details/${id}`, {
450
+ credentials: 'same-origin' // Ensure cookies are sent with the request
451
+ })
452
+ .then(response => {
453
+ if (!response.ok) {
454
+ throw new Error('Network response was not ok');
455
+ }
456
+ return response.json();
457
+ })
458
+ .then(data => {
459
+ // Cache the result
460
+ loadedDetails[id] = data;
461
+ displayDetails(data);
462
+ })
463
+ .catch(error => {
464
+ console.error('Error:', error);
465
+ document.getElementById('modalBody').innerHTML = `
466
+ <div class="alert alert-danger" role="alert">
467
+ <i class="fas fa-exclamation-circle"></i>
468
+ Error loading information. Please try again.
469
+ </div>
470
+ `;
471
+ });
472
+ }
473
+
474
+ function displayDetails(data) {
475
+ // Update modal title
476
+ document.getElementById('modalTitle').textContent = data.name;
477
+
478
+ // Format the details
479
+ const details = data.details;
480
+ let content = `
481
+ <div class="text-center mb-4">
482
+ <img src="${data.image_url}" alt="${data.name}" class="img-fluid rounded" style="max-height: 300px;">
483
+ <div class="mt-2">
484
+ <span class="badge ${data.type} mb-2">${data.type.charAt(0).toUpperCase() + data.type.slice(1)}</span>
485
+ <div>
486
+ <strong>Affects:</strong>
487
+ ${data.crop.split(', ').map(crop => `<span class="badge crop-badge">${crop}</span>`).join(' ')}
488
+ </div>
489
+ </div>
490
+ </div>
491
+ `;
492
+
493
+ // Add each section
494
+ const sections = [
495
+ {key: 'description', icon: 'info-circle', title: details.description.title || 'Description and Identification'},
496
+ {key: 'lifecycle', icon: 'sync', title: details.lifecycle.title || 'Lifecycle and Spread'},
497
+ {key: 'symptoms', icon: 'exclamation-triangle', title: details.symptoms.title || 'Damage Symptoms'},
498
+ {key: 'impact', icon: 'chart-line', title: details.impact.title || 'Economic Impact'},
499
+ {key: 'management', icon: 'tasks', title: details.management.title || 'Management and Control'},
500
+ {key: 'prevention', icon: 'shield-alt', title: details.prevention.title || 'Prevention'}
501
+ ];
502
+
503
+ sections.forEach(section => {
504
+ if (details[section.key] && details[section.key].text) {
505
+ content += `
506
+ <h4 class="section-title"><i class="fas fa-${section.icon} me-2"></i>${section.title}</h4>
507
+ <p>${details[section.key].text}</p>
508
+ `;
509
+ }
510
+ });
511
+
512
+ document.getElementById('modalBody').innerHTML = content;
513
+ }
514
+
515
+ // Back to top button functionality
516
+ window.onscroll = function() {
517
+ if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
518
+ document.getElementById("backToTop").style.display = "block";
519
+ } else {
520
+ document.getElementById("backToTop").style.display = "none";
521
+ }
522
+ };
523
+
524
+ document.getElementById("backToTop").addEventListener("click", function() {
525
+ window.scrollTo({top: 0, behavior: 'smooth'});
526
+ });
527
+ </script>
528
+ </body>
529
  </html>