${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();
});