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;
}