`;
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.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)})
`;
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 = `
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();
})();