`;
ul.appendChild(li);
});
taskInstancesViewDiv.appendChild(ul);
// Add event listeners for Mark Done / Skip
document.querySelectorAll('.mark-done-btn, .skip-instance-btn').forEach(btn => {
btn.addEventListener('click', function() {
handleInstanceAction(this.dataset.seriesId, this.dataset.instanceDate, this.classList.contains('mark-done-btn'));
});
});
}
function handleInstanceAction(seriesId, instanceDateStr, isMarkDone) {
const seriesIndex = taskSeriesArray.findIndex(s => s.id === seriesId);
if (seriesIndex === -1) return;
const series = taskSeriesArray[seriesIndex];
// Ensure we are acting on the *current* nextDueDate of the series
if (series.nextDueDate !== instanceDateStr) {
// This can happen if user clicks on an older projected instance.
// For simplicity, "Mark Done" / "Skip" always operate on the series' current `nextDueDate`.
// A more complex system might allow marking historical instances done.
// For now, if it's not the current nextDueDate, we could just refresh or give a message.
// Let's assume for now it should be the current one to avoid complications with out-of-order completions.
if (new Date(instanceDateStr) > new Date(series.nextDueDate)) {
alert("You can only mark the current or next upcoming instance as done/skipped for this series from this view. This instance is further in the future.");
return;
} else if (new Date(instanceDateStr) < new Date(series.nextDueDate) && instanceDateStr !== series.lastCompletedInstanceDate) {
// If user tries to mark an older instance as done (that is not the last completed one)
// This implies they want to "catch up".
// For now, let's just increment completed count and set this as last completed.
if(isMarkDone) series.completedOccurrences++;
series.lastCompletedInstanceDate = instanceDateStr;
}
} else { // Acting on the current `series.nextDueDate`
if(isMarkDone) series.completedOccurrences++;
series.lastCompletedInstanceDate = series.nextDueDate;
}
const newNextDueDate = calculateNextDueDate(series.lastCompletedInstanceDate, series, false);
if (newNextDueDate) {
series.nextDueDate = newNextDueDate;
} else {
// Task series has ended (either by condition or no more valid dates)
series.nextDueDate = null; // Mark as no more due dates
// Optionally: Could move to an "archived" state or flag as "completed series"
alert(`Task series "${series.description}" has now ended based on its rules.`);
}
taskSeriesArray[seriesIndex] = series;
saveTaskSeries();
renderTaskSeriesList(); // Update the defined series list (e.g. its next due date)
generateAndDisplayInstances(); // Refresh the current view
}
// --- PDF Download ---
downloadInstancesPdfBtn.addEventListener('click', () => {
const instancesUl = taskInstancesViewDiv.querySelector('.item-list');
if (!instancesUl || !instancesUl.children.length) {
alert('No instances to download for the current view.');
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
let viewStartStr, viewEndStr;
const period = viewPeriodSelect.value;
if (period === 'customRange') {
viewStartStr = new Date(customStartDateInput.value + "T00:00:00").toLocaleDateString();
viewEndStr = new Date(customEndDateInput.value + "T23:59:59").toLocaleDateString();
} else {
const tempStart = new Date(today); tempStart.setHours(0,0,0,0);
const tempEnd = new Date(tempStart);
if (period === 'next7days') tempEnd.setDate(tempStart.getDate() + 6); // 0-6 is 7 days
else if (period === 'next30days') tempEnd.setDate(tempStart.getDate() + 29);
viewStartStr = tempStart.toLocaleDateString();
viewEndStr = tempEnd.toLocaleDateString();
}
doc.setFontSize(18);
doc.setTextColor(parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(1,3),16),
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(3,5),16),
parseInt(getComputedStyle(document.documentElement).getPropertyValue('--primary-color').substring(5,7),16));
doc.text('Scheduled Task Instances', 14, 22);
doc.setFontSize(12);
doc.setTextColor(100);
doc.text(`Period: ${viewStartStr} - ${viewEndStr}`, 14, 30);
doc.setFontSize(10);
doc.text(`Generated on: ${new Date().toLocaleString()}`, 14, 36);
const body = [];
Array.from(instancesUl.children).forEach(li => {
const description = li.querySelector('.task-description').textContent;
// Extracting from textContent, so need to be careful with labels
const metaText = li.querySelector('.task-meta').textContent;
const dueDateMatch = metaText.match(/Due: (.*?)(?:Duration:|Priority:|$)/);
const durationMatch = metaText.match(/Duration: (.*?)(?:Priority:|$)/);
const priorityMatch = metaText.match(/Priority: (.*)/);
const dueDate = dueDateMatch ? dueDateMatch[1].trim() : 'N/A';
const duration = durationMatch ? durationMatch[1].trim() : 'N/A';
const priority = priorityMatch ? priorityMatch[1].trim() : 'N/A';
body.push([dueDate, description, priority, duration]);
});
if (body.length === 0) {
doc.text("No instances in the selected period.", 14, 45);
} else {
doc.autoTable({
startY: 42,
head: [['Due Date & Time', 'Description', 'Priority', 'Est. Duration']],
body: body,
theme: 'grid',
headStyles: { fillColor: [0, 123, 255], textColor: 255 },
didParseCell: function (data) {
const priorityColorsPDF = {
high: getComputedStyle(document.documentElement).getPropertyValue('--danger-color').trim(),
medium: getComputedStyle(document.documentElement).getPropertyValue('--warning-color').trim(),
low: getComputedStyle(document.documentElement).getPropertyValue('--info-color').trim(),
};
if (data.column.index === 2 && data.cell.section === 'body') { // Priority column
const priorityValue = data.cell.raw.toString().toLowerCase();
if (priorityColorsPDF[priorityValue]) {
data.cell.styles.textColor = priorityColorsPDF[priorityValue];
data.cell.styles.fontStyle = 'bold';
}
}
}
});
}
doc.save(`Task_Instances_${viewStartStr}_to_${viewEndStr}.pdf`);
});
// --- Initializations ---
setDateInputMin();
updateRecurrenceOptionsVisibility();
updateEndRecurrenceOptionsVisibility();
renderTaskSeriesList();
initializeCustomRangeDates(); // Set default custom range dates
generateAndDisplayInstances(); // Initial load of instances for default view (next 7 days)
})(); // End IIFE