Smart Task Interdependency Analyzer

Smart Task Interdependency Analyzer

Add New Task

Task List with Dependencies

    Depends on: ${predecessorsText} ${readiness.blockers.length > 0 ? `
    Blocked by: ${readiness.blockers.join(', ')}` : ''}

    `; 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 })();
    Scroll to Top