Due: ${task.dueDate ? new Date(task.dueDate+'T00:00:00Z').toLocaleDateString() : 'N/A'} | Assigned: ${atrsEscapeHtml(task.assignedTo) || 'N/A'}
Checklist: ${atrsCalculateChecklistCompletion(task.checklist)}% complete
`;
atrsTaskTrackingAreaEl.appendChild(taskDiv);
});
}
function atrsUpdateTaskStatus(taskId, newStatus) {
const task = atrsTasksData.find(t => t.id === taskId);
if (task) {
task.status = newStatus;
atrsSaveDataToStorage();
atrsRenderMainTaskList();
const taskDivInTracking = Array.from(document.getElementById('atrsTaskTrackingArea').children).find(div => div.querySelector('h4')?.textContent.includes(`(ID: ${taskId})`));
if(taskDivInTracking) {
taskDivInTracking.className = 'atrs-review-item'; // Reset classes
taskDivInTracking.classList.add(`status-${newStatus.toLowerCase().replace(/\s+/g, '')}`);
}
// Clear previous system notes when status is manually changed by user
const reviewNoteElTrack = document.getElementById(`reviewNoteTrack-${taskId}`);
if(reviewNoteElTrack) reviewNoteElTrack.textContent = '';
}
}
function atrsPerformGlobalReview() {
if(!atrsReviewResultItemsEl || !atrsNoReviewIssuesMessageEl || !atrsReviewResultsDisplayAreaEl || !atrsDownloadReportBtnEl) return;
atrsReviewResultItemsEl.innerHTML = '';
let issuesFound = 0;
const today = new Date(); today.setHours(0,0,0,0);
atrsTasksData.forEach(task => {
let notes = [];
const checklistCompletion = atrsCalculateChecklistCompletion(task.checklist);
if (task.dueDate) {
const dueDate = new Date(task.dueDate); // Already YYYY-MM-DD, new Date will parse as local
dueDate.setHours(0,0,0,0);
if (dueDate < today && task.status !== 'Completed') {
notes.push("Task is OVERDUE.");
}
}
if (task.status === 'Blocked') notes.push("Task is BLOCKED. Requires attention.");
if (task.status === 'Pending Review' && checklistCompletion < 100) notes.push("'Pending Review' but checklist is not 100% complete.");
if (task.status === 'Completed' && checklistCompletion < 100) notes.push("'Completed' but checklist was not 100% complete (Potential Discrepancy).");
if (task.status !== 'Completed' && checklistCompletion === 100 && task.status !== 'Pending Review') notes.push("Checklist 100% complete, but task not 'Completed' or 'Pending Review'. Consider updating status.");
const reviewNoteElTrack = document.getElementById(`reviewNoteTrack-${task.id}`);
if(reviewNoteElTrack) reviewNoteElTrack.textContent = notes.join(' ');
if (notes.length > 0) {
issuesFound++;
const itemDiv = document.createElement('div');
itemDiv.classList.add('atrs-review-item');
if(notes.some(n => n.includes("OVERDUE"))) itemDiv.classList.add('status-overdue');
else if(notes.some(n => n.includes("BLOCKED"))) itemDiv.classList.add('status-blocked');
else if(notes.some(n => n.includes("Pending Review"))) itemDiv.classList.add('status-pendingreview', 'incomplete');
else if(notes.some(n => n.includes("Completed") && n.includes("Discrepancy"))) itemDiv.classList.add('status-completed', 'discrepancy');
else if(notes.some(n => n.includes("Consider updating status"))) itemDiv.classList.add('status-completed','ok');
itemDiv.innerHTML = `
${atrsEscapeHtml(task.title)} (ID: ${task.id} | Status: ${task.status})
${notes.map(atrsEscapeHtml).join('
')}
`;
atrsReviewResultItemsEl.appendChild(itemDiv);
}
});
atrsNoReviewIssuesMessageEl.style.display = issuesFound === 0 ? 'block' : 'none';
atrsReviewResultsDisplayAreaEl.style.display = 'block';
atrsDownloadReportBtnEl.style.display = 'block';
}
function atrsDownloadReviewReport() {
if (atrsReviewResultsDisplayAreaEl.style.display === 'none' && atrsTasksData.length === 0) {
alert("Please perform a review or add tasks first."); return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF('p', 'pt', 'a4');
const primaryColorPDF = getComputedStyle(document.documentElement).getPropertyValue('--atrs-primary-color').trim();
const whiteColorPDF = getComputedStyle(document.documentElement).getPropertyValue('--atrs-text-color-light').trim();
const textColorPDF = getComputedStyle(document.documentElement).getPropertyValue('--atrs-text-color-dark').trim();
const todayStr = new Date().toLocaleDateString();
let yPos = 40; const leftMargin = 40; const contentWidth = doc.internal.pageSize.getWidth() - (2 * leftMargin);
doc.setFontSize(18); doc.setTextColor(primaryColorPDF);
doc.text("Automated Task Review Report", doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 20;
doc.setFontSize(10); doc.setTextColor(textColorPDF);
doc.text(`Report Date: ${todayStr}`, doc.internal.pageSize.getWidth() / 2, yPos, { align: 'center' });
yPos += 30;
const reviewItemNodes = atrsReviewResultItemsEl.children;
if (reviewItemNodes.length > 0) {
doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.setTextColor(primaryColorPDF);
doc.text("Review Findings / Tasks Needing Attention:", leftMargin, yPos); yPos += 20;
doc.setFont(undefined, 'normal');
Array.from(reviewItemNodes).forEach(itemNode => {
const titleText = itemNode.querySelector('h4')?.textContent || "Task";
const notesHTML = itemNode.querySelector('p.system-note')?.innerHTML || "No specific notes.";
const notesText = notesHTML.replace(/
/gi, "\n"); // Convert
to newlines for PDF
yPos = checkPdfPageBreak(doc, yPos, 45); // Estimate space needed
doc.setFontSize(10); doc.setFont(undefined,'bold');
doc.text(titleText, leftMargin, yPos); yPos += 14;
doc.setFontSize(9); doc.setFont(undefined,'italic');
const noteLines = doc.splitTextToSize(notesText, contentWidth - 10); // Small indent
doc.text(noteLines, leftMargin + 10, yPos);
yPos += (noteLines.length * 11) + 8;
});
} else if (atrsTasksData.length > 0) {
doc.setFontSize(12); doc.setTextColor(primaryColorPDF);
doc.text("No specific review issues flagged based on automated checks.", leftMargin, yPos); yPos += 20;
}
if (atrsTasksData.length > 0) {
yPos = checkPdfPageBreak(doc, yPos, 50);
doc.setFontSize(12); doc.setFont(undefined, 'bold'); doc.setTextColor(primaryColorPDF);
doc.text("All Tasks Overview:", leftMargin, yPos); yPos += 20;
const tableColumns = ["ID", "Task Title", "Due Date", "Assigned To", "Status", "Checklist %"];
const tableRows = atrsTasksData.map(task => [
task.id, task.title, task.dueDate ? new Date(task.dueDate+'T00:00:00Z').toLocaleDateString() : 'N/A',
task.assignedTo || 'N/A', task.status, `${atrsCalculateChecklistCompletion(task.checklist)}%`
]);
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: { 1: {cellWidth: 'auto'}}
});
} else {
yPos = checkPdfPageBreak(doc, yPos, 20);
doc.setFontSize(10); doc.text("No tasks were defined in the system for this report.", leftMargin, yPos);
}
doc.save(`TaskReviewReport_${todayStr.replace(/\//g,'-')}.pdf`);
}
function checkPdfPageBreak(doc, currentY, spaceNeeded) {
if (currentY + spaceNeeded > doc.internal.pageSize.getHeight() - 40) {
doc.addPage(); return 40;
}
return currentY;
}
function atrsEscapeHtml(unsafe) {
if (typeof unsafe !== 'string') return '';
return unsafe.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'");
}