Work Session Optimizer

Work Session Optimizer

Schedule New Optimized Session

Upcoming Scheduled Sessions

    No active session. Schedule one from the 'Plan & Schedule' tab or select an upcoming session to start.

    Completed Session Log

      Prep Notes: ${session.prepNotes}

      ` : ''} ${session.prepRitualItemsApplied && session.prepRitualItemsApplied.length > 0 ? `

      Rituals: ${session.prepRitualItemsApplied.join(', ')}

      ` : ''}
      `; upcomingSessionsListUL.appendChild(li); }); upcomingSessionsListUL.querySelectorAll('.start-this-session-btn').forEach(btn => btn.addEventListener('click', (e) => startOptimizedSession(e.target.dataset.id))); upcomingSessionsListUL.querySelectorAll('.edit-session-btn').forEach(btn => btn.addEventListener('click', (e) => populateSessionFormForEdit(e.target.dataset.id))); upcomingSessionsListUL.querySelectorAll('.delete-session-btn').forEach(btn => btn.addEventListener('click', (e) => deleteScheduledSession(e.target.dataset.id))); } // --- Execute Session Logic --- function startOptimizedSession(sessionId) { const session = scheduledSessions.find(s => s.id === sessionId); if (!session || session.status !== 'scheduled') { alert("Session not found or already completed/missed."); renderUpcomingSessionsList(); // Refresh list return; } currentExecutingSessionId = sessionId; currentSessionPhase = 'pre-session'; renderExecuteView(); // Switch to Execute Tab tabs.forEach(t => t.classList.remove('active')); tabContents.forEach(tc => tc.classList.remove('active')); document.querySelector('.tab-link[data-tab="executeSessionTab"]').classList.add('active'); document.getElementById('executeSessionTab').classList.add('active'); } function renderExecuteView() { executeSessionViewDiv.innerHTML = ''; // Clear previous state noActiveSessionMsgP.style.display = 'none'; const session = scheduledSessions.find(s => s.id === currentExecutingSessionId); if (!session) { resetExecuteView(); return; } if (currentSessionPhase === 'pre-session') { executeSessionViewDiv.innerHTML = `

      Pre-Session Checklist

      ${session.objective}

      Your Rituals & Notes:

        ${(session.prepRitualItemsApplied && session.prepRitualItemsApplied.length > 0 ? session.prepRitualItemsApplied.map(item => `
      • ${item}
      • `).join('') : '
      • No specific ritual items selected.
      • ')}
      ${session.prepNotes ? `

      Specific Prep: ${session.prepNotes}

      ` : ''}
      `; executeSessionViewDiv.querySelector('#beginWorkPhaseBtn').onclick = () => { currentSessionPhase = 'work'; sessionTimeLeftSeconds = session.plannedDurationMinutes * 60; currentSessionDistractions = 0; // Reset for new session currentSessionBrainDump = ""; // Reset for new session renderExecuteView(); startSessionTimer(); }; } else if (currentSessionPhase === 'work') { executeSessionViewDiv.innerHTML = `

      Work Session In Progress

      ${session.objective}

      ${formatTimeMMSS(sessionTimeLeftSeconds)}
      `; executeSessionViewDiv.querySelector('#logDistractionBtn').onclick = () => { currentSessionDistractions++; executeSessionViewDiv.querySelector('#distractionCountDisplay').textContent = currentSessionDistractions; }; executeSessionViewDiv.querySelector('#brainDumpBtn').onclick = () => { brainDumpTextarea.value = currentSessionBrainDump; // Load existing dump for this session brainDumpModal.style.display = 'block'; }; executeSessionViewDiv.querySelector('#endSessionEarlyBtn').onclick = () => { if(confirm("End session early and proceed to review?")){ endSessionTimer(true); // true for ended early } }; } else if (currentSessionPhase === 'review') { const actualDurationMinutes = session.actualDurationMinutes || session.plannedDurationMinutes; executeSessionViewDiv.innerHTML = `

      Post-Session Review

      Session: ${session.objective} (Completed in ${formatDuration(actualDurationMinutes)})

      ${[1,2,3,4,5].map(v => ``).join('')}

      Distractions Logged: ${session.distractionsLogged !== undefined ? session.distractionsLogged : currentSessionDistractions}

      ${session.brainDump ? `
      ` : ''}
      `; let reviewFocusRating = 0; const stars = executeSessionViewDiv.querySelectorAll('#reviewFocusRatingStars span'); stars.forEach(star => { star.onmouseover = () => stars.forEach((s,i) => s.classList.toggle('selected', i < star.dataset.value)); star.onmouseout = () => stars.forEach((s,i) => s.classList.toggle('selected', i < reviewFocusRating)); star.onclick = () => { reviewFocusRating = parseInt(star.dataset.value); executeSessionViewDiv.querySelector('#reviewFocusRatingInput').value = reviewFocusRating; stars.forEach((s,i) => s.classList.toggle('selected', i < reviewFocusRating)); }; }); executeSessionViewDiv.querySelector('#postSessionReviewForm').onsubmit = (e) => { e.preventDefault(); savePostSessionReview(session.id); resetExecuteView(); renderUpcomingSessionsList(); // Refresh main list as session is now completed // Optionally switch tab or show a "well done" message. alert("Session review saved! Well done."); // Switch to log tab to see it tabs.forEach(t => t.classList.remove('active')); tabContents.forEach(tc => tc.classList.remove('active')); document.querySelector('.tab-link[data-tab="sessionLogTab"]').classList.add('active'); document.getElementById('sessionLogTab').classList.add('active'); filterLogBtn.click(); // Refresh log }; } } saveBrainDumpBtn.addEventListener('click', () => { currentSessionBrainDump = brainDumpTextarea.value; // No immediate save to localStorage, saved with post-session review. brainDumpModal.style.display = 'none'; }); function startSessionTimer() { if (sessionTimerInterval) clearInterval(sessionTimerInterval); sessionTimerInterval = setInterval(() => { sessionTimeLeftSeconds--; executeSessionViewDiv.querySelector('#sessionTimerDisplay').textContent = formatTimeMMSS(sessionTimeLeftSeconds); if (sessionTimeLeftSeconds <= 0) { endSessionTimer(false); // false for not ended early } }, 1000); } function pauseSessionTimer() { // Example: if user navigates away if(sessionTimerInterval) clearInterval(sessionTimerInterval); // Could add a "Paused" state if needed, but for now, switching tab implies pausing. } function endSessionTimer(endedEarly) { clearInterval(sessionTimerInterval); sessionTimerInterval = null; const sessionIndex = scheduledSessions.findIndex(s => s.id === currentExecutingSessionId); if (sessionIndex === -1) { resetExecuteView(); return; } const session = scheduledSessions[sessionIndex]; session.actualDurationMinutes = endedEarly ? (session.plannedDurationMinutes - Math.floor(sessionTimeLeftSeconds / 60)) : session.plannedDurationMinutes; session.distractionsLogged = currentSessionDistractions; // Capture from the live counter session.brainDump = currentSessionBrainDump; // Capture from live var currentSessionPhase = 'review'; renderExecuteView(); if (settings.soundEnabled) alertSound.play().catch(e=>console.warn(e)); if (settings.notificationPermission === 'granted') { new Notification('Work Session Ended!', { body: `Time to review your session: "${session.objective}"` }); } else { alert(`Work session "${session.objective}" ended. Please complete your review.`); } } function savePostSessionReview(sessionId) { const sessionIndex = scheduledSessions.findIndex(s => s.id === sessionId); if (sessionIndex === -1) return; scheduledSessions[sessionIndex].status = 'completed'; scheduledSessions[sessionIndex].reviewData = { objectiveAchieved: document.getElementById('reviewObjectiveAchieved').value, focusLevel: parseInt(document.getElementById('reviewFocusRatingInput').value) || 0, // distractions already set on session.distractionsLogged improvements: document.getElementById('reviewImprovements').value.trim() || null, }; // brainDump is already on scheduledSessions[sessionIndex].brainDump saveScheduledSessions(); } function resetExecuteView() { currentExecutingSessionId = null; currentSessionPhase = 'idle'; clearInterval(sessionTimerInterval); sessionTimerInterval = null; executeSessionViewDiv.innerHTML = ''; // Clear it noActiveSessionMsgP.style.display = 'block'; } // --- Session Log & History --- filterLogBtn.addEventListener('click', renderCompletedSessionsLog); function renderCompletedSessionsLog() { const startFilter = logFilterDateStartInput.value; const endFilter = logFilterDateEndInput.value; const completed = scheduledSessions.filter(s => { if (s.status !== 'completed') return false; if (startFilter && s.date < startFilter) return false; if (endFilter && s.date > endFilter) return false; return true; }).sort((a,b) => new Date(`${b.date}T${b.startTime}`) - new Date(`${a.date}T${a.startTime}`)); // Recent first completedSessionsLogUL.innerHTML = ''; if(completed.length === 0) { completedSessionsLogUL.innerHTML = '
    • No completed sessions match the filter.
    • '; return; } completed.forEach(session => { const li = document.createElement('li'); li.className = 'log-item'; const review = session.reviewData || {}; li.innerHTML = `
      ${session.objective} ${formatDate(session.date)} @ ${formatTimeSimple(session.startTime)} (${formatDuration(session.actualDurationMinutes || session.plannedDurationMinutes)})

      Objective Achieved: ${review.objectiveAchieved || 'N/A'}

      Focus Level: ${review.focusLevel > 0 ? '★'.repeat(review.focusLevel)+'☆'.repeat(5-review.focusLevel) : 'N/A'}

      Distractions Logged: ${session.distractionsLogged !== undefined ? session.distractionsLogged : 'N/A'}

      ${review.improvements ? `

      Insights/Improvements: ${review.improvements}

      ` : ''} ${session.brainDump ? `

      Brain Dump during session:

      ${session.brainDump}

      ` : ''}
      `; completedSessionsLogUL.appendChild(li); }); } // --- Reminders for Upcoming Scheduled Sessions --- function scheduleSessionReminder(session) { clearSessionReminder(session.id); // Clear existing before setting new if (session.status !== 'scheduled' || settings.reminderLeadTimeMinutes === 0) return; const reminderTimeMs = new Date(`${session.date}T${session.startTime}`).getTime() - (settings.reminderLeadTimeMinutes * 60000); const nowMs = new Date().getTime(); if (reminderTimeMs > nowMs) { const delay = reminderTimeMs - nowMs; activeSessionReminders[session.id] = setTimeout(() => { const notificationMessage = `Upcoming Deep Work: "${session.objective}" starts in ${settings.reminderLeadTimeMinutes} mins!`; if (settings.soundEnabled) alertSound.play().catch(e=>console.warn(e)); if (settings.notificationPermission === 'granted') { new Notification('Upcoming Session Reminder', { body: notificationMessage }); } else { // Could add an in-app subtle reminder here if desired console.log("Reminder (no perm): " + notificationMessage); } delete activeSessionReminders[session.id]; // Remove after firing }, delay); } } function clearSessionReminder(sessionId) { if (activeSessionReminders[sessionId]) { clearTimeout(activeSessionReminders[sessionId]); delete activeSessionReminders[sessionId]; } } function scheduleAllSessionReminders() { Object.keys(activeSessionReminders).forEach(id => clearTimeout(activeSessionReminders[id])); activeSessionReminders = {}; scheduledSessions.forEach(s => scheduleSessionReminder(s)); } // --- PDF Download --- downloadSessionLogPdfBtn.addEventListener('click', () => { const { jsPDF } = window.jspdf; const doc = new jsPDF('p', 'pt', 'a4'); const margin = 40; let yPos = margin; const pageWidth = doc.internal.pageSize.getWidth(); const startFilter = logFilterDateStartInput.value; const endFilter = logFilterDateEndInput.value; let periodText = "All Completed Sessions"; if(startFilter && endFilter) periodText = `Sessions from ${formatDate(startFilter)} to ${formatDate(endFilter)}`; else if (startFilter) periodText = `Sessions from ${formatDate(startFilter)}`; else if (endFilter) periodText = `Sessions up to ${formatDate(endFilter)}`; doc.setFontSize(18); doc.setTextColor(varToRGB('--primary-color').r, varToRGB('--primary-color').g, varToRGB('--primary-color').b); doc.text('Optimized Work Session Log', pageWidth / 2, yPos, { align: 'center' }); yPos += 20; doc.setFontSize(11); doc.setTextColor(100); doc.text(periodText, pageWidth / 2, yPos, { align: 'center' }); yPos += 15; doc.text(`Generated: ${new Date().toLocaleString()}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 25; const logItemsForPdf = []; completedSessionsLogUL.querySelectorAll('.log-item').forEach(li => { const objective = li.querySelector('.log-item-header strong')?.textContent || 'N/A'; const dateTime = li.querySelector('.log-item-header span')?.textContent || 'N/A'; const detailsParas = li.querySelectorAll('.log-item-details p'); const achieved = detailsParas[0]?.textContent.replace('Objective Achieved: ','') || 'N/A'; const focus = detailsParas[1]?.textContent.replace('Focus Level: ','') || 'N/A'; const distractions = detailsParas[2]?.textContent.replace('Distractions Logged: ','') || 'N/A'; const improvements = detailsParas[3]?.textContent.replace('Insights/Improvements: ','') || ''; const brainDump = li.querySelector('.log-item-details pre')?.textContent || ''; logItemsForPdf.push([ objective, dateTime, achieved, focus, distractions, improvements, brainDump ]); }); if (logItemsForPdf.length > 0) { doc.autoTable({ startY: yPos, head: [['Objective', 'Date & Time', 'Achieved?', 'Focus', 'Distractions', 'Improvements/Insights', 'Brain Dump']], body: logItemsForPdf, theme: 'grid', headStyles: { fillColor: varToRGB('--primary-color', true), textColor: varToRGB('--light-text', true) }, styles: { fontSize: 8, cellPadding: 3, overflow: 'linebreak' }, columnStyles: { 0: {cellWidth: 80}, 1: {cellWidth: 70}, 2:{cellWidth:40}, 3:{cellWidth:50}, 4:{cellWidth:50}, 5:{cellWidth:'auto'}, 6:{cellWidth:'auto'} } }); } else { doc.setFontSize(10); doc.text("No completed session logs for the selected period.", margin, yPos); } doc.save(`Work_Session_Optimizer_Log_${todayStr}.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 --- sessionDateInput.value = todayStr; // Default date for new session logFilterDateStartInput.value = todayStr; // Default log filter start logFilterDateEndInput.value = todayStr; // Default log filter end renderGlobalRitualsList(); // For settings modal renderSessionRitualChecklist(); // For plan tab renderUpcomingSessionsList(); filterLogBtn.click(); // Render initial log view resetSessionForm(); // Set default duration based on settings scheduleAllSessionReminders(); })();
      Scroll to Top