`;
depTasksListUL.appendChild(li);
});
depTasksListUL.querySelectorAll('.edit-dep-task-btn').forEach(btn => btn.addEventListener('click', (e) => populateDepTaskFormForEdit(e.target.dataset.id)));
depTasksListUL.querySelectorAll('.delete-dep-task-btn').forEach(btn => btn.addEventListener('click', (e) => deleteDepTask(e.target.dataset.id)));
populatePredecessorSelect(editingTaskId); // Refresh for form
}
// --- Analysis Tab Logic ---
analyzeDependenciesBtn.addEventListener('click', () => {
readyTasksListUL.innerHTML = '';
blockedTasksListUL.innerHTML = '';
inProgressTasksListUL.innerHTML = '';
completedTasksListUL.innerHTML = '';
circularWarningSection.style.display = 'none'; // Reset cycle warning before new analysis
if (tasks.length === 0) {
analysisResultsDiv.style.display = 'block'; // Show it to display "no tasks" message
downloadAnalysisPdfBtn.style.display = 'none';
readyTasksListUL.innerHTML = '
No tasks defined.';
return;
}
// Re-run cycle detection on the whole graph for good measure (though input validation should catch most)
let globalCycleFound = false;
for (const task of tasks) {
if (hasCircularDependency(task.id, task.predecessorIds)) {
// hasCircularDependency already updates the warning text and section display
globalCycleFound = true;
break; // Stop if one cycle is found
}
}
// If no cycle on individual checks, ensure warning is hidden
if(!globalCycleFound) circularWarningSection.style.display = 'none';
let readyCount = 0, blockedCount = 0, inProgressCount = 0, completedCount = 0;
tasks.forEach(task => {
const readiness = getTaskReadiness(task, tasks);
const li = document.createElement('li');
li.textContent = task.description;
if (readiness.state === 'Ready to Start') {
readyTasksListUL.appendChild(li); readyCount++;
} else if (readiness.state === 'Blocked') {
const blockerInfo = document.createElement('div');
blockerInfo.className = 'blocker-tasks';
blockerInfo.textContent = `Blocked by: ${readiness.blockers.join(', ')}`;
li.appendChild(blockerInfo);
blockedTasksListUL.appendChild(li); blockedCount++;
} else if (readiness.state === 'In Progress (Ready)') {
inProgressTasksListUL.appendChild(li); inProgressCount++;
} else if (readiness.state === 'done') {
completedTasksListUL.appendChild(li); completedCount++;
}
});
if(readyCount === 0) readyTasksListUL.innerHTML = '
No tasks are currently ready to start.';
if(blockedCount === 0) blockedTasksListUL.innerHTML = '
No tasks are currently blocked.';
if(inProgressCount === 0) inProgressTasksListUL.innerHTML = '
No tasks are currently in progress.';
if(completedCount === 0) completedTasksListUL.innerHTML = '
No tasks have been completed.';
analysisResultsDiv.style.display = 'block';
downloadAnalysisPdfBtn.style.display = 'block';
});
// --- PDF Download ---
downloadAnalysisPdfBtn.addEventListener('click', () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF('p', 'pt', 'a4');
const margin = 40; let yPos = margin;
const pageWidth = doc.internal.pageSize.getWidth();
doc.setFontSize(18);
doc.setTextColor(varToRGB('--primary-color').r, varToRGB('--primary-color').g, varToRGB('--primary-color').b);
doc.text('Task Dependency Analysis Report', pageWidth / 2, yPos, { align: 'center' });
yPos += 25;
doc.setFontSize(10);
doc.setTextColor(100);
doc.text(`Report Generated: ${new Date().toLocaleString()}`, pageWidth / 2, yPos, { align: 'center' });
yPos += 30;
if (tasks.length === 0) {
doc.text('No tasks defined for this report.', margin, yPos);
doc.save('Task_Dependency_Analysis_Empty.pdf');
return;
}
function addTaskListToPdf(title, ulElement, titleColor = {r:50,g:50,b:50}) {
if (yPos > doc.internal.pageSize.getHeight() - 60 && ulElement.children.length > (ulElement.children[0]?.textContent.startsWith("No tasks") ? 0 : 1) ) {
doc.addPage(); yPos = margin;
}
doc.setFontSize(14);
doc.setTextColor(titleColor.r, titleColor.g, titleColor.b);
doc.text(title, margin, yPos);
yPos += 20;
doc.setFontSize(10);
doc.setTextColor(80);
if (ulElement.children.length === 0 || (ulElement.children.length === 1 && ulElement.children[0].textContent.startsWith("No tasks"))) {
doc.text("None in this category.", margin + 5, yPos);
yPos += 15;
} else {
Array.from(ulElement.children).forEach(li => {
const taskText = li.childNodes[0].textContent; // Main task description
const blockerDiv = li.querySelector('.blocker-tasks');
const fullText = blockerDiv ? `${taskText} (${blockerDiv.textContent})` : taskText;
const lines = doc.splitTextToSize(`• ${fullText}`, pageWidth - (2 * margin) - 5);
if (yPos + (lines.length * 12) > doc.internal.pageSize.getHeight() - margin) {
doc.addPage(); yPos = margin;
}
doc.text(lines, margin + 5, yPos);
yPos += (lines.length * 12) + 3;
});
}
yPos += 10;
}
if (circularWarningSection.style.display !== 'none') {
doc.setFontSize(12); doc.setTextColor(varToRGB('--danger-color').r, varToRGB('--danger-color').g, varToRGB('--danger-color').b);
doc.text("WARNING: Circular Dependency Detected!", margin, yPos); yPos += 15;
doc.setFontSize(10);
const warnLines = doc.splitTextToSize(circularWarningText.textContent, pageWidth - (2 * margin));
doc.text(warnLines, margin, yPos); yPos += (warnLines.length * 12) + 10;
}
addTaskListToPdf("Ready to Start Tasks", readyTasksListUL, varToRGB('--accent-color'));
addTaskListToPdf("Blocked Tasks", blockedTasksListUL, varToRGB('--danger-color'));
addTaskListToPdf("In Progress Tasks", inProgressTasksListUL, varToRGB('--warning-color'));
addTaskListToPdf("Completed Tasks", completedTasksListUL, varToRGB('--accent-color'));
// Detailed list of all tasks and their direct predecessors
if (yPos > doc.internal.pageSize.getHeight() - 80) { doc.addPage(); yPos = margin; }
doc.setFontSize(14); doc.setTextColor(varToRGB('--primary-color').r, varToRGB('--primary-color').g, varToRGB('--primary-color').b);
doc.text("All Tasks & Predecessors:", margin, yPos); yPos += 20;
const allTasksBody = tasks.map(task => {
const predecessors = task.predecessorIds.map(pId => tasks.find(t => t.id === pId)?.description || 'Unknown').join(', ') || 'None';
const readiness = getTaskReadiness(task, tasks);
return [task.description, STATUS_LABELS[task.status], predecessors, readiness.state];
});
if(allTasksBody.length > 0){
doc.autoTable({
startY: yPos,
head: [['Task Description', 'Status', 'Predecessors', 'Readiness']],
body: allTasksBody, theme: 'grid',
headStyles: { fillColor: varToRGB('--primary-color', true), textColor: varToRGB('--light-text', true) },
columnStyles: {0:{cellWidth:150}, 2:{cellWidth:150}}
});
} else {
doc.text("No tasks to list.", margin, yPos);
}
doc.save('Task_Dependency_Analysis.pdf');
});
function varToRGB(varName, asArray = false) {
let colorHex = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
if (!colorHex.startsWith('#')) { colorHex = '#000000'; }
const r = parseInt(colorHex.slice(1, 3), 16);
const g = parseInt(colorHex.slice(3, 5), 16);
const b = parseInt(colorHex.slice(5, 7), 16);
return asArray ? [r,g,b] : {r,g,b};
}
// --- Initializations ---
renderTasksListWithDetails(); // Also populates predecessor select in form
resetDepTaskForm(); // Ensure form is clean and predecessor select is populated correctly at start
if (tasks.length > 0) analyzeDependenciesBtn.click(); // Initial analysis if tasks exist
})();