Task Reminder System

Task Reminder System

Settings

Add / Edit Task

Your Tasks

Task Reminder!

${statusText}

${reminderInfo}

Priority: ${task.priority} | Recurrence: ${task.recurrence}

`; taskListUL.appendChild(li); }); document.querySelectorAll('.edit-task-btn').forEach(btn => btn.addEventListener('click', (e) => populateFormForEdit(e.target.dataset.id))); document.querySelectorAll('.delete-task-btn').forEach(btn => btn.addEventListener('click', (e) => { if (confirm('Are you sure you want to delete this task?')) deleteTask(e.target.dataset.id); })); } function formatTimeDiff(ms) { if (ms < 0) return "ago"; let seconds = Math.floor(ms / 1000); let minutes = Math.floor(seconds / 60); let hours = Math.floor(minutes / 60); let days = Math.floor(hours / 24); if (days > 0) return `${days}d ${hours % 24}h left`; if (hours > 0) return `${hours}h ${minutes % 60}m left`; if (minutes > 0) return `${minutes}m ${seconds % 60}s left`; return `${seconds}s left`; } // --- Reminder Logic --- function checkReminders() { const now = new Date().getTime(); let nextReminderCheckTime = Infinity; // For scheduling the global check more efficiently tasks.forEach(task => { if (task.isSnoozed && task.snoozeUntil && now < task.snoozeUntil) { nextReminderCheckTime = Math.min(nextReminderCheckTime, task.snoozeUntil); return; // Still snoozed } if (task.isSnoozed) task.isSnoozed = false; // Snooze period over const dueDateTime = new Date(task.dueDateTime).getTime(); const reminderTriggerTime = dueDateTime - (task.reminderLeadTime * 60000); if (now >= reminderTriggerTime && dueDateTime > (task.lastDismissedAt || 0) && !task.triggeredThisSession) { // Check if it should have triggered and hasn't been dismissed for this occurrence yet // `triggeredThisSession` prevents re-triggering immediately on page load if already handled by setTimeout if (now < dueDateTime + (5 * 60000)) { // Allow a 5-min window past due time for reminder to be relevant triggerReminder(task); task.triggeredThisSession = true; // Mark as triggered to avoid loops by setInterval } } else if (reminderTriggerTime > now) { nextReminderCheckTime = Math.min(nextReminderCheckTime, reminderTriggerTime); } }); renderTaskList(); // Update display (e.g. time until due) scheduleNextGlobalReminderCheck(nextReminderCheckTime); // Schedule next check based on closest reminder } function scheduleNextGlobalReminderCheck(specificTime = null) { if (activeReminderTimeout) clearTimeout(activeReminderTimeout); // Clear previous specific reminder timeout if (reminderCheckInterval) clearInterval(reminderCheckInterval); // Clear the general interval // General periodic check (e.g., every minute) for UI updates and catching missed reminders reminderCheckInterval = setInterval(checkReminders, 30000); // Check every 30 seconds // More precise check for the very next upcoming reminder if (specificTime && specificTime !== Infinity && specificTime > new Date().getTime()) { const delay = specificTime - new Date().getTime(); activeReminderTimeout = setTimeout(() => { checkReminders(); // This will find and trigger the specific reminder }, delay); } } function triggerReminder(task) { currentRemindingTask = task; reminderTextP.innerHTML = `${task.description} is due at ${new Date(task.dueDateTime).toLocaleTimeString()}.`; reminderAlertDiv.style.display = 'block'; if (soundEnabled) { reminderSound.currentTime = 0; // Rewind reminderSound.play().catch(e => console.warn("Audio play failed:", e)); // Autoplay might be blocked } if (notificationPermission === 'granted') { try { const notificationBody = `Due: ${new Date(task.dueDateTime).toLocaleTimeString()}\nPriority: ${task.priority}`; const notification = new Notification(task.description, { body: notificationBody, icon: 'https://ssl.gstatic.com/calendar/images/dynamiclogo_2020q4/calendar_14_2x.png' // Generic icon }); notification.onclick = () => { // Focus tab on click window.focus(); reminderAlertDiv.style.display = 'block'; // Ensure in-page is visible }; } catch(e) { console.error("Error showing browser notification:", e); } } } closeReminderAlertBtn.onclick = () => reminderAlertDiv.style.display = 'none'; dismissReminderBtn.addEventListener('click', () => { if (!currentRemindingTask) return; currentRemindingTask.lastDismissedAt = new Date().getTime(); currentRemindingTask.triggeredThisSession = false; // Reset for next potential trigger currentRemindingTask.isSnoozed = false; if (currentRemindingTask.recurrence === 'daily') { const currentDueDate = new Date(currentRemindingTask.originalDueDateTime); // Base next on original due time let nextDueDate = new Date(currentDueDate); // Ensure nextDueDate is in the future relative to current dueDateTime const now = new Date(); let taskLastEffectiveDueDate = new Date(currentRemindingTask.dueDateTime); if (taskLastEffectiveDueDate <= now) { // If current due date is past or now nextDueDate.setDate(now.getDate() + 1); // Set to tomorrow relative to today nextDueDate.setHours(currentDueDate.getHours(), currentDueDate.getMinutes(), 0, 0); // Keep original time } else { // If current due date is still in future (e.g. dismissed early) // This case means it was dismissed before it was due, just advance from its current due date nextDueDate = new Date(taskLastEffectiveDueDate); nextDueDate.setDate(nextDueDate.getDate() + 1); } currentRemindingTask.dueDateTime = nextDueDate.toISOString().slice(0, 16); // Note: originalDueDateTime remains the same for daily recurrence to keep the time of day. } else { // For non-recurring, effectively "done" with reminders for this task unless edited. // Could add a 'completed' flag if needed. } const taskIndex = tasks.findIndex(t => t.id === currentRemindingTask.id); if (taskIndex > -1) tasks[taskIndex] = { ...currentRemindingTask }; // Update task in main array saveTasks(); renderTaskList(); reminderAlertDiv.style.display = 'none'; currentRemindingTask = null; scheduleNextGlobalReminderCheck(); }); function snoozeReminder(minutes) { if (!currentRemindingTask) return; const now = new Date().getTime(); currentRemindingTask.snoozeUntil = now + (minutes * 60000); currentRemindingTask.isSnoozed = true; currentRemindingTask.triggeredThisSession = false; // Allow it to be picked up by checkReminders after snooze const taskIndex = tasks.findIndex(t => t.id === currentRemindingTask.id); if (taskIndex > -1) tasks[taskIndex] = { ...currentRemindingTask }; saveTasks(); renderTaskList(); reminderAlertDiv.style.display = 'none'; currentRemindingTask = null; scheduleNextGlobalReminderCheck(); // Will pick up the snoozeUntil time } snooze5minBtn.addEventListener('click', () => snoozeReminder(5)); snooze10minBtn.addEventListener('click', () => snoozeReminder(10)); // --- PDF Export --- downloadTasksPdfBtn.addEventListener('click', () => { const { jsPDF } = window.jspdf; const doc = new jsPDF(); 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('Task Reminder List', 14, 22); doc.setFontSize(11); doc.setTextColor(100); doc.text(`Generated on: ${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`, 14, 30); const body = []; const sortedTasksForPdf = [...tasks].sort((a,b) => new Date(a.dueDateTime) - new Date(b.dueDateTime)); sortedTasksForPdf.forEach(task => { const dueDate = new Date(task.dueDateTime); const reminderLead = task.reminderLeadTime === 0 ? 'At event time' : `${task.reminderLeadTime} min before`; body.push([ task.description, dueDate.toLocaleString(), task.priority.charAt(0).toUpperCase() + task.priority.slice(1), reminderLead, task.recurrence !== 'none' ? task.recurrence.charAt(0).toUpperCase() + task.recurrence.slice(1) : 'One-time' ]); }); if (body.length === 0) { doc.text("No tasks to report.", 14, 45); } else { doc.autoTable({ startY: 35, head: [['Description', 'Due Date & Time', 'Priority', 'Reminder', 'Recurrence']], body: body, theme: 'grid', headStyles: { fillColor: [0, 123, 255], textColor: 255 }, // Primary color header 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('--success-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_Reminder_List.pdf'); }); // --- Initial Load --- renderTaskList(); checkReminders(); // Initial check and schedule next });
Scroll to Top