Workload Overlap Detector

Setup Resources and Tasks/Events

Manage Resources

Available Resources:

    Add Task/Event


    All Scheduled Tasks/Events

    TitleResourceStartEndDescriptionActions
    No tasks/events scheduled yet.

    Workload Overlap Detection

    Detected Overlaps

    Task 2: ${wodEscapeHtml(overlap.taskB.title)} (${overlap.taskB.start.toLocaleString()} - ${overlap.taskB.end.toLocaleString()})

    Overlapping Period: ${overlap.overlapStart.toLocaleString()} - ${overlap.overlapEnd.toLocaleString()}

    `; wodOverlapListEl.appendChild(itemDiv); }); } function wodDisplayFullScheduleWithHighlights(){ wodHighlightedScheduleBodyEl.innerHTML = ''; const allTaskIdsInOverlaps = new Set(); wodDetectedOverlaps.forEach(ov => { allTaskIdsInOverlaps.add(ov.taskA.id); allTaskIdsInOverlaps.add(ov.taskB.id); }); wodTasks.sort((a,b) => new Date(a.startDateTime) - new Date(b.startDateTime)).forEach(task => { const row = wodHighlightedScheduleBodyEl.insertRow(); if(allTaskIdsInOverlaps.has(task.id)){ row.classList.add('overlapping-task'); } row.insertCell().textContent = task.title; row.insertCell().textContent = task.resource; row.insertCell().textContent = new Date(task.startDateTime).toLocaleString(); row.insertCell().textContent = new Date(task.endDateTime).toLocaleString(); row.insertCell().textContent = task.description.substring(0,30) + (task.description.length > 30 ? '...' : ''); }); wodFullScheduleWithHighlightsEl.style.display = wodTasks.length > 0 ? 'block' : 'none'; } function wodEscapeHtml(unsafe) { if (typeof unsafe !== 'string') return ''; return unsafe.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } // PDF Download function wodDownloadPDF() { if (wodTasks.length === 0 && wodDetectedOverlaps.length === 0) { alert("No data to export."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('p', 'pt', 'a4'); const primaryColorPDF = getComputedStyle(document.documentElement).getPropertyValue('--wod-primary-color').trim(); const overlapColorPDF = getComputedStyle(document.documentElement).getPropertyValue('--wod-overlap-color').trim(); const whiteColorPDF = getComputedStyle(document.documentElement).getPropertyValue('--wod-text-color-light').trim(); const textColorPDF = getComputedStyle(document.documentElement).getPropertyValue('--wod-text-color-dark').trim(); let yPos = 40; const leftMargin = 40; const contentWidth = doc.internal.pageSize.getWidth() - (2 * leftMargin); doc.setFontSize(18); doc.setTextColor(primaryColorPDF); doc.text("Workload Overlap Report", doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' }); yPos += 25; doc.setFontSize(10); doc.setTextColor(textColorPDF); doc.text(`Report Generated: ${new Date().toLocaleString()}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' }); yPos += 30; if (wodDetectedOverlaps.length > 0) { doc.setFontSize(14); doc.setTextColor(overlapColorPDF); doc.text("Detected Overlaps:", leftMargin, yPos); yPos += 20; wodDetectedOverlaps.forEach(overlap => { yPos = checkPdfPageBreak(doc, yPos, 70); // Estimate space for an overlap item doc.setFontSize(10); doc.setFont(undefined, 'bold'); doc.setTextColor(textColorPDF); doc.text(`Resource: ${wodEscapeHtml(overlap.resource)}`, leftMargin, yPos); yPos += 12; doc.setFont(undefined, 'normal'); doc.text(`Task 1: ${wodEscapeHtml(overlap.taskA.title)} (${overlap.taskA.start.toLocaleString()} - ${overlap.taskA.end.toLocaleString()})`, leftMargin + 10, yPos); yPos += 12; doc.text(`Task 2: ${wodEscapeHtml(overlap.taskB.title)} (${overlap.taskB.start.toLocaleString()} - ${overlap.taskB.end.toLocaleString()})`, leftMargin + 10, yPos); yPos += 12; doc.setTextColor(overlapColorPDF); doc.text(`Overlap: ${overlap.overlapStart.toLocaleString()} - ${overlap.overlapEnd.toLocaleString()}`, leftMargin + 10, yPos); yPos += 18; doc.setTextColor(textColorPDF); }); } else { doc.setFontSize(12); doc.setTextColor(primaryColorPDF); // Use a less alarming color for no overlaps doc.text("No workload overlaps detected.", leftMargin, yPos); yPos += 20; } // Full Schedule with Highlights if (wodTasks.length > 0) { yPos = checkPdfPageBreak(doc, yPos, 50); doc.setFontSize(14); doc.setFont(undefined, 'bold'); doc.setTextColor(primaryColorPDF); doc.text("Full Schedule:", leftMargin, yPos); yPos += 22; const tableColumns = ["Title", "Resource", "Start", "End", "Description"]; const tableRows = []; const allTaskIdsInOverlaps = new Set(); wodDetectedOverlaps.forEach(ov => { allTaskIdsInOverlaps.add(ov.taskA.id); allTaskIdsInOverlaps.add(ov.taskB.id); }); wodTasks.sort((a,b) => new Date(a.startDateTime) - new Date(b.startDateTime)).forEach(task => { tableRows.push([ task.title, task.resource, new Date(task.startDateTime).toLocaleString(), new Date(task.endDateTime).toLocaleString(), task.description.substring(0,40) + (task.description.length > 40 ? "..." : "") ]); }); doc.autoTable({ head: [tableColumns], body: tableRows, startY: yPos, theme: 'grid', headStyles: { fillColor: primaryColorPDF, textColor: whiteColorPDF, fontSize: 9, fontStyle: 'bold' }, styles: { fontSize: 8, cellPadding: 3, textColor: textColorPDF, overflow: 'linebreak' }, columnStyles: { 0: {cellWidth: 120}, 1: {cellWidth:80}, 4:{cellWidth: 'auto'} }, didParseCell: function (data) { // Highlight rows of overlapping tasks const taskInRow = wodTasks.sort((a,b) => new Date(a.startDateTime) - new Date(b.startDateTime))[data.row.index]; if (taskInRow && allTaskIdsInOverlaps.has(taskInRow.id)) { data.cell.styles.fillColor = '#ffebee'; // Light red data.cell.styles.textColor = overlapColorPDF; } } }); } doc.save("Workload_Overlap_Report.pdf"); } function checkPdfPageBreak(doc, currentY, spaceNeeded) { if (currentY + spaceNeeded > doc.internal.pageSize.getHeight() - 40) { doc.addPage(); return 40; } return currentY; }
    Scroll to Top