${project.milestones.map(m => `
${m.name}
| Task |
Est. Hours |
Priority |
${m.tasks.length > 0 ? m.tasks.map(t => `
| ${t.desc} |
${t.hours} |
${t.priority} |
`).join('') : `| No tasks for this milestone. |
`}
`).join('')}
`;
};
// --- Event Handlers ---
const handleUpdateGoal = (e) => {
project.goal = e.target.value;
saveData();
};
const handleAddMilestone = (e) => {
e.preventDefault();
const input = getElement('milestone-name');
if (input.value.trim()) {
project.milestones.push({ id: Date.now(), name: input.value.trim(), tasks: [] });
input.value = '';
saveData();
renderMilestoneList();
}
};
const handleDeleteMilestone = (e) => {
if (e.target.classList.contains('delete-milestone-btn')) {
const id = parseInt(e.target.dataset.id);
project.milestones = project.milestones.filter(m => m.id !== id);
saveData();
renderMilestoneList();
}
};
const handleAddTask = (e) => {
e.preventDefault();
if (e.target.classList.contains('add-task-form')) {
const milestoneId = parseInt(e.target.dataset.milestoneId);
const milestone = project.milestones.find(m => m.id === milestoneId);
const desc = e.target.elements.desc.value;
const hours = parseInt(e.target.elements.hours.value);
const priority = e.target.elements.priority.value;
if (milestone && desc && hours > 0) {
milestone.tasks.push({ id: Date.now(), desc, hours, priority });
saveData();
renderTaskBreakdownArea();
}
}
};
const handleDeleteTask = (e) => {
if (e.target.classList.contains('delete-task-btn')) {
const milestoneId = parseInt(e.target.dataset.milestoneId);
const taskId = parseInt(e.target.dataset.taskId);
const milestone = project.milestones.find(m => m.id === milestoneId);
if (milestone) {
milestone.tasks = milestone.tasks.filter(t => t.id !== taskId);
saveData();
renderTaskBreakdownArea();
}
}
};
const generatePDF = () => {
const content = getElement('pdf-content');
if (!content) return;
content.classList.add('pdf-mode');
html2canvas(content, { scale: 2 }).then(canvas => {
content.classList.remove('pdf-mode');
const imgData = canvas.toDataURL('image/png');
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const ratio = canvas.width / canvas.height;
const pdfImgWidth = pdfWidth - 20;
const pdfImgHeight = pdfImgWidth / ratio;
pdf.addImage(imgData, 'PNG', 10, 10, pdfImgWidth, pdfImgHeight);
pdf.save('Project_Breakdown.pdf');
});
};
// --- Navigation & Tab Logic ---
const updateTabView = () => {
tabs.forEach(tab => tab.classList.toggle('active', parseInt(tab.dataset.tab) === currentTab));
tabContents.forEach(content => content.classList.toggle('active', parseInt(content.id.split('-')[1]) === currentTab));
prevBtn.style.display = currentTab === 1 ? 'none' : 'inline-flex';
nextBtn.style.display = currentTab === 3 ? 'none' : 'inline-flex';
};
let currentTab = 1;
tabs.forEach(tab => tab.addEventListener('click', () => {
currentTab = parseInt(tab.dataset.tab);
if (currentTab === 2) renderTaskBreakdownArea();
if (currentTab === 3) renderReview();
updateTabView();
}));
prevBtn.addEventListener('click', () => { if (currentTab > 1) currentTab--; updateTabView(); });
nextBtn.addEventListener('click', () => { if (currentTab < 3) currentTab++; if (currentTab === 2) renderTaskBreakdownArea(); if (currentTab === 3) renderReview(); updateTabView(); });
// --- Initial Setup & Event Listeners ---
getElement('project-goal').addEventListener('change', handleUpdateGoal);
getElement('add-milestone-form').addEventListener('submit', handleAddMilestone);
getElement('milestone-list').addEventListener('click', handleDeleteMilestone);
getElement('task-breakdown-area').addEventListener('submit', handleAddTask);
getElement('task-breakdown-area').addEventListener('click', handleDeleteTask);
getElement('download-pdf-btn').addEventListener('click', generatePDF);
loadData();
renderAll();
updateTabView();
});