`;
kpiCardsContainer.innerHTML += card;
});
};
const renderTasks = () => {
taskListBody.innerHTML = '';
state.tasks.forEach(task => {
const statusColors = {
"Not Started": "bg-red-100 text-red-800",
"In Progress": "bg-yellow-100 text-yellow-800",
"Completed": "bg-green-100 text-green-800"
};
const row = `
${task.description}
`;
taskListBody.innerHTML += row;
});
// Add event listeners to the new status dropdowns
document.querySelectorAll('.status-select').forEach(select => {
select.addEventListener('change', handleStatusChange);
});
};
const updateProgress = () => {
const completedTasks = state.tasks.filter(t => t.status === 'Completed').length;
const totalTasks = state.tasks.length;
const percentage = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
progressBar.style.width = `${percentage}%`;
progressBar.textContent = `${percentage}%`;
};
const renderNotes = () => {
notesArea.value = state.notes;
};
const renderKpiConfig = () => {
kpiConfigContainer.innerHTML = '';
state.kpis.forEach((kpi, index) => {
const configItem = `
`;
kpiConfigContainer.innerHTML += configItem;
});
document.querySelectorAll('.kpi-config-input').forEach(input => {
input.addEventListener('change', handleKpiChange);
});
};
const renderTaskConfig = () => {
taskConfigContainer.innerHTML = '';
state.tasks.forEach((task, index) => {
const configItem = `
`;
taskConfigContainer.innerHTML += configItem;
});
document.querySelectorAll('.task-config-input').forEach(input => {
input.addEventListener('change', handleTaskDescriptionChange);
});
document.querySelectorAll('.remove-task-btn').forEach(button => {
button.addEventListener('click', handleRemoveTask);
});
};
// --- EVENT HANDLERS ---
const handleStatusChange = (e) => {
const taskId = parseInt(e.target.dataset.taskId, 10);
const newStatus = e.target.value;
const task = state.tasks.find(t => t.id === taskId);
if (task) {
task.status = newStatus;
renderTasks(); // Re-render to update colors
updateProgress();
}
};
const handleTitleChange = (e) => {
state.title = e.target.textContent;
};
const handleNotesChange = (e) => {
state.notes = e.target.value;
};
const handleKpiChange = (e) => {
const kpiIndex = parseInt(e.target.dataset.kpiIndex, 10);
const newValue = parseFloat(e.target.value);
if (!isNaN(newValue)) {
state.kpis[kpiIndex].value = newValue;
renderKpis();
}
};
const handleTaskDescriptionChange = (e) => {
const taskIndex = parseInt(e.target.dataset.taskIndex, 10);
state.tasks[taskIndex].description = e.target.value;
renderTasks();
};
const handleRemoveTask = (e) => {
const taskIndex = parseInt(e.currentTarget.dataset.taskIndex, 10);
state.tasks.splice(taskIndex, 1);
renderAll(); // Re-render everything after removal
};
const handleAddTask = () => {
const newId = state.tasks.length > 0 ? Math.max(...state.tasks.map(t => t.id)) + 1 : 1;
state.tasks.push({ id: newId, description: "New Task", status: "Not Started" });
renderAll();
};
const handleDownloadPdf = () => {
const { jsPDF } = window.jspdf;
const pdfContent = document.getElementById('pdf-content');
// Temporarily show print-friendly status for capture
const printOnlyElements = pdfContent.querySelectorAll('.print-only');
const noPrintElements = pdfContent.querySelectorAll('.no-print, .status-select');
printOnlyElements.forEach(el => el.style.display = 'inline-flex');
noPrintElements.forEach(el => el.style.display = 'none');
html2canvas(pdfContent, {
scale: 2, // Higher scale for better quality
useCORS: true,
backgroundColor: '#ffffff'
}).then(canvas => {
// Revert visibility changes after capture
printOnlyElements.forEach(el => el.style.display = 'none');
noPrintElements.forEach(el => el.style.display = '');
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'p',
unit: 'mm',
format: 'a4'
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const ratio = canvasWidth / canvasHeight;
const imgWidth = pdfWidth - 20; // with margin
const imgHeight = imgWidth / ratio;
let heightLeft = imgHeight;
let position = 10; // top margin
pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight);
heightLeft -= (pdfHeight - 20);
while (heightLeft > 0) {
position = -heightLeft - 10;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight);
heightLeft -= (pdfHeight - 20);
}
pdf.save(`${state.title.replace(/ /g, '_')}_Dashboard.pdf`);
}).catch(err => {
console.error("Error generating PDF:", err);
// Revert visibility changes even if there's an error
printOnlyElements.forEach(el => el.style.display = 'none');
noPrintElements.forEach(el => el.style.display = '');
});
};
// --- TABBING LOGIC ---
let currentTabIndex = 0;
const updateTabButtons = () => {
prevTabBtn.disabled = currentTabIndex === 0;
nextTabBtn.disabled = currentTabIndex === tabContents.length - 1;
};
const switchTab = (index) => {
tabButtons.forEach(btn => btn.classList.remove('active'));
tabContents.forEach(content => content.classList.remove('active'));
tabButtons[index].classList.add('active');
tabContents[index].classList.add('active');
currentTabIndex = index;
updateTabButtons();
};
tabButtons.forEach((button, index) => {
button.addEventListener('click', () => switchTab(index));
});
prevTabBtn.addEventListener('click', () => {
if (currentTabIndex > 0) {
switchTab(currentTabIndex - 1);
}
});
nextTabBtn.addEventListener('click', () => {
if (currentTabIndex < tabContents.length - 1) {
switchTab(currentTabIndex + 1);
}
});
// --- INITIALIZATION ---
// Null checks for essential elements
if (dashboardTitle && kpiCardsContainer && taskListBody && progressBar && notesArea && kpiConfigContainer && taskConfigContainer && addTaskBtn && downloadPdfBtn) {
// Add event listeners
dashboardTitle.addEventListener('blur', handleTitleChange);
notesArea.addEventListener('change', handleNotesChange);
addTaskBtn.addEventListener('click', handleAddTask);
downloadPdfBtn.addEventListener('click', handleDownloadPdf);
// Initial render
renderAll();
updateTabButtons();
} else {
console.error("One or more essential elements could not be found in the DOM.");
}
});
${kpi.prefix || ''}
