CoolFace
Apppublic

moebyx/denguewatch-ncr-alert

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
script.js767 linesDownload Raw Back to root
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