No specific tasks listed for this session yet.
';
return;
}
const ul = document.createElement('ul');
ul.style.listStyle = 'none';
ul.style.paddingLeft = '0';
wsoSessionTasksList.forEach(task => {
const li = document.createElement('li');
li.className = 'wso-task-item-in-plan';
li.textContent = `${task.name}${task.estimatedTimeMinutes ? ` (${task.estimatedTimeMinutes}m)` : ''}`;
const delBtn = document.createElement('button');
delBtn.innerHTML = '×';
delBtn.className = 'wso-button wso-danger';
delBtn.style.padding = '2px 6px';
delBtn.style.fontSize = '0.8em';
delBtn.onclick = () => {
wsoSessionTasksList = wsoSessionTasksList.filter(t => t.id !== task.id);
wsoRenderSessionTasksList();
};
li.appendChild(delBtn);
ul.appendChild(li);
});
wsoSessionTasksListEl.appendChild(ul);
}
// --- Session Plan Generation ---
window.wsoGenerateSessionPlan = function() { /* ... from thought process ... */
const totalDurationInput = parseInt(wsoTotalSessionDurationInput.value);
if (isNaN(totalDurationInput) || totalDurationInput <= 0) {
alert("Please enter a valid total session duration.");
return;
}
const activeConfig = wsoGetActiveSettingsForSession();
const plan = [];
let currentTimeElapsed = 0;
let workSprintCycleCount = 0; // For long breaks
while (currentTimeElapsed < totalDurationInput) {
// Add Work Sprint
let currentWorkDuration = Math.min(activeConfig.workSprintDuration, totalDurationInput - currentTimeElapsed);
if (currentWorkDuration <= 0) break;
plan.push({ type: "work", duration: currentWorkDuration, start: currentTimeElapsed, end: currentTimeElapsed + currentWorkDuration });
currentTimeElapsed += currentWorkDuration;
workSprintCycleCount++;
if (currentTimeElapsed >= totalDurationInput) break;
// Add Break
let breakDurationToUse;
let breakType;
if (activeConfig.longBreakEnabled && workSprintCycleCount > 0 && workSprintCycleCount % activeConfig.sprintsBeforeLongBreak === 0) {
breakDurationToUse = activeConfig.longBreakDuration;
breakType = "long_break";
} else {
breakDurationToUse = activeConfig.shortBreakDuration;
breakType = "short_break";
}
let currentBreakDuration = Math.min(breakDurationToUse, totalDurationInput - currentTimeElapsed);
if (currentBreakDuration <= 0) break;
plan.push({ type: breakType, duration: currentBreakDuration, start: currentTimeElapsed, end: currentTimeElapsed + currentBreakDuration });
currentTimeElapsed += currentBreakDuration;
}
wsoCurrentSessionPlan = {
totalSessionDurationMinutes: totalDurationInput,
planName: wsoSessionNameInput.value.trim() || "Work Session",
intervals: plan,
generatedAt: new Date().toISOString(),
settingsUsed: activeConfig,
userTasksForSession: [...wsoSessionTasksList] // Copy current tasks list
};
wsoDisplayGeneratedPlanOutput();
if(wsoGeneratedPlanAreaEl) wsoGeneratedPlanAreaEl.style.display = 'block';
if(wsoActiveSessionAreaEl) wsoActiveSessionAreaEl.style.display = 'none';
if(wsoDownloadPdfButton) wsoDownloadPdfButton.style.display = 'block';
}
function wsoDisplayGeneratedPlanOutput() { /* ... renders wsoCurrentSessionPlan.intervals ... */
if(!wsoGeneratedPlanOutputEl) return;
wsoGeneratedPlanOutputEl.innerHTML = '';
if (!wsoCurrentSessionPlan || wsoCurrentSessionPlan.intervals.length === 0) {
wsoGeneratedPlanOutputEl.innerHTML = '
No plan generated. Adjust settings and try again.
';
return;
}
const ul = document.createElement('ul');
let sessionClock = 0; // To show actual clock times if a start time was implied
wsoCurrentSessionPlan.intervals.forEach((interval, index) => {
const li = document.createElement('li');
let typeDisplay = interval.type.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase());
const startTimeDisplay = wsoMinutesToTime(interval.start);
const endTimeDisplay = wsoMinutesToTime(interval.end);
li.textContent = `${index + 1}. ${typeDisplay} (${interval.duration} min) | From ${startTimeDisplay} to ${endTimeDisplay} of session`;
li.classList.add(`plan-${interval.type}`);
ul.appendChild(li);
});
wsoGeneratedPlanOutputEl.appendChild(ul);
}
// --- Active Session Timer Logic & UI Update ---
// (wsoUpdateTimerDisplay, wsoUpdateActiveSessionUI, wsoStartNextInterval, wsoStartIntervalTimer,
// wsoPauseResumeSession, wsoStopSession - similar to Smart Break Reminder but uses wsoCurrentSessionPlan.intervals)
// ... Placeholder for these complex functions ...
// Placeholder for active timer functions (to be adapted from Smart Break Reminder)
function wsoUpdateActiveTimerDisplay() {
if(!wsoTimerCountdownDisplayEl || !wsoActiveSessionState.isRunning) return;
const minutes = Math.floor(wsoActiveSessionState.timeRemainingInIntervalSeconds / 60);
const seconds = wsoActiveSessionState.timeRemainingInIntervalSeconds % 60;
wsoTimerCountdownDisplayEl.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
const currentInterval = wsoCurrentSessionPlan.intervals[wsoActiveSessionState.currentIntervalIndex];
let intervalName = currentInterval.type.replace('_',' ').replace(/\b\w/g, l=>l.toUpperCase());
if (!wsoActiveSessionState.isPaused) {
document.title = `${wsoTimerCountdownDisplayEl.textContent} - ${intervalName}`;
} else {
document.title = `Paused - ${intervalName}`;
}
}
function wsoUpdateActiveSessionView() {
if (!wsoCurrentSessionPlan || wsoActiveSessionState.currentIntervalIndex < 0 ||
wsoActiveSessionState.currentIntervalIndex >= wsoCurrentSessionPlan.intervals.length) {
wsoActiveSessionAreaEl.style.display = 'none';
wsoGeneratedPlanAreaEl.style.display = 'block'; // Show plan setup again
if(wsoStartSessionButtonEl) wsoStartSessionButtonEl.textContent = "Start New Session";
document.title = "Work Session Optimizer";
return;
}
wsoActiveSessionAreaEl.style.display = 'block';
wsoGeneratedPlanAreaEl.style.display = 'none';
const currentInterval = wsoCurrentSessionPlan.intervals[wsoActiveSessionState.currentIntervalIndex];
let intervalName = currentInterval.type.replace('_',' ').replace(/\b\w/g, l=>l.toUpperCase());
wsoCurrentIntervalNameDisplayEl.textContent = `${intervalName}`;
wsoTimerCountdownDisplayEl.className = currentInterval.type.includes('work') ? 'work-active' : 'break-active';
wsoSessionProgressDisplayEl.textContent = `Interval ${wsoActiveSessionState.currentIntervalIndex + 1} of ${wsoCurrentSessionPlan.intervals.length}`;
wsoCurrentTaskInputGroupEl.style.display = currentInterval.type.includes('work') ? 'block' : 'none';
if (wsoActiveSessionState.isPaused) {
wsoPauseResumeButtonEl.textContent = "Resume";
} else {
wsoPauseResumeButtonEl.textContent = "Pause";
}
wsoUpdateActiveTimerDisplay();
}
function wsoStartNextActiveInterval(isSkipping = false) {
// Log previous interval completion if not skipped or if work was done
if (!isSkipping && wsoActiveSessionState.currentIntervalIndex >=0) {
const prevInterval = wsoCurrentSessionPlan.intervals[wsoActiveSessionState.currentIntervalIndex];
// Simple log to console for now; PDF/localStorage log could be more detailed
console.log(`Finished: ${prevInterval.type} (${prevInterval.duration}m). Task: ${wsoCurrentTaskDescriptionInput.value}`);
}
wsoActiveSessionState.currentIntervalIndex++;
if (wsoActiveSessionState.currentIntervalIndex >= wsoCurrentSessionPlan.intervals.length) {
wsoStopActiveSession(true);
wsoShowNotification("Session Complete!", "Great job finishing your planned session.");
return;
}
const nextInterval = wsoCurrentSessionPlan.intervals[wsoActiveSessionState.currentIntervalIndex];
wsoActiveSessionState.timeRemainingInIntervalSeconds = nextInterval.duration * 60;
wsoActiveSessionState.isPaused = false;
wsoCurrentTaskDescriptionInput.value = ""; // Clear for next work sprint
let notificationTitle = nextInterval.type.includes('work') ? "Time for Work!" : "Time for a Break!";
let notificationBody = `Starting ${nextInterval.type.replace('_',' ')} for ${nextInterval.duration} minutes.`;
wsoShowNotification(notificationTitle, notificationBody);
wsoPlayBeep();
wsoRunActiveIntervalTimer();
wsoUpdateActiveSessionView();
}
function wsoRunActiveIntervalTimer() {
clearInterval(wsoActiveSessionState.timerIntervalId);
wsoActiveSessionState.isRunning = true;
wsoActiveSessionState.isPaused = false;
wsoUpdateActiveSessionView();
wsoActiveSessionState.timerIntervalId = setInterval(() => {
if (wsoActiveSessionState.isPaused) return;
wsoActiveSessionState.timeRemainingInIntervalSeconds--;
wsoUpdateActiveTimerDisplay();
if (wsoActiveSessionState.timeRemainingInIntervalSeconds < 0) {
wsoActiveSessionState.timeRemainingInIntervalSeconds = 0;
clearInterval(wsoActiveSessionState.timerIntervalId);
wsoStartNextActiveInterval(); // Auto-move to next
}
}, 1000);
}
function wsoTogglePauseResumeActiveTimer() {
if (!wsoActiveSessionState.isRunning) return;
wsoActiveSessionState.isPaused = !wsoActiveSessionState.isPaused;
if (wsoActiveSessionState.isPaused) {
clearInterval(wsoActiveSessionState.timerIntervalId);
} else {
wsoRunActiveIntervalTimer();
}
wsoUpdateActiveSessionView();
}
function wsoStopActiveSession(completedNormally = false) {
clearInterval(wsoActiveSessionState.timerIntervalId);
wsoActiveSessionState.isRunning = false;
wsoActiveSessionState.isPaused = false;
wsoCurrentIntervalNameDisplayEl.textContent = completedNormally ? "Session Completed!" : "Session Stopped";
wsoTimerCountdownDisplayEl.textContent = "00:00";
wsoTimerCountdownDisplayEl.className = ""; // Reset color
wsoSessionProgressDisplayEl.textContent = completedNormally ? "All intervals finished." : "Session ended early.";
wsoCurrentTaskInputGroupEl.style.display = 'none';
// Don't nullify wsoCurrentSessionPlan so PDF can still be generated
// Reset active state parts for next potential run
wsoActiveSessionState.currentIntervalIndex = -1;
wsoActiveSessionState.timeRemainingInIntervalSeconds = 0;
// Make plan generation visible again
wsoActiveSessionAreaEl.style.display = 'none';
wsoGeneratedPlanAreaEl.style.display = 'block';
wsoStartSessionButtonEl.textContent = "Start This Session"; // Or "Start New Session"
document.title = "Work Session Optimizer";
}
// --- PDF Export ---
function handleWsoPdfDownload() { /* ... similar to previous PDF logic, using wsoCurrentSessionPlan ... */
if (!wsoCurrentSessionPlan) {
alert("Please generate a session plan first.");
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
const reportDate = new Date().toLocaleDateString();
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--wso-primary-color').trim();
doc.setFontSize(18);
doc.setTextColor(primaryColor);
doc.text(wsoCurrentSessionPlan.planName || "Work Session Plan", 14, 22);
doc.setFontSize(10);
doc.setTextColor(100);
doc.text(`Total Planned Duration: ${wsoCurrentSessionPlan.totalSessionDurationMinutes} minutes`, 14, 30);
const settings = wsoCurrentSessionPlan.settingsUsed;
let settingsText = `Config: Work ${settings.workSprintDuration}m, Short Break ${settings.shortBreakDuration}m`;
if(settings.longBreakEnabled) {
settingsText += `, Long Break ${settings.longBreakDuration}m after ${settings.sprintsBeforeLongBreak} sprints.`;
}
doc.text(settingsText, 14, 36);
doc.text(`Plan Generated: ${new Date(wsoCurrentSessionPlan.generatedAt).toLocaleDateString()}`, 14, 42);
let yPos = 50;
if (wsoCurrentSessionPlan.userTasksForSession && wsoCurrentSessionPlan.userTasksForSession.length > 0) {
doc.setFontSize(12);
doc.setTextColor(primaryColor);
doc.text("Intended Tasks for this Session:", 14, yPos); yPos +=7;
doc.setFontSize(9);
doc.setTextColor(50);
wsoCurrentSessionPlan.userTasksForSession.forEach(task => {
if (yPos > 270) { doc.addPage(); yPos = 20; }
doc.text(`\u2022 ${task.name} ${task.estimatedTimeMinutes ? `(Est: ${task.estimatedTimeMinutes}m)` : ''}`, 16, yPos);
yPos += 5;
});
yPos += 5;
}
doc.setFontSize(12);
doc.setTextColor(primaryColor);
doc.text("Generated Interval Schedule:", 14, yPos); yPos+=2;
const tableColumn = ["#", "Type", "Duration (min)", "Session Start", "Session End"];
const tableRows = [];
wsoCurrentSessionPlan.intervals.forEach((interval, index) => {
tableRows.push([
index + 1,
interval.type.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()),
interval.duration,
wsoMinutesToTime(interval.start),
wsoMinutesToTime(interval.end)
]);
});
doc.autoTable({
head: [tableColumn], body: tableRows, startY: yPos,
theme: 'striped', headStyles: { fillColor: primaryColor }
});
doc.save(`${(wsoCurrentSessionPlan.planName || 'WorkSessionPlan').replace(/\s+/g, '_')}.pdf`);
}
// --- Initialization ---
document.addEventListener('DOMContentLoaded', () => {
// Assign DOM elements
wsoSessionNameInput = document.getElementById('wsoSessionName');
wsoTotalSessionDurationInput = document.getElementById('wsoTotalSessionDuration');
wsoOverrideWorkDurationInput = document.getElementById('wsoOverrideWorkDuration');
wsoOverrideShortBreakDurationInput = document.getElementById('wsoOverrideShortBreakDuration');
wsoOverrideLongBreakEnabledCheckbox = document.getElementById('wsoOverrideLongBreakEnabled');
wsoOverrideLongBreakSettingsEl = document.getElementById('wsoOverrideLongBreakSettings');
wsoOverrideLongBreakDurationInput = document.getElementById('wsoOverrideLongBreakDuration');
wsoOverrideSprintsBeforeLongBreakInput = document.getElementById('wsoOverrideSprintsBeforeLongBreak');
wsoAddTaskToSessionForm = document.getElementById('wsoAddTaskToSessionForm');
wsoSessionTaskNameInput = document.getElementById('wsoSessionTaskNameInput');
wsoSessionTaskEstTimeInput = document.getElementById('wsoSessionTaskEstTimeInput');
wsoSessionTasksListEl = document.getElementById('wsoSessionTasksList');
wsoGeneratedPlanAreaEl = document.getElementById('wsoGeneratedPlanArea');
wsoGeneratedPlanOutputEl = document.getElementById('wsoGeneratedPlanOutput');
wsoStartSessionButtonEl = document.getElementById('wsoStartSessionButton');
wsoActiveSessionAreaEl = document.getElementById('wsoActiveSessionArea');
wsoCurrentIntervalNameDisplayEl = document.getElementById('wsoCurrentIntervalNameDisplay');
wsoTimerCountdownDisplayEl = document.getElementById('wsoTimerCountdownDisplay');
wsoSessionProgressDisplayEl = document.getElementById('wsoSessionProgressDisplay');
wsoCurrentTaskInputGroupEl = document.getElementById('wsoCurrentTaskInputGroup');
wsoCurrentTaskDescriptionInputEl = document.getElementById('wsoCurrentTaskDescription');
wsoPauseResumeButtonEl = document.getElementById('wsoPauseResumeButton');
wsoNextIntervalButtonEl = document.getElementById('wsoNextIntervalButton');
wsoStopSessionButtonEl = document.getElementById('wsoStopSessionButton');
wsoDefaultWorkDurationInput = document.getElementById('wsoDefaultWorkDuration');
wsoDefaultShortBreakDurationInput = document.getElementById('wsoDefaultShortBreakDuration');
wsoDefaultLongBreakEnabledCheckbox = document.getElementById('wsoDefaultLongBreakEnabled');
wsoDefaultLongBreakFieldsEl = document.getElementById('wsoDefaultLongBreakFields');
wsoDefaultLongBreakDurationInput = document.getElementById('wsoDefaultLongBreakDuration');
wsoDefaultSprintsBeforeLongBreakInput = document.getElementById('wsoDefaultSprintsBeforeLongBreak');
wsoSoundAlertsCheckbox = document.getElementById('wsoSoundAlerts');
wsoBrowserNotificationsCheckbox = document.getElementById('wsoBrowserNotifications');
wsoDownloadPdfButton = document.getElementById('wsoDownloadPdfButton');
// Attach event listeners
if(wsoDefaultLongBreakEnabledCheckbox) {
wsoDefaultLongBreakEnabledCheckbox.addEventListener('change', function() {
if(wsoDefaultLongBreakFieldsEl) wsoDefaultLongBreakFieldsEl.style.display = this.checked ? 'block' : 'none';
});
}
if(wsoOverrideLongBreakEnabledCheckbox) {
wsoOverrideLongBreakEnabledCheckbox.addEventListener('change', function() {
if(wsoOverrideLongBreakSettingsEl) wsoOverrideLongBreakSettingsEl.style.display = this.checked ? 'block' : 'none';
if (this.checked) { // Pre-fill with defaults if enabling override
wsoOverrideLongBreakDurationInput.value = wsoOverrideLongBreakDurationInput.value || wsoSettings.defaultLongBreakDuration;
wsoOverrideSprintsBeforeLongBreakInput.value = wsoOverrideSprintsBeforeLongBreakInput.value || wsoSettings.defaultSprintsBeforeLongBreak;
}
});
}
if(wsoAddTaskToSessionForm) wsoAddTaskToSessionForm.addEventListener('submit', handleAddTaskToSessionFormSubmit);
if(wsoStartSessionButtonEl) wsoStartSessionButtonEl.onclick = () => {
if (!wsoCurrentSessionPlan) { alert("Please generate a plan first."); return; }
wsoActiveSessionState.currentIntervalIndex = -1;
wsoActiveSessionState.sessionStartTimeEpoch = Date.now(); // Mark overall session start
wsoStartNextActiveInterval();
};
if(wsoPauseResumeButtonEl) wsoPauseResumeButtonEl.onclick = wsoTogglePauseResumeActiveTimer;
if(wsoNextIntervalButtonEl) wsoNextIntervalButtonEl.onclick = () => {
// Log actual time spent in current interval if ending early? More complex.
// For now, just skips to next planned.
clearInterval(wsoActiveSessionState.timerIntervalId);
wsoStartNextActiveInterval(true); // Pass true if skipping
};
if(wsoStopSessionButtonEl) wsoStopSessionButtonEl.onclick = () => wsoStopActiveSession(false);
if(wsoDownloadPdfButton) wsoDownloadPdfButton.onclick = handleWsoPdfDownload;
wsoLoadGlobalSettings();
// Initialize override placeholders based on loaded defaults
if(wsoOverrideWorkDurationInput) wsoOverrideWorkDurationInput.placeholder = `Default: ${wsoSettings.defaultWorkSprintDuration}`;
if(wsoOverrideShortBreakDurationInput) wsoOverrideShortBreakDurationInput.placeholder = `Default: ${wsoSettings.defaultShortBreakDuration}`;
if(wsoOverrideLongBreakEnabledCheckbox) wsoOverrideLongBreakEnabledCheckbox.checked = wsoSettings.defaultLongBreakEnabled; // Set override checkbox based on default
if(wsoOverrideLongBreakSettingsEl) wsoOverrideLongBreakSettingsEl.style.display = wsoSettings.defaultLongBreakEnabled ? 'block' : 'none'; // Show/hide based on default initially
if(wsoOverrideLongBreakDurationInput) wsoOverrideLongBreakDurationInput.placeholder = `Default: ${wsoSettings.defaultLongBreakDuration}`;
if(wsoOverrideSprintsBeforeLongBreakInput) wsoOverrideSprintsBeforeLongBreakInput.placeholder = `Default: ${wsoSettings.defaultSprintsBeforeLongBreak}`;
const firstTabButton = document.querySelector('#workSessionOptimizerTool .wso-tab-button');
if (firstTabButton) firstTabButton.click();
if (wsoBrowserNotificationsCheckbox) {
wsoBrowserNotificationsCheckbox.addEventListener('change', function() {
wsoSettings.browserNotifications = this.checked;
if (this.checked && typeof Notification !== 'undefined' && Notification.permission !== "granted" && Notification.permission !== "denied") {
Notification.requestPermission();
}
});
}
// Initial check for notification permission if already enabled in settings
if (wsoSettings.browserNotifications && typeof Notification !== 'undefined' && Notification.permission !== "granted" && Notification.permission !== "denied") {
Notification.requestPermission();
}
});