Cultural Institution Attendance Dashboard

Cultural Institution Attendance Dashboard

Key Attendance & Engagement Indicators

Attendance Insights

No insights generated yet. Please configure your data.

Cultural Institution Attendance Dashboard Report

Key Attendance & Engagement Indicators

Attendance Insights

No metrics defined to generate insights. Please add metrics in the Data Configuration tab.

'; } else { let overallProgress = 0; let metricsBelowTarget = []; let metricsAboveTarget = []; let significantProgress = []; attendanceMetrics.forEach(metric => { const progress = calculateProgress(metric.actual, metric.target); overallProgress += progress; if (progress < 100 && metric.actual < metric.target) { metricsBelowTarget.push({ name: metric.name, progress: progress.toFixed(2) }); } else if (progress >= 100 && metric.actual >= metric.target) { metricsAboveTarget.push({ name: metric.name, progress: progress.toFixed(2) }); } if (progress >= 90) { significantProgress.push({ name: metric.name, progress: progress.toFixed(2) }); } }); const avgProgress = attendanceMetrics.length > 0 ? (overallProgress / attendanceMetrics.length).toFixed(2) : 0; insightsHtml += `

Overall Performance: The average progress across all defined metrics is ${avgProgress}%.

`; if (metricsAboveTarget.length > 0) { insightsHtml += `

Strengths: Excellent performance observed in: ${metricsAboveTarget.map(m => `${m.name} (${m.progress}%)`).join(', ')}. These areas show strong visitor engagement or growth.

`; } if (metricsBelowTarget.length > 0) { insightsHtml += `

Areas for Improvement: Metrics currently below target include: ${metricsBelowTarget.map(m => `${m.name} (${m.progress}%)`).join(', ')}. Focus efforts on these to boost attendance or participation.

`; } if (significantProgress.length > 0 && metricsAboveTarget.length === 0) { // Avoid redundancy if already listed in strengths insightsHtml += `

Notable Progress: Metrics showing significant positive momentum (above 90% target) include: ${significantProgress.map(m => `${m.name} (${m.progress}%)`).join(', ')}.

`; } if (insightsHtml === '') { insightsHtml = '

No specific insights generated. Ensure your metrics have valid target and actual values.

'; } } attendanceInsights.innerHTML = insightsHtml; } // --- Event Handlers (Globally accessible for onclick attributes) --- /** * Switches between dashboard and data configuration tabs. * @param {string} tabName - 'dashboard' or 'dataConfig'. */ window.switchTab = function(tabName) { // Guard clauses for elements if (!dashboardTabBtn || !dataConfigTabBtn || !dashboardTabContent || !dataConfigTabContent || !prevTabBtn || !nextTabBtn) { console.error("One or more tab elements not found."); return; } currentTab = tabName; // Update button styles dashboardTabBtn.classList.remove('active'); dataConfigTabBtn.classList.remove('active'); dashboardTabContent.classList.add('hidden'); dataConfigTabContent.classList.add('hidden'); if (tabName === 'dashboard') { dashboardTabBtn.classList.add('active'); dashboardTabContent.classList.remove('hidden'); prevTabBtn.disabled = true; nextTabBtn.disabled = false; renderAllKpiCards(); // Re-render dashboard content when tab is active generateAttendanceInsights(); // Re-generate insights } else if (tabName === 'dataConfig') { dataConfigTabBtn.classList.add('active'); dataConfigTabContent.classList.remove('hidden'); prevTabBtn.disabled = false; nextTabBtn.disabled = true; renderAllMetricInputRows(); // Re-render input rows when tab is active } }; /** * Navigates between tabs using 'Next' and 'Previous' buttons. * @param {string} direction - 'prev' or 'next'. */ window.navigateTabs = function(direction) { if (direction === 'next') { if (currentTab === 'dashboard') { switchTab('dataConfig'); } } else if (direction === 'prev') { if (currentTab === 'dataConfig') { switchTab('dashboard'); } } }; /** * Adds a new empty metric row to the data configuration. */ if (addMetricBtn) { // Null check for addMetricBtn addMetricBtn.onclick = function() { const newMetricId = generateUniqueId(); const newMetric = { id: newMetricId, name: '', target: null, actual: null, unit: '' }; attendanceMetrics.push(newMetric); renderMetricInputRow(newMetric); showMessage('New metric row added.', 'info'); }; } /** * Updates a specific property of a metric in the attendanceMetrics array. * This function is called directly from input fields' oninput. * @param {string} id - The ID of the metric to update. * @param {string} property - The property to update ('name', 'target', 'actual', 'unit'). * @param {*} value - The new value for the property. */ window.updateMetricValue = function(id, property, value) { const metricIndex = attendanceMetrics.findIndex(m => m.id === id); if (metricIndex !== -1) { attendanceMetrics[metricIndex][property] = value; } }; /** * Removes a metric from the attendanceMetrics array and its corresponding input row. * @param {string} id - The ID of the metric to remove. */ window.removeMetric = function(id) { attendanceMetrics = attendanceMetrics.filter(m => m.id !== id); const metricRow = document.getElementById(`metric-row-${id}`); if (metricRow) { metricRow.remove(); showMessage('Metric removed successfully.', 'success'); } else { console.warn(`Metric row with ID ${id} not found for removal.`); } }; /** * Saves the data (which is already updated via oninput) and refreshes the dashboard. */ if (saveDataBtn) { // Null check for saveDataBtn saveDataBtn.onclick = function() { // Filter out any metrics that are completely empty or invalid attendanceMetrics = attendanceMetrics.filter(metric => metric.name.trim() !== '' || (typeof metric.target === 'number' && !isNaN(metric.target)) || (typeof metric.actual === 'number' && !isNaN(metric.actual)) ); // Ensure all numerical values are indeed numbers attendanceMetrics.forEach(metric => { metric.target = parseFloat(metric.target) || 0; metric.actual = parseFloat(metric.actual) || 0; }); renderAllKpiCards(); // Re-render dashboard generateAttendanceInsights(); // Re-generate insights showMessage('Data saved and dashboard updated!', 'success'); }; } /** * Handles the PDF download functionality. */ if (downloadPdfBtn) { // Null check for downloadPdfBtn downloadPdfBtn.onclick = async function() { if (!pdfContent || !pdfKpiMetrics || !pdfAttendanceInsights) { console.error("PDF content elements not found."); showMessage('Error: PDF content elements missing.', 'error'); return; } // Populate the hidden PDF content div with current dashboard data pdfKpiMetrics.innerHTML = `

