Deep Work Scheduler

Deep Work Scheduler

Schedule New Deep Work Block

Settings

Scheduled Deep Work Blocks

    ${block.objective ? `

    Objective: ${block.objective}

    ` : ''} ${block.prepNotes ? `

    Prep Notes: ${block.prepNotes}

    ` : ''}
    `; scheduledBlocksListUL.appendChild(li); }); scheduledBlocksListUL.querySelectorAll('.edit-block-btn').forEach(btn => btn.addEventListener('click', (e) => populateBlockFormForEdit(e.target.dataset.id))); scheduledBlocksListUL.querySelectorAll('.delete-block-btn').forEach(btn => btn.addEventListener('click', (e) => deleteBlock(e.target.dataset.id))); } // --- Reminder Logic --- function scheduleReminder(block) { clearReminder(block.id); // Clear existing reminder for this block first if (block.reminderSent) return; // Don't schedule if already sent (needs better reset logic if task is edited far future) const reminderTimeMs = new Date(`${block.date}T${block.startTime}`).getTime() - (userSettings.reminderLeadTime * 60000); const nowMs = new Date().getTime(); if (reminderTimeMs > nowMs) { const delay = reminderTimeMs - nowMs; activeReminders[block.id] = setTimeout(() => { triggerReminderNotification(block); block.reminderSent = true; // Mark as sent // This flag might need to be reset if block is edited significantly // For now, it prevents re-triggering on page reload if timeout was "missed" const idx = scheduledBlocks.findIndex(b => b.id === block.id); if(idx > -1) scheduledBlocks[idx].reminderSent = true; saveBlocks(); }, delay); } } function triggerReminderNotification(block) { const message = `Deep Work session "${block.taskFocus}" is starting in ${userSettings.reminderLeadTime} minutes!`; if (userSettings.soundEnabled && deepWorkReminderSound) { deepWorkReminderSound.currentTime = 0; deepWorkReminderSound.play().catch(e => console.warn("Reminder sound play failed:", e)); } if (userSettings.notificationPermission === 'granted') { new Notification('Deep Work Reminder!', { body: message, icon: 'https://ssl.gstatic.com/calendar/images/dynamiclogo_2020q4/calendar_14_2x.png' }); } else { alert(message); // Fallback } } function clearReminder(blockId) { if (activeReminders[blockId]) { clearTimeout(activeReminders[blockId]); delete activeReminders[blockId]; } } function scheduleAllReminders() { // Clear all existing timeouts for (const blockId in activeReminders) { clearTimeout(activeReminders[blockId]); } activeReminders = {}; // Reschedule for all relevant blocks scheduledBlocks.forEach(block => { const blockStartDateTime = new Date(`${block.date}T${block.startTime}`); // Only schedule reminders for future blocks that haven't had their reminder "sent" conceptually // Resetting reminderSent if task is moved to far future is important for robustness (not fully handled here for simplicity) if(blockStartDateTime > new Date() && !block.reminderSent){ scheduleReminder(block); } }); } // --- PDF Download --- downloadSchedulePdfBtn.addEventListener('click', () => { const periodText = viewPeriodSelect.options[viewPeriodSelect.selectedIndex].text; let dateRangeText; if(viewPeriodSelect.value === 'customRange' && viewStartDateInput.value && viewEndDateInput.value){ dateRangeText = `${formatDateForDisplay(new Date(viewStartDateInput.value+"T00:00:00"))} to ${formatDateForDisplay(new Date(viewEndDateInput.value+"T00:00:00"))}`; } else if (viewPeriodSelect.value !== 'customRange'){ // Calculate based on period selection for PDF header // This part can be simplified for PDF if exact range is not critical or just use periodText dateRangeText = periodText; // Simplified } else { dateRangeText = "Current View"; } const { jsPDF } = window.jspdf; const doc = new jsPDF('p', 'pt', 'a4'); const margin = 40; let yPos = margin; const pageWidth = doc.internal.pageSize.getWidth(); doc.setFontSize(18); doc.setTextColor(varToRGB('--primary-color').r, varToRGB('--primary-color').g, varToRGB('--primary-color').b); doc.text('Deep Work Schedule', pageWidth / 2, yPos, { align: 'center' }); yPos += 20; doc.setFontSize(12); doc.setTextColor(100); doc.text(`Period: ${dateRangeText}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 15; doc.setFontSize(10); doc.text(`Generated: ${new Date().toLocaleString()}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 25; const itemsToPrint = []; scheduledBlocksListUL.querySelectorAll('.deep-work-block-item').forEach(li => { const timeText = li.querySelector('.block-time')?.textContent || 'N/A'; const focusText = li.querySelector('.block-focus')?.textContent || 'N/A'; const objectiveText = li.querySelector('.block-details p:nth-child(1)')?.textContent.replace('Objective: ','') || ''; const prepNotesText = li.querySelector('.block-details p:nth-child(2)')?.textContent.replace('Prep Notes: ','') || (li.querySelector('.block-details p:nth-child(1)') && !li.querySelector('.block-details p:nth-child(1)').textContent.startsWith('Objective: ') ? li.querySelector('.block-details p:nth-child(1)').textContent.replace('Prep Notes: ','') : ''); itemsToPrint.push([ timeText.split('@')[0].trim(), // Date part timeText.split('@')[1]?.split('(')[0].trim(), // Time part `(${timeText.split('(')[1]?.split(')')[0]})` || '', // Duration part focusText, objectiveText, prepNotesText ]); }); if (itemsToPrint.length > 0) { doc.autoTable({ startY: yPos, head: [['Date', 'Time', 'Duration', 'Task/Goal Focus', 'Objective', 'Preparation Notes']], body: itemsToPrint, theme: 'grid', headStyles: { fillColor: varToRGB('--primary-color', true), textColor: varToRGB('--light-text', true) }, styles: { fontSize: 9, cellPadding: 4 }, columnStyles: { 0: {cellWidth: 70}, 1: {cellWidth: 70}, 2:{cellWidth:50}, 3: {cellWidth: 'auto'}, 4:{cellWidth:80}, 5:{cellWidth:80} } }); } else { doc.text('No deep work blocks scheduled for this period.', margin, yPos); } doc.save(`Deep_Work_Schedule_${dateRangeText.replace(/\s+/g, '_').replace(/[\/\:]/g,'-')}.pdf`); }); function varToRGB(varName, asArray = false) { const colorHex = getComputedStyle(document.documentElement).getPropertyValue(varName).trim(); if (!colorHex.startsWith('#')) { return asArray ? [0,0,0] : {r:0,g:0,b:0}; } const r = parseInt(colorHex.slice(1, 3), 16); const g = parseInt(colorHex.slice(3, 5), 16); const b = parseInt(colorHex.slice(5, 7), 16); return asArray ? [r,g,b] : {r,g,b}; } // --- Initializations --- blockDateInput.value = todayDateStr; // Default date to today const nowTime = new Date().toTimeString().slice(0,5); blockStartTimeInput.value = nowTime; // Default time to now // Set default custom range view dates viewStartDateInput.value = todayDateStr; const sevenDaysLater = new Date(todayDateStr+"T00:00:00"); sevenDaysLater.setDate(sevenDaysLater.getDate() + 6); viewEndDateInput.value = new Date(sevenDaysLater.getTime() - sevenDaysLater.getTimezoneOffset() * 60000).toISOString().slice(0,10); renderScheduledBlocks(); // Initial render for default view scheduleAllReminders(); })();
    Scroll to Top