moebyx/denguewatch-ncr-alert
0
1// Load external scripts dynamically2function loadScript(src) {3 return new Promise((resolve, reject) => {4 const script = document.createElement('script');5 script.src = src;6 script.onload = resolve;7 script.onerror = reject;8 document.head.appendChild(script);9 });10}11// Toggle mobile menu12function toggleMobileMenu() {13 const mobileMenu = document.querySelector('.mobile-menu');14 if (mobileMenu.classList.contains('hidden')) {15 mobileMenu.classList.remove('hidden');16 mobileMenu.classList.add('flex');17 } else {18 mobileMenu.classList.add('hidden');19 mobileMenu.classList.remove('flex');20 }21}22 23// Close mobile menu when clicking outside24function closeMobileMenuOnOutsideClick(event) {25 const mobileMenu = document.querySelector('.mobile-menu');26 const hamburgerButton = document.querySelector('.mobile-menu-button');27 28 if (!mobileMenu.contains(event.target) && 29 !hamburgerButton.contains(event.target) &&30 !mobileMenu.classList.contains('hidden')) {31 mobileMenu.classList.add('hidden');32 mobileMenu.classList.remove('flex');33 }34}35// Check authentication on all pages except login36if (!window.location.pathname.includes('login.html') && !sessionStorage.getItem('authenticated')) {37 window.location.href = 'login.html';38}39 40// Main initialization function41async function initializeApp() {42// Load required scripts based on current page43 await Promise.all([44 loadScript('https://cdn.jsdelivr.net/npm/chart.js'),45 loadScript('https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js'),46 loadScript('https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js'),47 document.location.pathname.includes('admin.html') && 48 loadScript('https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js')49 ]);50 51 feather.replace();52 53 // Initialize mobile menu54 const hamburgerButtons = document.querySelectorAll('.mobile-menu-button');55 if (hamburgerButtons.length > 0) {56 hamburgerButtons.forEach(button => {57 button.addEventListener('click', toggleMobileMenu);58 });59 document.addEventListener('click', closeMobileMenuOnOutsideClick);60 }61// Initialize Vanta.js globe if element exists62 if (document.getElementById('vanta-globe')) {63 VANTA.GLOBE({64 el: "#vanta-globe",65 mouseControls: true,66 touchControls: true,67 gyroControls: false,68 minHeight: 200.00,69 minWidth: 200.00,70 scale: 1.00,71 scaleMobile: 1.00,72 color: 0x3b82f6,73 backgroundColor: 0x1e3a8a74 });75 }76 77 // Initialize charts if element exists78 if (document.getElementById('riskChart')) {79 const riskCtx = document.getElementById('riskChart').getContext('2d');80 81 // Count risks dynamically from paths82 const paths = document.querySelectorAll('.risk-path');83 let riskCounts = { Low: 0, Moderate: 0, High: 0 , VeryHigh: 0};84 85 paths.forEach(path => {86 const risk = path.dataset.risk; // 'Low', 'Moderate', 'High', 'VeryHigh'87 if (riskCounts[risk] !== undefined) {88 riskCounts[risk]++;89 }90 });91 const riskChart = new Chart(riskCtx, {92 type: 'doughnut',93 data: {94 labels: ['Low Risk', 'Moderate Risk', 'High Risk', 'Very High Risk'],95 datasets: [{96 data: [97 riskCounts.Low,98 riskCounts.Moderate,99 riskCounts.High,100 riskCounts.VeryHigh101 ],102 backgroundColor: ['#4ade80', '#fbbf24', '#f87171','#dc2626'],103 borderWidth: 0104 }]105 },106 options: {107 responsive: true, // Make chart responsive108 maintainAspectRatio: false, // Let container size dictate chart size109 cutout: '70%',110 plugins: {111 legend: {112 position: 'bottom'113 }114 }115 }116 });117 118 // Animate elements119 anime({120 targets: '.risk-high',121 scale: [1, 1.1, 1],122 duration: 1500,123 loop: true,124 easing: 'easeInOutSine'125 });126 }127 128 // Admin page functionality129 if (document.getElementById('dropzone')) {130 const dropzone = document.getElementById('dropzone');131 const fileInput = document.getElementById('csv-upload');132 const uploadArea = document.getElementById('upload-area');133 const processingArea = document.getElementById('processing-area');134 const successMessage = document.getElementById('success-message');135 const previewTable = document.getElementById('preview-table');136 const submitBtn = document.getElementById('submit-btn');137 138 // Handle drag and drop139 ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {140 dropzone.addEventListener(eventName, preventDefaults, false);141 });142 143 function preventDefaults(e) {144 e.preventDefault();145 e.stopPropagation();146 }147 148 ['dragenter', 'dragover'].forEach(eventName => {149 dropzone.addEventListener(eventName, highlight, false);150 });151 152 ['dragleave', 'drop'].forEach(eventName => {153 dropzone.addEventListener(eventName, unhighlight, false);154 });155 156 function highlight() {157 dropzone.classList.add('border-blue-500');158 }159 160 function unhighlight() {161 dropzone.classList.remove('border-blue-500');162 }163 164 // Handle dropped files165 dropzone.addEventListener('drop', handleDrop, false);166 167 function handleDrop(e) {168 const dt = e.dataTransfer;169 const files = dt.files;170 handleFiles(files);171 }172 173 // Handle selected files174 fileInput.addEventListener('change', function() {175 handleFiles(this.files);176 });177 178 function handleFiles(files) {179 if (files.length && files[0].type === 'text/csv') {180 uploadArea.classList.add('hidden');181 processingArea.classList.remove('hidden');182 183 // Parse CSV184 Papa.parse(files[0], {185 header: true,186 complete: function(results) {187 processingArea.classList.add('hidden');188 successMessage.classList.remove('hidden');189 190 // Update preview table191 updatePreviewTable(results.data);192 193 // Reset after 3 seconds194 setTimeout(() => {195 successMessage.classList.add('hidden');196 uploadArea.classList.remove('hidden');197 }, 3000);198 },199 error: function(error) {200 console.error('Error parsing CSV:', error);201 processingArea.classList.add('hidden');202 uploadArea.classList.remove('hidden');203 alert('Error parsing CSV file. Please check the format.');204 }205 });206 } else {207 alert('Please upload a valid CSV file.');208 }209 }210 211 function updatePreviewTable(data) {212 if (data && data.length > 0) {213 previewTable.innerHTML = '';214 215 data.slice(0, 5).forEach(row => { // Show first 5 rows only216 const tr = document.createElement('tr');217 218 ['Region', 'Cases', 'Date', 'Risk Level'].forEach(col => {219 const td = document.createElement('td');220 td.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500';221 td.textContent = row[col] || 'N/A';222 tr.appendChild(td);223 });224 225 previewTable.appendChild(tr);226 });227 228 if (data.length > 5) {229 const tr = document.createElement('tr');230 const td = document.createElement('td');231 td.colSpan = 4;232 td.className = 'px-6 py-4 text-center text-sm text-gray-500';233 td.textContent = `+ ${data.length - 5} more rows not shown`;234 tr.appendChild(td);235 previewTable.appendChild(tr);236 }237 }238 }239 240 // Submit button action241 submitBtn.addEventListener('click', function() {242 submitBtn.disabled = true;243 submitBtn.innerHTML = `244 <span class="flex items-center justify-center w-full">245 <i data-feather="loader" class="animate-spin w-4 h-4 mr-2"></i>246 Updating...247 </span>248 `;249 feather.replace();250 251 252 // Simulate API call253 setTimeout(() => {254 submitBtn.disabled = false;255 submitBtn.textContent = 'Update Dashboard Data';256 257 // Create overlay div258 const overlay = document.createElement('div');259 overlay.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';260 overlay.innerHTML = `261 <div class="bg-white p-6 rounded-lg shadow-lg text-center animate-fade-in">262 <p class="text-gray-800 font-medium"><strong>Dashboard data has been updated successfully!</strong></p>263 </div>264 `;265 document.body.appendChild(overlay);266 267 setTimeout(() => {268 overlay.remove();269 }, 1500);270 }, 2000);271 });272 }273 274 // Alerts page weather functionality275 if (document.getElementById('location-select')) {276 const locationSelect = document.getElementById("location-select");277 278 locationSelect.addEventListener("change", (e) => {279 // Remove ",PH" since WeatherAPI already recognizes Philippine cities280 const city = e.target.value.replace(",PH", "");281 const displayName = e.target.options[e.target.selectedIndex].textContent;282 const headingSpan = document.getElementById('weather-city');283 if (headingSpan) headingSpan.textContent = displayName;284 updateWeather(city);285 });286 287 // Initial load (default Manila)288 const initialDisplayName = locationSelect.options[locationSelect.selectedIndex].textContent;289 const headingSpan = document.getElementById('weather-city');290 if (headingSpan) headingSpan.textContent = initialDisplayName;291 updateWeather("Manila");292 293 // Update every 30 minutes294 setInterval(() => {295 const city = document.getElementById("location-select").value.replace(",PH", "");296 updateWeather(city);297 }, 30 * 60 * 1000);298 299 // Add some styling for weather blocks300 const style = document.createElement('style');301 style.textContent = `302 #past-week-weather > div, #forecast-weather > div {303 min-width: 100px;304 }305 #past-week-weather > div:hover, #forecast-weather > div:hover {306 background-color: #f8fafc;307 transform: scale(1.05);308 transition: all 0.2s ease;309 }310 `;311 document.head.appendChild(style);312 }313 // Map hover card for NCR map314 const mapContainer = document.getElementById('map-container');315 const ncrMap = document.getElementById('ncr-map');316 317 if (mapContainer && ncrMap) {318 // Create hover card319 const mapCard = document.createElement('div');320 mapCard.id = 'map-hover-card';321 mapCard.style.position = 'absolute';322 mapCard.style.pointerEvents = 'none';323 mapCard.style.opacity = '0';324 mapCard.style.transform = 'translateY(10px)';325 mapCard.style.transition = 'opacity 0.2s ease, transform 0.2s ease';326 mapCard.style.background = 'white';327 mapCard.style.borderRadius = '12px';328 mapCard.style.boxShadow = '0 12px 28px rgba(0,0,0,0.25)';329 mapCard.style.padding = '12px 16px';330 mapCard.style.fontSize = '14px';331 mapCard.style.zIndex = '50';332 333 mapContainer.appendChild(mapCard);334 335 const paths = ncrMap.querySelectorAll('path');336 337 paths.forEach(path => {338 let originalNextSibling = null;339 340 path.addEventListener('mouseenter', () => {341 // Bring hovered path to top342 originalNextSibling = path.nextSibling;343 ncrMap.appendChild(path);344 345 // Card content (uses SVG data attributes)346 const name = path.getAttribute('data-name') || 'Unknown Area';347 const risk = path.getAttribute('data-risk') || 'Low';348 const cases = path.getAttribute('data-cases') || 'N/A';349 350 mapCard.innerHTML = `351 <div style="font-weight:600;">${name}</div>352 <div style="font-size:12px; margin-top:4px;">353 Risk Level: <strong>${risk}</strong><br>354 Cases: ${cases}355 </div>356 `;357 358 mapCard.style.opacity = '1';359 mapCard.style.transform = 'translateY(0)';360 });361 362 path.addEventListener('mousemove', (e) => {363 const rect = mapContainer.getBoundingClientRect();364 mapCard.style.left = `${e.clientX - rect.left + 16}px`;365 mapCard.style.top = `${e.clientY - rect.top + 16}px`;366 });367 368 path.addEventListener('mouseleave', () => {369 // Restore original SVG order370 if (originalNextSibling) {371 ncrMap.insertBefore(path, originalNextSibling);372 }373 374 // Hide card375 mapCard.style.opacity = '0';376 mapCard.style.transform = 'translateY(10px)';377 });378 });379 }380}381 382// Weather update function383function updateWeather(city = "Manila") {384 const apiKey = "003f133077684b34a7493149260701"; // Replace with your WeatherAPI key385 fetch(`https://api.weatherapi.com/v1/forecast.json?key=${apiKey}&q=${encodeURIComponent(city)}&days=14`)386 .then(response => response.json())387 .then(data => {388 // Current weather389 const temperature = Math.round(data.current.temp_c);390 const rainProbability = data.forecast.forecastday[0].day.daily_chance_of_rain + "%";391 392 // Risk calculation393 let dengueRisk = "Low";394 let advisory = "Normal weather conditions. Maintain regular prevention measures.";395 const rainChance = parseInt(data.forecast.forecastday[0].day.daily_chance_of_rain);396 397 if (rainChance > 70) {398 dengueRisk = "Critical";399 advisory = "Very high rain probability increases standing water. Expect increased mosquito activity.";400 } else if (rainChance > 50) {401 dengueRisk = "High";402 advisory = "Frequent rain expected. Check and eliminate standing water around your area.";403 } else if (rainChance > 30) {404 dengueRisk = "Medium";405 advisory = "Moderate rain probability. Stay alert and continue preventive actions.";406 }407 408 // Update current weather UI409 document.getElementById('rain-probability').textContent = rainProbability;410 document.getElementById('temperature').textContent = `${temperature}°C`;411 document.getElementById('dengue-risk').textContent = dengueRisk;412 document.getElementById('weather-advisory').textContent = advisory;413 document.getElementById('weather-update-time').textContent = new Date().toLocaleTimeString();414 415 // Generate past week (mock data since API doesn't provide historical)416 const pastWeekContainer = document.getElementById('past-week-weather');417 pastWeekContainer.innerHTML = '';418 for (let i = 6; i >= 0; i--) {419 const date = new Date();420 date.setDate(date.getDate() - i);421 const day = date.toLocaleDateString('en-US', { weekday: 'short' });422 const rainChance = Math.floor(Math.random() * 100);423 424 pastWeekContainer.innerHTML += `425 <div class="text-center p-2 border rounded-lg">426 <div class="font-medium">${day}</div>427 <div class="text-sm text-gray-500">${date.getDate()}/${date.getMonth()+1}</div>428 <div class="text-blue-500 font-medium">${rainChance}%</div>429 <div class="text-sm">${rainChance > 50 ? '🌧️' : rainChance > 30 ? '⛅' : '☀️'}</div>430 </div>431 `;432 }433 434 // Generate 14-day forecast435 const forecastContainer = document.getElementById('forecast-weather');436 forecastContainer.innerHTML = '';437 for (let i = 0; i < 14; i++) {438 const forecast = data.forecast.forecastday[i].day;439 const date = new Date(data.forecast.forecastday[i].date);440 const day = date.toLocaleDateString('en-US', { weekday: 'short' });441 442 forecastContainer.innerHTML += `443 <div class="text-center p-2 border rounded-lg">444 <div class="font-medium">${day}</div>445 <div class="text-sm text-gray-500">${date.getDate()}/${date.getMonth()+1}</div>446 <div class="text-blue-500 font-medium">${forecast.daily_chance_of_rain}%</div>447 <div class="text-sm">${forecast.daily_chance_of_rain > 50 ? '🌧️' : forecast.daily_chance_of_rain > 30 ? '⛅' : '☀️'}</div>448 </div>449 `;450 }451 })452 .catch(error => {453 console.error("Error fetching weather:", error);454 document.getElementById('rain-probability').textContent = "N/A";455 document.getElementById('temperature').textContent = "N/A";456 document.getElementById('dengue-risk').textContent = "Unknown";457 document.getElementById('weather-advisory').textContent = "Weather data unavailable.";458 459 // Show error placeholders for forecast460 document.getElementById('past-week-weather').innerHTML = '<div class="text-center text-gray-500">Weather data unavailable</div>';461 document.getElementById('forecast-weather').innerHTML = '<div class="text-center text-gray-500">Weather data unavailable</div>';462 });463}464// Alert data and recommendations (would normally come from API)465const alertData = {466 "1": {467 title: "Quezon City Outbreak",468 location: "Quezon City",469 cases: "247 this week",470 increase: "120%",471 assessment: "This area has exceeded the epidemic threshold with a rapid increase in cases.",472 updated: "Today, 10:45 AM",473 status: "CRITICAL ALERT",474 risk: "high",475 recommendedActions: [476 "Conduct immediate fogging operations",477 "Deploy additional medical teams",478 "Issue public health advisory"479 ]480 },481 "2": {482 title: "Manila Cluster",483 location: "Manila",484 cases: "87 this week",485 increase: "45%",486 assessment: "This area is approaching the epidemic threshold with moderate increase in cases.",487 updated: "Today, 8:30 AM",488 status: "MODERATE ALERT",489 risk: "moderate",490 recommendedActions: [491 "Increase public awareness campaigns",492 "Schedule neighborhood cleanups",493 "Monitor high-risk areas daily"494 ]495 },496 "3": {497 title: "Makati Monitoring",498 location: "Makati",499 cases: "23 this week",500 increase: "15%",501 assessment: "This area is being monitored for potential outbreak.",502 updated: "Yesterday, 4:15 PM",503 status: "LOW ALERT",504 risk: "low",505 recommendedActions: [506 "Continue routine inspections",507 "Educate residents on prevention",508 "Maintain mosquito control measures"509 ]510 }511};512// City to Barangay mapping (to be populated with actual barangays for each city)513const cityBarangays = {514 "Manila": [515 "Binondo",516 "Ermita",517 "Intramuros",518 "Malate",519 "Paco",520 "Pandacan",521 "Port Area",522 "Quiapo",523 "Sampaloc",524 "San Andres",525 "San Miguel",526 "San Nicolas",527 "Santa Ana",528 "Santa Cruz",529 "Santa Mesa",530 "Tondo"531 ],532"Quezon City": [],533 "Caloocan": [],534 "Las Piñas": [],535 "Makati": [],536 "Malabon": [],537 "Mandaluyong": [],538 "Marikina": [],539 "Muntinlupa": [],540 "Navotas": [],541 "Parañaque": [],542 "Pasay": [],543 "Pasig": [],544 "San Juan": [],545 "Taguig": [],546 "Valenzuela": [],547 "Pateros": []548};549 550// City-specific alert recommendations551const cityRecommendations = {552"Manila": {553 assessment: "This area has exceeded the epidemic threshold with a rapid increase in cases.",554 recommendedActions: [555 "Conduct immediate fogging operations",556 "Deploy additional medical teams",557 "Issue public health advisory"558 ]559 },560 "Quezon City": {561 assessment: "This area is approaching the epidemic threshold with moderate increase in cases.",562 recommendedActions: [563 "Increase public awareness campaigns",564 "Schedule neighborhood cleanups",565 "Monitor high-risk areas daily"566 ]567 },568 // Add all other cities with their default recommendations569 "Caloocan": {570 assessment: "This area is being monitored for potential outbreak.",571 recommendedActions: [572 "Conduct immediate fogging operations",573 "Deploy additional medical teams",574 "Issue public health advisory"575 ]576 },577 // ... other cities578 "Default": {579 assessment: "This area is being monitored for potential outbreak.",580 recommendedActions: [581 "Continue routine inspections",582 "Educate residents on prevention",583 "Maintain mosquito control measures"584 ]585 }586};587 588function getCityRecommendations(city) {589 return cityRecommendations[city] || cityRecommendations["Default"];590}591 592function updateCityRecommendations(city, assessment, actions) {593 if (!cityRecommendations[city]) {594 cityRecommendations[city] = {};595 }596 cityRecommendations[city].assessment = assessment;597 cityRecommendations[city].recommendedActions = actions;598 599 // Also update any active alerts for this city600 Object.values(alertData).forEach(alert => {601 if (alert.location === city) {602 alert.assessment = assessment;603 alert.recommendedActions = actions;604 }605 });606}607// Filter alerts by risk level608function filterAlerts(riskLevel) {609 const alerts = document.querySelectorAll('.alert-card');610 alerts.forEach(alert => {611 if (riskLevel === 'all' || alert.dataset.risk === riskLevel) {612 alert.style.display = 'block';613 } else {614 alert.style.display = 'none';615 }616 });617}618 619// Initialize filter functionality620function initializeFilter() {621 const filterBtn = document.getElementById('filter-btn');622 const riskSelect = document.querySelector('select[name="risk-level"]');623 624 if (filterBtn && riskSelect) {625 filterBtn.addEventListener('click', () => {626 const riskLevel = riskSelect.value;627 filterAlerts(riskLevel);628 });629 }630 631 // Also filter when risk select changes632 if (riskSelect) {633 riskSelect.addEventListener('change', (e) => {634 filterAlerts(e.target.value);635 });636 }637}638// Handle alert details modal639function setupAlertModal() {640 const modal = document.getElementById('alert-modal');641 const closeBtn = document.getElementById('close-modal');642 const detailBtns = document.querySelectorAll('.alert-details-btn');643 644 detailBtns.forEach(btn => {645 btn.addEventListener('click', () => {646 const alertId = btn.getAttribute('data-alert-id');647 const alert = alertData[alertId];648 649 if (alert) {650 document.getElementById('alert-modal-title').textContent = alert.title;651 document.getElementById('alert-location').textContent = alert.location;652 document.getElementById('alert-cases').textContent = alert.cases;653 document.getElementById('alert-increase').textContent = alert.increase;654 document.getElementById('alert-assessment').textContent = alert.assessment;655 document.getElementById('alert-updated').textContent = alert.updated;656 657 // Update status indicator658 const statusIndicator = document.querySelector('#alert-modal .font-bold.text-red-500');659 statusIndicator.textContent = alert.status;660 statusIndicator.previousElementSibling.className = `inline-block w-3 h-3 ${alert.risk === 'high' ? 'bg-red-500' : alert.risk === 'moderate' ? 'bg-yellow-500' : 'bg-green-500'} rounded-full mr-2`;661 662 // Update recommended actions663 const actionsList = document.querySelector('#alert-modal ul');664 actionsList.innerHTML = '';665 alert.recommendedActions.forEach(action => {666 const li = document.createElement('li');667 li.className = 'flex items-start';668 li.innerHTML = `669 <i data-feather="check-circle" class="text-green-500 mr-2 mt-0.5"></i>670 <span>${action}</span>671 `;672 actionsList.appendChild(li);673 });674 feather.replace();675 676 modal.classList.remove('hidden');677 document.body.style.overflow = 'hidden';678 }679 });680 });681closeBtn.addEventListener('click', () => {682 modal.classList.add('hidden');683 document.body.style.overflow = 'auto';684 });685 686 modal.addEventListener('click', (e) => {687 if (e.target === modal) {688 modal.classList.add('hidden');689 document.body.style.overflow = 'auto';690 }691 });692}693// Populate cases table694function populateCasesTable() {695 const tableBody = document.getElementById('cases-table-body');696 if (!tableBody) return;697 698 // TODO: Replace this with database fetch in the future699 // Example: const casesData = await fetch('/api/cases').then(r => r.json());700 701 // REMOVE (dummy data for table) - Replace with database fetch702 const casesData = [703 { city: 'Quezon City', cases: 247, risk: 'High', lastUpdated: 'Jan 15, 2025' },704 { city: 'Manila', cases: 128, risk: 'High', lastUpdated: 'Jan 15, 2025' },705 { city: 'Caloocan', cases: 87, risk: 'High', lastUpdated: 'Jan 14, 2025' },706 { city: 'Las Piñas', cases: 65, risk: 'Moderate', lastUpdated: 'Jan 14, 2025' },707 { city: 'Makati', cases: 45, risk: 'Moderate', lastUpdated: 'Jan 14, 2025' },708 { city: 'Malabon', cases: 38, risk: 'Moderate', lastUpdated: 'Jan 13, 2025' },709 { city: 'Mandaluyong', cases: 32, risk: 'Moderate', lastUpdated: 'Jan 13, 2025' },710 { city: 'Marikina', cases: 28, risk: 'Moderate', lastUpdated: 'Jan 13, 2025' },711 { city: 'Muntinlupa', cases: 25, risk: 'Low', lastUpdated: 'Jan 12, 2025' },712 { city: 'Navotas', cases: 22, risk: 'Low', lastUpdated: 'Jan 12, 2025' },713 { city: 'Parañaque', cases: 19, risk: 'Low', lastUpdated: 'Jan 12, 2025' },714 { city: 'Pasay', cases: 16, risk: 'Low', lastUpdated: 'Jan 11, 2025' },715 { city: 'Pasig', cases: 14, risk: 'Low', lastUpdated: 'Jan 11, 2025' },716 { city: 'San Juan', cases: 12, risk: 'Low', lastUpdated: 'Jan 11, 2025' },717 { city: 'Taguig', cases: 10, risk: 'Low', lastUpdated: 'Jan 10, 2025' },718 { city: 'Valenzuela', cases: 8, risk: 'Low', lastUpdated: 'Jan 10, 2025' },719 { city: 'Pateros', cases: 5, risk: 'Low', lastUpdated: 'Jan 10, 2025' }720 ];721 // END REMOVE (dummy data for table)722 723 // Sort by cases (descending)724 casesData.sort((a, b) => b.cases - a.cases);725 726 // Clear existing rows727 tableBody.innerHTML = '';728 729 // Populate table730 casesData.forEach(item => {731 const row = document.createElement('tr');732 row.className = 'hover:bg-gray-50';733 734 const riskBadgeClass = {735 'Low': 'bg-green-100 text-green-800',736 'Moderate': 'bg-yellow-100 text-yellow-800',737 'High': 'bg-redorange-100 text-red-800',738 'VeryHigh': 'bg-red-100 text red-800'739 }[item.risk] || 'bg-gray-100 text-gray-800';740 741 row.innerHTML = `742 <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${item.city}</td>743 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.cases}</td>744 <td class="px-6 py-4 whitespace-nowrap">745 <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${riskBadgeClass}">746 ${item.risk}747 </span>748 </td>749 <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${item.lastUpdated}</td>750 `;751 752 tableBody.appendChild(row);753 });754}755 756// Initialize app when DOM is loaded757document.addEventListener('DOMContentLoaded', () => {758 initializeApp();759 setupAlertModal();760 initializeFilter();761 762 // Populate cases table after a short delay to ensure SVG paths are loaded763 setTimeout(() => {764 populateCasesTable();765 }, 100);766});767 