Expense Breakdown
| Task/Expense | Cost |
${project.tasks.length > 0 ? project.tasks.map(task => `| ${task.name} | $${task.cost.toLocaleString()} |
`).join('') : `| No expenses logged. |
`}
`;
container.appendChild(projectElement);
});
}
// --- EVENT HANDLERS & LOGIC --- //
function attachEventListeners() {
prevBtn.addEventListener('click', () => changeTab(state.activeTab - 1));
nextBtn.addEventListener('click', () => changeTab(state.activeTab + 1));
addProjectForm.addEventListener('submit', handleAddProject);
downloadPdfBtn.addEventListener('click', handleDownloadPdf);
}
window.changeTab = (tabIndex) => {
if (tabIndex < 0 || tabIndex >= tabButtons.length) return;
state.activeTab = tabIndex;
updateTabUI();
};
function updateTabUI() {
tabButtons.forEach((button, index) => {
button.classList.toggle('active', index === state.activeTab);
button.classList.toggle('inactive', index !== state.activeTab);
tabPanes[index].classList.toggle('hidden', index !== state.activeTab);
});
prevBtn.disabled = state.activeTab === 0;
nextBtn.disabled = state.activeTab === tabButtons.length - 1;
prevBtn.classList.toggle('opacity-50', prevBtn.disabled);
nextBtn.classList.toggle('opacity-50', nextBtn.disabled);
}
function handleAddProject(e) {
e.preventDefault();
const newProject = {
id: Date.now(),
name: document.getElementById('project-name').value,
budget: parseFloat(document.getElementById('project-budget').value),
startDate: document.getElementById('project-start-date').value,
endDate: document.getElementById('project-end-date').value,
tasks: []
};
state.projects.push(newProject);
addProjectForm.reset();
render();
changeTab(1);
}
window.deleteProject = (projectId) => {
if (confirm('Are you sure you want to delete this entire project?')) {
state.projects = state.projects.filter(p => p.id !== projectId);
render();
}
};
window.addTask = (event, projectId) => {
const form = event.target;
const project = state.projects.find(p => p.id === projectId);
if (project) {
project.tasks.push({
id: Date.now(),
name: form.elements.taskName.value,
status: 'Not Started',
assignedTo: form.elements.assignedTo.value,
dueDate: form.elements.dueDate.value,
cost: parseFloat(form.elements.cost.value)
});
form.reset();
render();
}
};
window.deleteTask = (projectId, taskId) => {
const project = state.projects.find(p => p.id === projectId);
if (project) {
project.tasks = project.tasks.filter(t => t.id !== taskId);
render();
}
};
window.updateTaskStatus = (projectId, taskId, newStatus) => {
const task = state.projects.find(p => p.id === projectId)?.tasks.find(t => t.id === taskId);
if (task) {
task.status = newStatus;
render();
}
};
async function handleDownloadPdf() {
const downloadBtn = document.getElementById('download-pdf-btn');
if (!downloadBtn || downloadBtn.disabled) return;
const originalBtnContent = downloadBtn.innerHTML;
downloadBtn.disabled = true;
downloadBtn.innerHTML = `
Generating PDF...`;
try {
const { jsPDF } = window.jspdf;
const contentToClone = document.getElementById('dashboard-container');
const contentToExport = contentToClone.cloneNode(true);
contentToExport.querySelectorAll('.pdf-hide').forEach(el => el.remove());
contentToExport.querySelectorAll('.tab-pane').forEach((pane, index) => { if (index !== 0) pane.remove(); });
contentToExport.querySelector('#tab-0-content').classList.remove('hidden');
document.body.appendChild(contentToExport);
contentToExport.style.position = 'absolute';
contentToExport.style.left = '0';
contentToExport.style.top = '0';
contentToExport.style.width = '1024px';
contentToExport.style.zIndex = '-1';
contentToExport.style.backgroundColor = 'white';
contentToExport.style.padding = '2rem';
const canvas = await html2canvas(contentToExport, { scale: 2, useCORS: true });
document.body.removeChild(contentToExport);
const imgData = canvas.toDataURL('image/png');
const imgWidth = canvas.width;
const imgHeight = canvas.height;
const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const scaledHeight = (imgHeight * pdfWidth) / imgWidth;
const pageHeight = pdf.internal.pageSize.getHeight();
let heightLeft = scaledHeight;
let position = 0;
pdf.addImage(imgData, 'PNG', 0, position, pdfWidth, scaledHeight);
heightLeft -= pageHeight;
while (heightLeft > 0) {
position = heightLeft - scaledHeight;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 0, position, pdfWidth, scaledHeight);
heightLeft -= pageHeight;
}
pdf.save('Post-Production_Dashboard_Summary.pdf');
} catch (error) {
console.error("Failed to generate PDF:", error);
} finally {
downloadBtn.disabled = false;
downloadBtn.innerHTML = originalBtnContent;
}
}
// --- UTILITY FUNCTIONS --- //
function getProjectProgress(project) {
if (project.tasks.length === 0) return 0;
const completedTasks = project.tasks.filter(t => t.status === 'Completed').length;
return Math.round((completedTasks / project.tasks.length) * 100);
}
// --- START THE APP --- //
initialize();
});