Growth Metrics Dashboard

Metric Trend

Manage Metric Types

Metric NameUnitAction

Manage Monthly Data

MetricDateValueActions

${latestPoint ? formatValue(latestPoint.value, metric.unit) : 'No Data'}

${changeHtml}
`; kpiCardsContainer.appendChild(card); }); }; const renderChartSelector = () => { chartSelector.innerHTML = metricTypes.map(m => ``).join(''); }; const renderTrendChart = () => { const selectedMetricId = chartSelector.value; if (!selectedMetricId) { chartContainer.innerHTML = '
Select a metric to display its trend.
'; return; } const metric = metricTypes.find(m => m.id === selectedMetricId); const chartData = dataPoints .filter(dp => dp.metricId === selectedMetricId) .sort((a, b) => new Date(a.date) - new Date(b.date)); const options = { chart: { type: 'area', height: '100%', toolbar: { show: false } }, series: [{ name: metric.name, data: chartData.map(dp => dp.value) }], xaxis: { categories: chartData.map(dp => new Date(dp.date + '-02').toLocaleString('default', { month: 'short', year: '2-digit' })), }, yaxis: { labels: { formatter: (value) => formatValue(value, metric.unit) } }, stroke: { curve: 'smooth' }, dataLabels: { enabled: false }, tooltip: { y: { formatter: (value) => formatValue(value, metric.unit) } } }; if (trendChart) trendChart.destroy(); chartContainer.innerHTML = ''; trendChart = new ApexCharts(chartContainer, options); trendChart.render(); }; const renderManagementUI = () => { // Render Metric Types Table metricTypesTableBody.innerHTML = ''; metricTypes.forEach(m => { const row = document.createElement('tr'); row.innerHTML = ` ${m.name} ${m.unit} `; metricTypesTableBody.appendChild(row); }); // Populate Metric Select document.getElementById('gmd-data-metric-type').innerHTML = metricTypes.map(m => ``).join(''); // Render Data Points Table dataPointsTableBody.innerHTML = ''; dataPoints.sort((a,b) => new Date(b.date) - new Date(a.date)).forEach(dp => { const metric = metricTypes.find(m => m.id === dp.metricId); if (!metric) return; const row = document.createElement('tr'); row.innerHTML = ` ${metric.name} ${dp.date} ${formatValue(dp.value, metric.unit)} `; dataPointsTableBody.appendChild(row); }); }; // --- EVENT LISTENERS --- chartSelector.addEventListener('change', renderTrendChart); addMetricTypeBtn.addEventListener('click', () => { const nameInput = document.getElementById('gmd-metric-type-name'); const unitInput = document.getElementById('gmd-metric-type-unit'); if (nameInput.value.trim()) { metricTypes.push({ id: generateId(), name: nameInput.value.trim(), unit: unitInput.value }); nameInput.value = ''; saveState(); renderAll(); } }); metricTypesTableBody.addEventListener('click', e => { if (e.target.tagName === 'BUTTON') { const id = e.target.dataset.id; if (confirm('Are you sure you want to delete this metric type and all its data?')) { metricTypes = metricTypes.filter(m => m.id !== id); dataPoints = dataPoints.filter(dp => dp.metricId !== id); saveState(); renderAll(); } } }); dataPointForm.addEventListener('submit', e => { e.preventDefault(); const id = document.getElementById('gmd-data-point-id').value; const data = { metricId: document.getElementById('gmd-data-metric-type').value, date: document.getElementById('gmd-data-date').value, value: parseFloat(document.getElementById('gmd-data-value').value) }; if (id) { // Update const index = dataPoints.findIndex(dp => dp.id === id); if (index > -1) dataPoints[index] = { ...dataPoints[index], ...data }; } else { // Add new dataPoints.push({ id: generateId(), ...data }); } saveState(); renderAll(); dataPointForm.reset(); document.getElementById('gmd-data-point-id').value = ''; }); clearFormBtn.addEventListener('click', () => { dataPointForm.reset(); document.getElementById('gmd-data-point-id').value = ''; }); dataPointsTableBody.addEventListener('click', e => { if(e.target.tagName !== 'BUTTON') return; const id = e.target.dataset.id; const action = e.target.dataset.action; if (action === 'delete') { if (confirm('Are you sure?')) { dataPoints = dataPoints.filter(dp => dp.id !== id); saveState(); renderAll(); } } else if (action === 'edit') { const dp = dataPoints.find(p => p.id === id); if (dp) { document.getElementById('gmd-data-point-id').value = dp.id; document.getElementById('gmd-data-metric-type').value = dp.metricId; document.getElementById('gmd-data-date').value = dp.date; document.getElementById('gmd-data-value').value = dp.value; } } }); // --- PDF EXPORT --- document.getElementById('gmd-download-pdf-btn').addEventListener('click', function () { const content = document.getElementById('gmd-pdf-capture-area'); const btn = this; btn.textContent = 'Generating...'; btn.disabled = true; html2canvas(content, { scale: 2 }).then(canvas => { const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'l', unit: 'mm', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); const imgWidth = canvas.width; const imgHeight = canvas.height; const ratio = imgWidth / imgHeight; let finalImgWidth = pdfWidth - 20; let finalImgHeight = finalImgWidth / ratio; if (finalImgHeight > pdfHeight - 20) { finalImgHeight = pdfHeight - 20; finalImgWidth = finalImgHeight * ratio; } const x = (pdfWidth - finalImgWidth) / 2; const y = (pdfHeight - finalImgHeight) / 2; pdf.addImage(imgData, 'PNG', x, y, finalImgWidth, finalImgHeight); pdf.save('Growth_Metrics_Report.pdf'); }).finally(() => { btn.textContent = 'Download Report as PDF'; btn.disabled = false; }); }); // --- INITIALIZATION --- renderAll(); });
Scroll to Top