Task Overlap Detection Tool

Task Overlap Detection Tool

Task List

Detected Overlaps:

Understanding & Resolving Task Overlaps

1. Input Your Tasks
Use the "Add New Task" button to enter each task with its name, start date & time, and end date & time. Providing an assignee or category can help add context.
2. Detect Overlaps
After adding your tasks, click the "Detect Overlaps" button. The tool will analyze all tasks and highlight those that have scheduling conflicts. A list of specific overlaps will also be displayed below the task list.
3. Review Highlighted Tasks
Tasks involved in an overlap will be visually marked (e.g., with a red highlight or border). This gives you a quick visual cue in your task list.
4. Analyze the Overlap List
The "Detected Overlaps" section will detail which specific tasks conflict with each other and their respective time slots. For example: "Task A (9 AM - 11 AM) overlaps with Task B (10 AM - 12 PM)."
5. Resolving Overlaps
Once overlaps are identified, consider these strategies:
  • Reschedule: Adjust the start or end times of conflicting tasks so they no longer overlap. Use the "Edit" button on a task.
  • Delegate: If tasks are assigned and the person is overbooked, can one of the tasks be delegated to someone else?
  • Re-prioritize: Decide which task is more critical. Can the less critical task be postponed or shortened?
  • Break Down Tasks: If a long task is causing many overlaps, see if it can be broken into smaller sub-tasks that can be scheduled more flexibly.
  • Allow Overlap (Consciously): In rare cases, you might decide that a minor overlap is acceptable (e.g., starting a new task while finishing up an old one). If so, acknowledge it.
6. Re-check
After making adjustments, click "Detect Overlaps" again to ensure the conflicts are resolved.
×

Add New Task

Start: ${startDateTime ? todFormatDateTimeForDisplay(startDateTime) : 'N/A'}
End: ${endDateTime ? todFormatDateTimeForDisplay(endDateTime) : 'N/A'}