Key Attendance & Engagement Indicators

`; if (attendanceMetrics.length === 0) { pdfKpiMetrics.innerHTML += '

No KPI data available.

'; } else { let kpiTableHtml = ` `; attendanceMetrics.forEach(metric => { const progress = calculateProgress(metric.actual, metric.target); kpiTableHtml += ` `; }); kpiTableHtml += `
Metric Name Target Value Actual Value Unit Progress (%)
${metric.name || 'N/A'} ${formatValue(metric.target, metric.unit)} ${formatValue(metric.actual, metric.unit)} ${metric.unit || 'N/A'} ${progress.toFixed(2)}%
`; pdfKpiMetrics.innerHTML += kpiTableHtml; } // Populate attendance insights for PDF let insightsForPdf = ''; if (attendanceMetrics.length === 0) { insightsForPdf = '

No insights generated. Please configure your data.

'; } else { let overallProgress = 0; let metricsBelowTarget = []; let metricsAboveTarget = []; attendanceMetrics.forEach(metric => { const progress = calculateProgress(metric.actual, metric.target); overallProgress += progress; if (progress < 100 && metric.actual < metric.target) { metricsBelowTarget.push({ name: metric.name, progress: progress.toFixed(2) }); } else if (progress >= 100 && metric.actual >= metric.target) { metricsAboveTarget.push({ name: metric.name, progress: progress.toFixed(2) }); } }); const avgProgress = attendanceMetrics.length > 0 ? (overallProgress / attendanceMetrics.length).toFixed(2) : 0; insightsForPdf += `

Overall Performance: The average progress across all defined metrics is ${avgProgress}%.

`; if (metricsAboveTarget.length > 0) { insightsForPdf += `

Strengths: Excellent performance observed in: ${metricsAboveTarget.map(m => `${m.name} (${m.progress}%)`).join(', ')}. These areas show strong visitor engagement or growth.

`; } if (metricsBelowTarget.length > 0) { insightsForPdf += `

Areas for Improvement: Metrics currently below target include: ${metricsBelowTarget.map(m => `${m.name} (${m.progress}%)`).join(', ')}. Focus efforts on these to boost attendance or participation.

`; } insightsForPdf += `
`; } pdfAttendanceInsights.innerHTML = `

Attendance Insights

${insightsForPdf}`; // Show the hidden content temporarily for html2canvas to render it pdfContent.style.display = 'block'; showMessage('Generating PDF...', 'info'); try { const canvas = await html2canvas(pdfContent, { scale: 2, // Increase scale for better quality useCORS: true, // If you have external images, enable CORS logging: false // Disable console logging from html2canvas }); const imgData = canvas.toDataURL('image/png'); const { jsPDF } = window.jspdf; const pdf = new jsPDF('p', 'mm', 'a4'); // 'p' for portrait, 'mm' for millimeters, 'a4' size 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; pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; while (heightLeft >= 0) { position = heightLeft - imgHeight; pdf.addPage(); pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; } pdf.save('Cultural_Institution_Attendance_Dashboard.pdf'); showMessage('PDF downloaded successfully!', 'success'); } catch (error) { console.error("Error generating PDF:", error); showMessage('Error generating PDF. Please try again.', 'error'); } finally { // Hide the content again after PDF generation pdfContent.style.display = 'none'; } }; } // --- Initial Load --- switchTab('dashboard'); // Start on the dashboard tab renderAllMetricInputRows(); // Render initial data in config tab renderAllKpiCards(); // Render initial data in dashboard tab generateAttendanceInsights(); // Generate initial insights });
Scroll to Top