1. Add New Task
2. Current Tasks & Dependencies
Optimized Task Sequence
| Order | Task Name | Duration | Priority | Dependencies | Start Time | End Time |
|---|
No tasks to optimize or sequence not generated yet.
×
Set Dependencies for: Task Name
Select tasks that must be completed BEFORE this task can start:
No tasks added yet.
'; return; } stsoTasks.forEach(task => { const item = document.createElement('div'); item.className = 'stso-task-list-item'; const details = document.createElement('div'); details.className = 'stso-task-details'; const depNames = task.dependencies.map(depId => stsoTasks.find(t => t.id === depId)?.name || 'Unknown Task').join(', '); details.innerHTML = ` ${task.name} (Duration: ${task.duration} ${task.durationUnit}, Priority: ${task.priority}) Dependencies: ${depNames || 'None'} `; const buttons = document.createElement('div'); const editButton = document.createElement('button'); editButton.className = 'stso-small-button'; editButton.textContent = 'Edit Deps'; editButton.onclick = () => stsoOpenDependencyModal(task.id); const deleteButton = document.createElement('button'); deleteButton.className = 'stso-small-button stso-danger-button'; deleteButton.textContent = 'Delete'; deleteButton.onclick = () => stsoDeleteTask(task.id); buttons.appendChild(editButton); buttons.appendChild(deleteButton); item.appendChild(details); item.appendChild(buttons); listContainer.appendChild(item); }); } function stsoDeleteTask(taskId) { if (!confirm('Are you sure you want to delete this task? This will also remove it from other tasks\' dependencies.')) return; stsoTasks = stsoTasks.filter(task => task.id !== taskId); // Remove this task from any other tasks' dependencies stsoTasks.forEach(task => { task.dependencies = task.dependencies.filter(depId => depId !== taskId); }); stsoRenderTaskList(); stsoOptimizedResult = null; // Invalidate previous optimization if (document.getElementById('stso-tab-optimize').classList.contains('active')) { document.getElementById('stso-optimized-sequence-body').innerHTML = ''; document.getElementById('stso-total-duration-display').textContent = ''; document.getElementById('stso-no-tasks-optimized-message').style.display = 'block'; document.getElementById('stso-optimization-message').style.display = 'none'; } } function stsoOpenDependencyModal(taskId) { stsoCurrentEditingTaskId = taskId; const task = stsoTasks.find(t => t.id === taskId); if (!task) return; document.getElementById('stso-modal-task-name').textContent = `Set Dependencies for: ${task.name}`; const checklist = document.getElementById('stso-dependency-checklist'); checklist.innerHTML = ''; stsoTasks.filter(t => t.id !== taskId).forEach(otherTask => { const li = document.createElement('li'); const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = `dep-checkbox-${otherTask.id}`; checkbox.value = otherTask.id; checkbox.checked = task.dependencies.includes(otherTask.id); const label = document.createElement('label'); label.htmlFor = `dep-checkbox-${otherTask.id}`; label.textContent = otherTask.name; li.appendChild(checkbox); li.appendChild(label); checklist.appendChild(li); }); document.getElementById('stso-dependency-modal').style.display = 'block'; } function stsoCloseDependencyModal() { document.getElementById('stso-dependency-modal').style.display = 'none'; stsoCurrentEditingTaskId = null; } function stsoSaveDependencies() { const task = stsoTasks.find(t => t.id === stsoCurrentEditingTaskId); if (!task) return; const newDependencies = []; document.querySelectorAll('#stso-dependency-checklist input[type="checkbox"]:checked').forEach(cb => { newDependencies.push(parseInt(cb.value)); }); task.dependencies = newDependencies; stsoRenderTaskList(); stsoCloseDependencyModal(); stsoOptimizedResult = null; // Invalidate previous optimization if dependencies change } function stsoOptimizeSequence() { if (stsoTasks.length === 0) { stsoShowMessage('info', 'No tasks to optimize. Please add tasks first.', 'stso-optimization-message'); document.getElementById('stso-optimized-sequence-body').innerHTML = ''; document.getElementById('stso-no-tasks-optimized-message').style.display = 'block'; document.getElementById('stso-total-duration-display').textContent = ''; return; } document.getElementById('stso-no-tasks-optimized-message').style.display = 'none'; // Graph representation const adj = new Map(); // task_id -> [dependent_task_ids] const inDegree = new Map(); // task_id -> count const taskMap = new Map(stsoTasks.map(task => [task.id, task])); stsoTasks.forEach(task => { adj.set(task.id, []); inDegree.set(task.id, 0); }); stsoTasks.forEach(task => { task.dependencies.forEach(depId => { if (adj.has(depId) && taskMap.has(task.id)) { // Ensure both exist adj.get(depId).push(task.id); inDegree.set(task.id, (inDegree.get(task.id) || 0) + 1); } }); }); // Ready queue: Min-heap based on (1. Priority DESC, 2. Duration ASC) // Higher priority value means higher priority. Lower duration means higher priority. const readyQueue = stsoTasks.filter(task => inDegree.get(task.id) === 0) .sort((a, b) => { if (PRIORITY_VALUES[b.priority] !== PRIORITY_VALUES[a.priority]) { return PRIORITY_VALUES[b.priority] - PRIORITY_VALUES[a.priority]; } return a.duration - b.duration; }); const optimizedSequence = []; let currentTime = 0; const taskEndTimes = new Map(); // To track when prerequisites finish while (readyQueue.length > 0) { const currentTaskDetails = readyQueue.shift(); // Get highest priority const currentTask = taskMap.get(currentTaskDetails.id); let taskStartTime = 0; currentTask.dependencies.forEach(depId => { taskStartTime = Math.max(taskStartTime, taskEndTimes.get(depId) || 0); }); // If tasks are strictly sequential in THIS final list and not just by deps: // taskStartTime = Math.max(taskStartTime, currentTime); optimizedSequence.push({ ...currentTask, startTime: taskStartTime, endTime: taskStartTime + currentTask.duration }); taskEndTimes.set(currentTask.id, taskStartTime + currentTask.duration); // For strictly sequential total time based on THIS list (not parallel execution): // currentTime = taskStartTime + currentTask.duration; (adj.get(currentTask.id) || []).forEach(dependentTaskId => { inDegree.set(dependentTaskId, inDegree.get(dependentTaskId) - 1); if (inDegree.get(dependentTaskId) === 0) { const dependentTask = taskMap.get(dependentTaskId); // Add to readyQueue and re-sort (simple way for now, a proper P-Queue would be better) readyQueue.push(dependentTask); readyQueue.sort((a, b) => { if (PRIORITY_VALUES[b.priority] !== PRIORITY_VALUES[a.priority]) { return PRIORITY_VALUES[b.priority] - PRIORITY_VALUES[a.priority]; } return a.duration - b.duration; }); } }); } // Re-calculate start/end times for the final linear sequence for simpler display let linearCurrentTime = 0; const finalLinearSequence = optimizedSequence.map(task => { const startTime = linearCurrentTime; const endTime = startTime + task.duration; linearCurrentTime = endTime; return {...task, startTime, endTime}; }); const tableBody = document.getElementById('stso-optimized-sequence-body'); tableBody.innerHTML = ''; if (finalLinearSequence.length !== stsoTasks.length) { stsoShowMessage('error', 'Cycle detected in dependencies! Cannot create a valid sequence. Please review task dependencies.', 'stso-optimization-message'); stsoOptimizedResult = { error: "Cycle detected", tasks: [] }; // Store error state document.getElementById('stso-total-duration-display').textContent = ''; return; } stsoShowMessage('success', 'Task sequence optimized successfully!', 'stso-optimization-message'); stsoOptimizedResult = { tasks: finalLinearSequence, durationUnit: stsoTasks.length > 0 ? stsoTasks[0].durationUnit : "units" }; // Store for PDF let totalDuration = 0; finalLinearSequence.forEach((task, index) => { const row = tableBody.insertRow(); row.insertCell().textContent = index + 1; row.insertCell().textContent = task.name; row.insertCell().textContent = `${task.duration} ${task.durationUnit}`; const priorityCell = row.insertCell(); priorityCell.textContent = task.priority; priorityCell.className = `stso-priority-${task.priority}`; const depNames = task.dependencies.map(depId => taskMap.get(depId)?.name || 'Unknown').join(', ') || 'None'; row.insertCell().textContent = depNames; row.insertCell().textContent = task.startTime.toFixed(1); row.insertCell().textContent = task.endTime.toFixed(1); totalDuration = Math.max(totalDuration, task.endTime); }); document.getElementById('stso-total-duration-display').textContent = `Total Estimated Project Duration: ${totalDuration.toFixed(1)} ${stsoOptimizedResult.durationUnit}`; } function stsoShowMessage(type, message, elementId) { const msgElement = document.getElementById(elementId); msgElement.textContent = message; msgElement.className = `stso-message stso-message-${type}`; msgElement.style.display = 'block'; setTimeout(() => { msgElement.style.display = 'none'; }, 5000); } function stsoDownloadPDF() { if (!stsoOptimizedResult || stsoOptimizedResult.tasks.length === 0) { alert("No optimized sequence to download. Please generate the sequence first."); return; } if (stsoOptimizedResult.error) { alert(`Cannot generate PDF due to an error: ${stsoOptimizedResult.error}`); return; } const doc = new jsPDF('p', 'pt', 'a4'); // Using points for finer control, A4 size const FONT_FAMILY = "helvetica"; const ACCENT_COLOR_HEX = getComputedStyle(document.documentElement).getPropertyValue('--accent-color').trim(); const PRIMARY_TEXT_COLOR_HEX = getComputedStyle(document.documentElement).getPropertyValue('--primary-text').trim(); const BUTTON_TEXT_COLOR_HEX = getComputedStyle(document.documentElement).getPropertyValue('--button-text-color').trim(); let yPos = 40; const pageWidth = doc.internal.pageSize.getWidth(); const margin = 40; // Header doc.setFillColor(ACCENT_COLOR_HEX); doc.rect(0, 0, pageWidth, yPos + 5, 'F'); doc.setFont(FONT_FAMILY, "bold"); doc.setFontSize(18); doc.setTextColor(BUTTON_TEXT_COLOR_HEX); doc.text("Optimized Task Sequence Report", pageWidth / 2, yPos -5, { align: "center" }); yPos += 25; // Report Date doc.setFont(FONT_FAMILY, "normal"); doc.setFontSize(10); doc.setTextColor(PRIMARY_TEXT_COLOR_HEX); const reportDate = new Date().toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }); doc.text(`Report Generated: ${reportDate}`, margin, yPos); yPos += 20; const tableColumns = ["#", "Task Name", "Duration", "Priority", "Dependencies", "Start", "End"]; const tableRows = stsoOptimizedResult.tasks.map((task, index) => [ index + 1, task.name, `${task.duration} ${task.durationUnit}`, task.priority, task.dependencies.map(depId => stsoTasks.find(t => t.id === depId)?.name || '?').join(', ') || 'None', task.startTime.toFixed(1), task.endTime.toFixed(1) ]); doc.autoTable({ head: [tableColumns], body: tableRows, startY: yPos, theme: 'grid', margin: { left: margin, right: margin }, headStyles: { fillColor: ACCENT_COLOR_HEX, textColor: BUTTON_TEXT_COLOR_HEX, fontStyle: 'bold', fontSize: 9, halign: 'center' }, styles: { font: FONT_FAMILY, fontSize: 8, cellPadding: 3, overflow: 'linebreak' }, columnStyles: { 0: { cellWidth: 25, halign: 'center' }, // Order # 1: { cellWidth: 'auto' }, // Task Name 2: { cellWidth: 50, halign: 'right' }, // Duration 3: { cellWidth: 40, halign: 'center' }, // Priority 4: { cellWidth: 'auto' }, // Dependencies 5: { cellWidth: 35, halign: 'right' }, // Start 6: { cellWidth: 35, halign: 'right' } // End }, didParseCell: function (data) { // Color priority if (data.column.index === 3 && data.cell.section === 'body') { const priority = data.cell.raw; if (priority === "High") data.cell.styles.textColor = getComputedStyle(document.documentElement).getPropertyValue('--danger-color').trim(); if (priority === "Medium") data.cell.styles.textColor = getComputedStyle(document.documentElement).getPropertyValue('--info-color').trim(); if (priority === "Low") data.cell.styles.textColor = getComputedStyle(document.documentElement).getPropertyValue('--success-color').trim(); data.cell.styles.fontStyle = 'bold'; } } }); yPos = doc.lastAutoTable.finalY + 20; doc.setFont(FONT_FAMILY, "bold"); doc.setFontSize(11); doc.setTextColor(PRIMARY_TEXT_COLOR_HEX); const totalDurationValue = stsoOptimizedResult.tasks.length > 0 ? stsoOptimizedResult.tasks[stsoOptimizedResult.tasks.length - 1].endTime : 0; doc.text(`Total Estimated Project Duration: ${totalDurationValue.toFixed(1)} ${stsoOptimizedResult.durationUnit}`, margin, yPos, {align: 'right', maxWidth: pageWidth - (2*margin)}); yPos += 20; // Footer const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); const pageHeight = doc.internal.pageSize.getHeight(); doc.setLineWidth(0.5); doc.setDrawColor(ACCENT_COLOR_HEX); doc.line(margin, pageHeight - 30, pageWidth - margin, pageHeight - 30); doc.setFont(FONT_FAMILY, "normal"); doc.setFontSize(8); doc.setTextColor(PRIMARY_TEXT_COLOR_HEX); doc.text(`Smart Task Sequence Optimizer - Page ${i} of ${pageCount}`, pageWidth / 2, pageHeight - 20, { align: "center" }); } doc.save("Optimized_Task_Sequence.pdf"); }