${task.assignee ? `

Assignee/Category: ${task.assignee}

` : ''}
`; todTaskListEl.appendChild(taskEl); }); } // --- Modal & Task CRUD --- window.todOpenTaskModal = function(taskId = null) { todTaskForm.reset(); todTaskIdInput.value = ''; const defaultDate = todFormatDateTimeInput(new Date(2025, 4, 14)); // May 14, 2025 if (taskId) { const task = todTasks.find(t => t.id === taskId); if (task) { todModalTitleEl.textContent = 'Edit Task'; todTaskIdInput.value = task.id; todTaskNameInput.value = task.name; todStartDateInput.value = task.startDate; todStartTimeInput.value = task.startTime; todEndDateInput.value = task.endDate; todEndTimeInput.value = task.endTime; todTaskAssigneeInput.value = task.assignee || ''; } } else { todModalTitleEl.textContent = 'Add New Task'; todStartDateInput.value = defaultDate.date; todStartTimeInput.value = "09:00"; // Default start time todEndDateInput.value = defaultDate.date; todEndTimeInput.value = "10:00"; // Default end time } todTaskModalEl.style.display = 'block'; } window.todCloseModal = (modalId) => { const modal = document.getElementById(modalId); if(modal) modal.style.display = 'none'; } // Helper to format date for input fields function todFormatDateTimeInput(dateObj) { const year = dateObj.getFullYear(); const month = String(dateObj.getMonth() + 1).padStart(2, '0'); const day = String(dateObj.getDate()).padStart(2, '0'); const hours = String(dateObj.getHours()).padStart(2, '0'); const minutes = String(dateObj.getMinutes()).padStart(2, '0'); return { date: `${year}-${month}-${day}`, time: `${hours}:${minutes}` }; } if(todTaskForm) { todTaskForm.addEventListener('submit', function(e) { e.preventDefault(); const taskId = todTaskIdInput.value; const taskData = { name: todTaskNameInput.value.trim(), startDate: todStartDateInput.value, startTime: todStartTimeInput.value, endDate: todEndDateInput.value, endTime: todEndTimeInput.value, assignee: todTaskAssigneeInput.value.trim() || '' }; if (!taskData.name || !taskData.startDate || !taskData.startTime || !taskData.endDate || !taskData.endTime) { alert("Task name and all date/time fields are required."); return; } const startDateTime = todGetDateTime(taskData.startDate, taskData.startTime); const endDateTime = todGetDateTime(taskData.endDate, taskData.endTime); if (!startDateTime || !endDateTime || endDateTime <= startDateTime) { alert("End date/time must be after start date/time."); return; } if (taskId) { // Editing const index = todTasks.findIndex(t => t.id === taskId); if (index > -1) { todTasks[index] = { ...todTasks[index], ...taskData }; } } else { // Adding new const newTask = { id: 'tod-task-' + Date.now(), ...taskData }; todTasks.push(newTask); } todSaveTasks(); todRenderTaskList(); // Re-render to show changes todDisplayOverlaps([]); // Clear old overlap results after edit/add todCloseModal('todTaskModal'); }); } window.todDeleteTask = function(taskId) { if (confirm('Are you sure you want to delete this task?')) { todTasks = todTasks.filter(t => t.id !== taskId); todSaveTasks(); todRenderTaskList(); todDisplayOverlaps([]); // Clear overlaps } } window.todClearAllTasks = function() { if (confirm('Are you sure you want to delete ALL tasks? This cannot be undone.')) { todTasks = []; todOverlaps = []; todSaveTasks(); todRenderTaskList(); todDisplayOverlaps([]); } } // --- Overlap Detection --- window.todDetectAndDisplayOverlaps = function() { todOverlaps = []; const highlightedTaskIds = new Set(); // First, validate all task ranges let hasInvalidTasks = false; todTasks.forEach(task => { const start = todGetDateTime(task.startDate, task.startTime); const end = todGetDateTime(task.endDate, task.endTime); if (!start || !end || end <= start) { task.hasError = true; // Mark task with error hasInvalidTasks = true; } else { task.hasError = false; } }); for (let i = 0; i < todTasks.length; i++) { for (let j = i + 1; j < todTasks.length; j++) { const taskA = todTasks[i]; const taskB = todTasks[j]; // Skip check if either task has an invalid range itself if (taskA.hasError || taskB.hasError) continue; const startA = todGetDateTime(taskA.startDate, taskA.startTime); const endA = todGetDateTime(taskA.endDate, taskA.endTime); const startB = todGetDateTime(taskB.startDate, taskB.startTime); const endB = todGetDateTime(taskB.endDate, taskB.endTime); if (startA && endA && startB && endB) { // Overlap condition: (StartA < EndB) and (EndA > StartB) if (startA < endB && endA > startB) { todOverlaps.push({ task1: taskA, task2: taskB }); highlightedTaskIds.add(taskA.id); highlightedTaskIds.add(taskB.id); } } } } todRenderTaskList(highlightedTaskIds); // Re-render list with highlights todDisplayOverlaps(todOverlaps); if (hasInvalidTasks) { alert("Some tasks have invalid time ranges (end before start) and were excluded from overlap checks. Please correct them."); } } function todDisplayOverlaps(overlaps) { if (!todOverlapListEl) return; todOverlapListEl.innerHTML = ''; if (overlaps.length === 0) { // Check if tasks exist before saying "No overlaps" if (todTasks.some(task => !task.hasError)) { // Only say "no overlaps" if there are valid tasks to check todOverlapListEl.innerHTML = '

No overlaps detected among valid tasks.

'; } else if (todTasks.length > 0 && todTasks.every(task => task.hasError)) { todOverlapListEl.innerHTML = '

All tasks have invalid time ranges. Cannot check for overlaps.

'; } else { todOverlapListEl.innerHTML = '

Add tasks and click "Detect Overlaps".

'; } return; } overlaps.forEach((pair, index) => { const overlapDiv = document.createElement('div'); overlapDiv.className = 'tod-overlap-pair'; const startA = todGetDateTime(pair.task1.startDate, pair.task1.startTime); const endA = todGetDateTime(pair.task1.endDate, pair.task1.endTime); const startB = todGetDateTime(pair.task2.startDate, pair.task2.startTime); const endB = todGetDateTime(pair.task2.endDate, pair.task2.endTime); overlapDiv.innerHTML = ` Overlap ${index + 1}:
Task "${pair.task1.name}" (${startA ? todFormatDateTimeForDisplay(startA) : 'N/A'} - ${endA ? todFormatDateTimeForDisplay(endA) : 'N/A'})
overlaps with
Task "${pair.task2.name}" (${startB ? todFormatDateTimeForDisplay(startB) : 'N/A'} - ${endB ? todFormatDateTimeForDisplay(endB) : 'N/A'}) `; todOverlapListEl.appendChild(overlapDiv); }); } // --- PDF Export --- if(todDownloadPdfBtn) { todDownloadPdfBtn.addEventListener('click', function() { if (todTasks.length === 0) { alert("No tasks to export."); return; } // Ensure overlaps are up-to-date before printing // todDetectAndDisplayOverlaps(); // User should click this first for UI consistency const { jsPDF } = window.jspdf; const doc = new jsPDF(); const reportDate = new Date().toLocaleDateString(); const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--tod-primary-color').trim(); doc.setFontSize(18); doc.setTextColor(primaryColor); doc.text("Task Overlap Report", 14, 22); doc.setFontSize(10); doc.setTextColor(100); doc.text(`Report Date: ${reportDate}`, 14, 30); let yPos = 40; doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text("All Tasks:", 14, yPos); yPos += 7; const taskTableColumn = ["Name", "Start Time", "End Time", "Assignee/Category", "Status"]; const taskTableRows = []; const sortedTasksForPdf = [...todTasks].sort((a,b) => { const aStart = todGetDateTime(a.startDate, a.startTime); const bStart = todGetDateTime(b.startDate, b.startTime); if (!aStart || !bStart) return 0; return aStart - bStart; }); sortedTasksForPdf.forEach(task => { const start = todGetDateTime(task.startDate, task.startTime); const end = todGetDateTime(task.endDate, task.endTime); let status = "Valid"; if (start && end && end <= start) { status = "Invalid Range"; } taskTableRows.push([ task.name, start ? todFormatDateTimeForDisplay(start) : "Invalid", end ? todFormatDateTimeForDisplay(end) : "Invalid", task.assignee || "", status ]); }); doc.autoTable({ head: [taskTableColumn], body: taskTableRows, startY: yPos, theme: 'striped', headStyles: { fillColor: getComputedStyle(document.documentElement).getPropertyValue('--tod-secondary-color').trim() }, styles: { fontSize: 9, cellPadding: 1.5, overflow: 'linebreak' }, didParseCell: function (data) { if (data.column.dataKey === 4 && data.cell.raw === "Invalid Range") { // Column index for Status data.cell.styles.textColor = [220, 53, 69]; // Red color for invalid range } } }); yPos = doc.lastAutoTable.finalY + 15; if (todOverlaps.length > 0) { if (yPos > 250) { doc.addPage(); yPos = 20; } doc.setFontSize(12); doc.setTextColor(primaryColor); doc.text("Detected Overlaps:", 14, yPos); yPos += 7; doc.setFontSize(9); doc.setTextColor(50); todOverlaps.forEach((pair, index) => { if (yPos > 270) { doc.addPage(); yPos = 20; } const startA = todGetDateTime(pair.task1.startDate, pair.task1.startTime); const endA = todGetDateTime(pair.task1.endDate, pair.task1.endTime); const startB = todGetDateTime(pair.task2.startDate, pair.task2.startTime); const endB = todGetDateTime(pair.task2.endDate, pair.task2.endTime); let textLines = []; textLines.push(`Overlap ${index + 1}:`); textLines.push(` Task "${pair.task1.name}" (${startA ? todFormatDateTimeForDisplay(startA) : 'N/A'} - ${endA ? todFormatDateTimeForDisplay(endA) : 'N/A'})`); textLines.push(` overlaps with Task "${pair.task2.name}" (${startB ? todFormatDateTimeForDisplay(startB) : 'N/A'} - ${endB ? todFormatDateTimeForDisplay(endB) : 'N/A'})`); doc.text(textLines, 14, yPos); yPos += (textLines.length * 5) + 3; // Adjust spacing based on lines }); } else { if (yPos > 270) { doc.addPage(); yPos = 20; } doc.setFontSize(10); doc.setTextColor(50); doc.text("No overlaps were detected among valid tasks.", 14, yPos); } doc.save(`Task_Overlap_Report_${reportDate.replace(/\//g, '-')}.pdf`); }); } // --- Initialization --- document.addEventListener('DOMContentLoaded', () => { todLoadTasks(); const defaultDate = todFormatDateTimeInput(new Date(2025, 4, 14)); // Default: May 14, 2025 if(todStartDateInput) todStartDateInput.value = defaultDate.date; if(todEndDateInput) todEndDateInput.value = defaultDate.date; const firstTabButton = document.querySelector('#taskOverlapDetectionTool .tod-tab-button'); if (firstTabButton) { firstTabButton.click(); } // Close modal if clicked outside window.addEventListener('click', function(event) { const modal = document.getElementById('todTaskModal'); if (modal && event.target == modal) { todCloseModal('todTaskModal'); } }); });
Scroll to Top