Microgrid Dashboard

Microgrid Dashboard

Overview

Total Energy Generated (kWh)

--

Total Energy Consumed (kWh)

--

Battery State of Charge (%)

--%

Renewable Energy Share (%)

--%

Daily Energy Flow (kWh)

Energy Source Breakdown

Grid Status & Alerts

Grid Connection: Connected

No recent alerts.

No recent alerts.

'; } else { dashboardData.alerts.forEach(item => { const alertDiv = document.createElement('div'); alertDiv.className = 'bg-red-50 p-3 rounded-md shadow-sm text-red-800'; alertDiv.textContent = `Alert: ${item.text}`; alertListElement.appendChild(alertDiv); }); } } // Render Energy Items in its tab renderEnergyManagement(); } // Function to render the energy management table function renderEnergyManagement() { const energyItemsTableBody = document.querySelector('#energy-items-table tbody'); if (energyItemsTableBody) { energyItemsTableBody.innerHTML = ''; // Clear existing rows if (dashboardData.energyItems.length === 0) { energyItemsTableBody.innerHTML = 'No energy items added yet.'; } else { dashboardData.energyItems.forEach(item => { const row = energyItemsTableBody.insertRow(); row.innerHTML = ` ${item.name} ${item.type} ${item.capacity.toFixed(1)} ${item.status} `; }); } } } // Function to populate data configuration inputs function populateConfigInputs() { const inputTotalGenerated = document.getElementById('input-total-generated'); const inputTotalConsumed = document.getElementById('input-total-consumed'); const inputBatterySoC = document.getElementById('input-battery-soc'); const inputRenewableShare = document.getElementById('input-renewable-share'); const inputEnergyFlow = document.getElementById('input-energy-flow'); const inputSourceBreakdown = document.getElementById('input-source-breakdown'); const inputGridConnection = document.getElementById('input-grid-connection'); const inputAlerts = document.getElementById('input-alerts'); if (inputTotalGenerated) inputTotalGenerated.value = dashboardData.totalGenerated; if (inputTotalConsumed) inputTotalConsumed.value = dashboardData.totalConsumed; if (inputBatterySoC) inputBatterySoC.value = dashboardData.batterySoC; if (inputRenewableShare) inputRenewableShare.value = dashboardData.renewableShare; if (inputEnergyFlow) { const flowEntries = dashboardData.energyFlow.map(d => `${d.generated},${d.consumed}`).join('\n'); inputEnergyFlow.value = flowEntries; } if (inputSourceBreakdown) { const sourceEntries = Object.entries(dashboardData.sourceBreakdown).map(([source, kwh]) => `${source}:${kwh}`).join(','); inputSourceBreakdown.value = sourceEntries; } if (inputGridConnection) inputGridConnection.value = dashboardData.gridConnection; if (inputAlerts) inputAlerts.value = dashboardData.alerts.map(a => a.text).join('\n'); } // Initial render of the dashboard and populate config inputs renderDashboard(); populateConfigInputs(); // Make functions globally accessible for onclick attributes window.showTab = function(tabId) { const tabs = ['dashboard', 'energy-management', 'data-config']; tabs.forEach(id => { const tabContent = document.getElementById(`${id}-tab-content`); const tabButton = document.getElementById(`${id}-tab-button`); if (tabContent && tabButton) { if (id === tabId) { tabContent.classList.remove('hidden'); tabButton.classList.add('active'); } else { tabContent.classList.add('hidden'); tabButton.classList.remove('active'); } } }); updateTabNavigationButtons(tabId); }; // Function to navigate tabs using Next/Previous buttons window.navigateTabs = function(direction) { const tabs = ['dashboard', 'energy-management', 'data-config']; let currentTabIndex = -1; tabs.forEach((id, index) => { const tabContent = document.getElementById(`${id}-tab-content`); if (tabContent && !tabContent.classList.contains('hidden')) { currentTabIndex = index; } }); let nextTabIndex = currentTabIndex; if (direction === 'next') { nextTabIndex = Math.min(currentTabIndex + 1, tabs.length - 1); } else if (direction === 'prev') { nextTabIndex = Math.max(currentTabIndex - 1, 0); } if (nextTabIndex !== currentTabIndex) { showTab(tabs[nextTabIndex]); } }; // Function to update the visibility of Next/Previous buttons function updateTabNavigationButtons(activeTabId) { const prevButton = document.getElementById('prev-tab-button'); const nextButton = document.getElementById('next-tab-button'); if (prevButton && nextButton) { if (activeTabId === 'dashboard') { prevButton.classList.add('hidden'); nextButton.classList.remove('hidden'); } else if (activeTabId === 'data-config') { prevButton.classList.remove('hidden'); nextButton.classList.add('hidden'); } else { // For intermediate tabs, both buttons are visible prevButton.classList.remove('hidden'); nextButton.classList.remove('hidden'); } } } // Initial state for tab navigation buttons updateTabNavigationButtons('dashboard'); // Function to add a new energy item window.addEnergyItem = function() { const newEnergyName = document.getElementById('new-energy-name'); const newEnergyType = document.getElementById('new-energy-type'); const newEnergyCapacity = document.getElementById('new-energy-capacity'); const newEnergyStatus = document.getElementById('new-energy-status'); if (!newEnergyName || !newEnergyType || !newEnergyCapacity || !newEnergyStatus) { console.error("One or more new energy item input fields not found."); return; } const name = newEnergyName.value.trim(); const type = newEnergyType.value; const capacity = parseFloat(newEnergyCapacity.value) || 0; const status = newEnergyStatus.value; if (name && type) { const newId = dashboardData.energyItems.length > 0 ? Math.max(...dashboardData.energyItems.map(item => item.id)) + 1 : 1; dashboardData.energyItems.push({ id: newId, name, type, capacity, status }); renderEnergyManagement(); // Clear form fields newEnergyName.value = ''; newEnergyType.value = 'Solar'; // Reset to default newEnergyCapacity.value = '0.0'; newEnergyStatus.value = 'Operational'; // Reset to default // Re-render dashboard to update charts and metrics that depend on energy items renderDashboard(); populateConfigInputs(); // Update config inputs as well } else { alert('Please fill in Name and Type for the energy item.'); // Using alert as per previous code, but typically would use a custom modal } }; // Function to edit an energy item (simplified: could open a modal for editing) window.editEnergyItem = function(id) { const item = dashboardData.energyItems.find(item => item.id === id); if (item) { document.getElementById('new-energy-name').value = item.name; document.getElementById('new-energy-type').value = item.type; document.getElementById('new-energy-capacity').value = item.capacity; document.getElementById('new-energy-status').value = item.status; deleteEnergyItem(id); // Remove the old item after pre-filling for a "replace" action alert('Energy item fields pre-filled for editing. Please adjust and click "Add Energy Item" to save changes.'); } }; // Function to delete an energy item window.deleteEnergyItem = function(id) { dashboardData.energyItems = dashboardData.energyItems.filter(item => item.id !== id); renderEnergyManagement(); renderDashboard(); // Re-render dashboard to update charts and metrics populateConfigInputs(); // Update config inputs as well }; // Function to update dashboard data from input fields window.updateDashboardData = function() { const inputTotalGenerated = document.getElementById('input-total-generated'); const inputTotalConsumed = document.getElementById('input-total-consumed'); const inputBatterySoC = document.getElementById('input-battery-soc'); const inputRenewableShare = document.getElementById('input-renewable-share'); const inputEnergyFlow = document.getElementById('input-energy-flow'); const inputSourceBreakdown = document.getElementById('input-source-breakdown'); const inputGridConnection = document.getElementById('input-grid-connection'); const inputAlerts = document.getElementById('input-alerts'); // Perform null checks for all elements if (inputTotalGenerated) dashboardData.totalGenerated = parseInt(inputTotalGenerated.value) || 0; if (inputTotalConsumed) dashboardData.totalConsumed = parseInt(inputTotalConsumed.value) || 0; if (inputBatterySoC) dashboardData.batterySoC = parseFloat(inputBatterySoC.value) || 0; if (inputRenewableShare) dashboardData.renewableShare = parseFloat(inputRenewableShare.value) || 0; if (inputEnergyFlow) { const flowData = inputEnergyFlow.value.split('\n').map(line => { const parts = line.split(',').map(Number).filter(n => !isNaN(n)); return parts.length === 2 ? { generated: parts[0], consumed: parts[1] } : null; }).filter(item => item !== null); if (flowData.length > 0) { dashboardData.energyFlow = flowData; } } if (inputSourceBreakdown) { const sourceMap = {}; inputSourceBreakdown.value.split(',').forEach(entry => { const parts = entry.split(':'); if (parts.length === 2) { const source = parts[0].trim(); const kwh = parseInt(parts[1].trim()); if (source && !isNaN(kwh)) { sourceMap[source] = kwh; } } }); dashboardData.sourceBreakdown = sourceMap; } if (inputGridConnection) dashboardData.gridConnection = inputGridConnection.value; if (inputAlerts) { const alertComments = inputAlerts.value.split('\n').map(comment => comment.trim()).filter(comment => comment.length > 0); dashboardData.alerts = alertComments.map((comment, index) => ({ id: index + 1, text: comment })); } renderDashboard(); // Re-render dashboard with new data showTab('dashboard'); // Switch back to dashboard tab after saving }; // Function to download dashboard as PDF window.downloadDashboardPdf = async function() { const dashboardContent = document.getElementById('dashboard-tab-content'); // Get the dashboard content div if (!dashboardContent) { console.error("Dashboard content element not found for PDF generation."); return; } // Temporarily hide elements not needed in PDF const elementsToHide = [ document.getElementById('prev-tab-button'), document.getElementById('next-tab-button'), document.getElementById('download-pdf-button'), document.querySelector('.flex.flex-wrap.border-b') // Tab navigation buttons container ]; elementsToHide.forEach(el => { if (el) el.style.display = 'none'; }); // Set a temporary background color for the PDF capture if needed, then revert const originalBg = document.body.style.backgroundColor; document.body.style.backgroundColor = '#ffffff'; // Ensure white background for PDF // Use html2canvas to capture the dashboard content const canvas = await html2canvas(dashboardContent, { scale: 2, // Increase scale for better resolution in PDF useCORS: true, // Important for images if any, though none are used here logging: false, // Disable logging for cleaner console backgroundColor: '#ffffff' // Ensure white background for the captured image }); // Revert hidden elements and background elementsToHide.forEach(el => { if (el) el.style.display = ''; // Revert to original display }); document.body.style.backgroundColor = originalBg; // Create a new jsPDF instance let jsPDF_lib; if (window.jspdf && typeof window.jspdf.jsPDF === 'function') { jsPDF_lib = window.jspdf.jsPDF; } else { console.error("jsPDF library (window.jspdf.jsPDF) not found or not a function."); return; // Cannot proceed without jsPDF } const pdf = new jsPDF_lib('p', 'mm', 'a4'); // 'p' for portrait, 'mm' for millimeters, 'a4' size const imgData = canvas.toDataURL('image/png'); const imgWidth = 210; // A4 width in mm const pageHeight = 297; // A4 height in mm const imgHeight = canvas.height * imgWidth / canvas.width; let heightLeft = imgHeight; let position = 0; // Add title to PDF pdf.setFontSize(22); pdf.text('Microgrid Dashboard Report', pdf.internal.pageSize.getWidth() / 2, 15, { align: 'center' }); pdf.setFontSize(10); pdf.text(`Report Date: ${new Date().toLocaleDateString('en-US')}`, pdf.internal.pageSize.getWidth() / 2, 25, { align: 'center' }); // Add a small margin after the title position = 35; // Add image to PDF, handling multiple pages if content is too long pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight - position; while (heightLeft >= 0) { position = heightLeft - imgHeight + 5; // Add a small margin at the top of new page pdf.addPage(); pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; } pdf.save('Microgrid_Dashboard.pdf'); }; });
Scroll to Top