No objectives added yet. Use the form above to get started.
';
pdfActionContainer.style.display = 'none';
return;
}
pdfActionContainer.style.display = 'block';
objectives.forEach(obj => {
const target = parseFloat(obj.targetValue) || 0;
const current = parseFloat(obj.currentValue) || 0;
let progress = target > 0 ? Math.max(0, Math.min(100, (current / target) * 100)) : 0;
const card = document.createElement('div');
card.className = 'cobit-card';
card.dataset.id = obj.id;
card.innerHTML = `
${obj.cobitDomain}
${escapeHtml(obj.objectiveName)}
Metric: ${escapeHtml(obj.keyMetric)}
Values: ${current} / ${target}
`;
dashboardOutput.appendChild(card);
});
}
/**
* Handles form submission for adding or updating objectives.
*/
function handleFormSubmit(event) {
event.preventDefault();
const id = objectiveIdInput.value;
const newObjective = {
objectiveName: objectiveNameInput.value.trim(),
cobitDomain: cobitDomainInput.value,
keyMetric: keyMetricInput.value.trim(),
targetValue: parseFloat(targetValueInput.value),
currentValue: parseFloat(currentValueInput.value)
};
if (id) { // Update existing
const index = objectives.findIndex(obj => obj.id === id);
if (index !== -1) {
objectives[index] = { ...objectives[index], ...newObjective };
}
} else { // Add new
newObjective.id = `obj_${Date.now()}`;
objectives.push(newObjective);
}
form.reset();
objectiveIdInput.value = '';
formSubmitBtn.textContent = 'Add Objective';
formSubmitBtn.classList.add('primary');
formSubmitBtn.classList.remove('secondary');
renderDashboard();
}
/**
* Handles clicks on Edit and Delete buttons using event delegation.
*/
function handleDashboardClick(event) {
const target = event.target;
const card = target.closest('.cobit-card');
if (!card) return;
const id = card.dataset.id;
if (target.classList.contains('edit-btn')) {
const objToEdit = objectives.find(obj => obj.id === id);
if (objToEdit) {
objectiveIdInput.value = objToEdit.id;
objectiveNameInput.value = objToEdit.objectiveName;
cobitDomainInput.value = objToEdit.cobitDomain;
keyMetricInput.value = objToEdit.keyMetric;
targetValueInput.value = objToEdit.targetValue;
currentValueInput.value = objToEdit.currentValue;
formSubmitBtn.textContent = 'Update Objective';
formSubmitBtn.classList.remove('primary');
formSubmitBtn.classList.add('secondary');
form.scrollIntoView({ behavior: 'smooth' });
objectiveNameInput.focus();
}
} else if (target.classList.contains('delete-btn')) {
if (confirm('Are you sure you want to delete this objective?')) {
objectives = objectives.filter(obj => obj.id !== id);
renderDashboard();
}
}
}
/**
* Generates and downloads a PDF report of the dashboard.
*/
function generatePdf() {
const doc = new jsPDF();
doc.setFontSize(18);
doc.text('COBIT Dashboard Report', 14, 22);
doc.setFontSize(11);
doc.setTextColor(100);
doc.text(`Report generated on: ${new Date().toLocaleDateString()}`, 14, 30);
const tableColumns = ['Objective', 'Domain', 'Metric', 'Target', 'Current', 'Progress (%)'];
const tableRows = [];
objectives.forEach(obj => {
const target = parseFloat(obj.targetValue) || 0;
const current = parseFloat(obj.currentValue) || 0;
let progress = target > 0 ? Math.max(0, Math.min(100, (current / target) * 100)) : 0;
const rowData = [
obj.objectiveName,
obj.cobitDomain,
obj.keyMetric,
target,
current,
`${progress.toFixed(1)}%`
];
tableRows.push(rowData);
});
doc.autoTable({
head: [tableColumns],
body: tableRows,
startY: 35,
theme: 'grid',
headStyles: { fillColor: [37, 99, 235] }, // blue-600
styles: { fontSize: 9, cellPadding: 2.5 },
columnStyles: {
0: { cellWidth: 'auto' }, // Objective
1: { cellWidth: 20 }, // Domain
2: { cellWidth: 'auto' }, // Metric
3: { halign: 'right' }, // Target
4: { halign: 'right' }, // Current
5: { halign: 'right' } // Progress
}
});
const pageCount = doc.internal.getNumberOfPages();
for(let i = 1; i <= pageCount; i++) {
doc.setPage(i);
doc.setFontSize(8);
doc.text(`Page ${i} of ${pageCount}`, doc.internal.pageSize.width - 25, doc.internal.pageSize.height - 10);
}
doc.save('COBIT_Dashboard_Report.pdf');
}
/**
* Escapes HTML to prevent XSS.
*/
function escapeHtml(unsafe) {
if (typeof unsafe !== 'string') return unsafe;
return unsafe
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// --- Event Listeners ---
form.addEventListener('submit', handleFormSubmit);
dashboardOutput.addEventListener('click', handleDashboardClick);
downloadPdfBtn.addEventListener('click', generatePdf);
// --- Initial Render ---
renderDashboard();
});