Deep Work Scheduler
Schedule Deep Work
View & Manage Schedule
Schedule New Deep Work Block
Settings
Scheduled Deep Work Blocks
${block.objective ? `
`;
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();
})();
Objective: ${block.objective}
` : ''} ${block.prepNotes ? `Prep Notes: ${block.prepNotes}
` : ''}