Work Session Planner
Organize your tasks into focused work and break intervals.
Session Settings
Add Tasks
Task List
Total Work Time
0 min
Total Break Time
0 min
Estimated End Time
--:--
Your Session Schedule
No tasks added yet.
`; } else { tasks.forEach(task => { const taskEl = document.createElement('div'); taskEl.className = 'flex items-center justify-between bg-gray-100 p-3 rounded-md'; taskEl.innerHTML = ` ${task.name}
${task.duration} min
`;
taskListContainer.appendChild(taskEl);
});
}
};
const renderPlan = () => {
const { schedule, totalWork, totalBreak, endTime } = generateSchedule();
scheduleContainer.innerHTML = '';
if (schedule.length === 0) {
scheduleContainer.innerHTML = `Add tasks on the previous tab to generate a session plan.
`; } schedule.forEach(item => { const itemEl = document.createElement('div'); itemEl.className = `schedule-item ${item.type}`; itemEl.innerHTML = `${item.time}
${item.title}
${item.duration} min
`;
scheduleContainer.appendChild(itemEl);
});
totalWorkTimeEl.textContent = `${totalWork} min`;
totalBreakTimeEl.textContent = `${totalBreak} min`;
endTimeEl.textContent = endTime;
};
const renderAll = () => {
renderTaskList();
renderPlan();
};
// --- CORE LOGIC --- //
const generateSchedule = () => {
const workDuration = parseInt(settings.workDuration.value);
const shortBreakDuration = parseInt(settings.shortBreak.value);
const longBreakDuration = parseInt(settings.longBreak.value);
const sessionsForLongBreak = parseInt(settings.sessionsBeforeLongBreak.value);
const startTimeValue = settings.startTime.value;
let schedule = [];
let remainingTasks = JSON.parse(JSON.stringify(tasks));
let totalWork = 0;
let totalBreak = 0;
let sessionCounter = 0;
let currentTime = new Date();
if (startTimeValue) {
const [hours, minutes] = startTimeValue.split(':');
currentTime.setHours(hours, minutes, 0, 0);
}
const formatTime = (date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
while (remainingTasks.length > 0) {
sessionCounter++;
totalWork += workDuration;
let timeForThisWorkBlock = workDuration;
let workBlockTasks = [];
while (timeForThisWorkBlock > 0 && remainingTasks.length > 0) {
let currentTask = remainingTasks[0];
if (currentTask.duration <= timeForThisWorkBlock) {
workBlockTasks.push(`${currentTask.name} (${currentTask.duration} min)`);
timeForThisWorkBlock -= currentTask.duration;
remainingTasks.shift();
} else {
workBlockTasks.push(`${currentTask.name} (partial)`);
currentTask.duration -= timeForThisWorkBlock;
timeForThisWorkBlock = 0;
}
}
const workStartTime = formatTime(currentTime);
schedule.push({ type: 'work', time: workStartTime, title: `Work Interval: ${workBlockTasks.join(', ')}`, duration: workDuration });
currentTime.setMinutes(currentTime.getMinutes() + workDuration);
if (remainingTasks.length > 0) {
let breakDuration;
let breakType;
if (sessionCounter % sessionsForLongBreak === 0) {
breakDuration = longBreakDuration;
breakType = 'Long Break';
totalBreak += longBreakDuration;
} else {
breakDuration = shortBreakDuration;
breakType = 'Short Break';
totalBreak += shortBreakDuration;
}
const breakStartTime = formatTime(currentTime);
schedule.push({ type: 'break', time: breakStartTime, title: breakType, duration: breakDuration });
currentTime.setMinutes(currentTime.getMinutes() + breakDuration);
}
}
const endTime = schedule.length > 0 ? formatTime(currentTime) : (startTimeValue ? formatTime(currentTime) : '--:--');
return { schedule, totalWork, totalBreak, endTime };
};
// --- EVENT HANDLERS --- //
taskForm.addEventListener('submit', (e) => {
e.preventDefault();
const name = taskNameInput.value.trim();
const duration = parseInt(taskDurationInput.value);
if (!name || isNaN(duration) || duration <= 0) {
alert('Please enter a valid task name and duration.');
return;
}
tasks.push({ id: Date.now(), name, duration });
taskForm.reset();
renderTaskList();
});
window.deleteTask = (id) => {
tasks = tasks.filter(task => task.id !== id);
renderTaskList();
};
downloadPdfBtn.addEventListener('click', () => {
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });
const { schedule, totalWork, totalBreak, endTime } = generateSchedule();
pdf.setFontSize(18);
pdf.text("Work Session Plan", 40, 40);
pdf.setFontSize(12);
pdf.text(`Total Work: ${totalWork} min | Total Break: ${totalBreak} min | End Time: ${endTime}`, 40, 60);
if (schedule.length > 0) {
pdf.autoTable({
startY: 80,
head: [['Time', 'Type', 'Details', 'Duration (min)']],
body: schedule.map(item => [item.time, item.type.charAt(0).toUpperCase() + item.type.slice(1), item.title, item.duration]),
theme: 'grid',
headStyles: { fillColor: [41, 128, 185] },
didParseCell: function (data) {
if (data.row.raw[1] === 'Break') {
data.cell.styles.fillColor = '#f0fdf4'; // Light green
data.cell.styles.textColor = '#15803d'; // Darker green
}
}
});
} else {
pdf.text("No tasks were added to generate a plan.", 40, 90);
}
pdf.save('Work_Session_Plan.pdf');
});
// --- TAB NAVIGATION --- //
const updateTabs = (targetTab) => {
currentTab = tabs.indexOf(targetTab);
tabContents.forEach(content => content.classList.add('hidden'));
tabButtons.forEach(button => button.classList.remove('active'));
const activeContent = document.getElementById(`${targetTab}-tab`);
const activeButton = document.querySelector(`.tab-btn[data-tab="${targetTab}"]`);
if(activeContent) activeContent.classList.remove('hidden');
if(activeButton) activeButton.classList.add('active');
prevBtn.disabled = currentTab === 0;
nextBtn.disabled = currentTab === tabs.length - 1;
if (targetTab === 'plan') {
renderPlan();
}
};
tabButtons.forEach(button => {
button.addEventListener('click', () => updateTabs(button.dataset.tab));
});
prevBtn.addEventListener('click', () => {
if (currentTab > 0) {
updateTabs(tabs[currentTab - 1]);
}
});
nextBtn.addEventListener('click', () => {
if (currentTab < tabs.length - 1) {
updateTabs(tabs[currentTab + 1]);
}
});
// --- INITIALIZATION --- //
const loadSampleData = () => {
tasks = [
{ id: Date.now() + 1, name: 'Draft quarterly report introduction', duration: 45 },
{ id: Date.now() + 2, name: 'Prepare slides for marketing team presentation', duration: 60 },
{ id: Date.now() + 3, name: 'Review and reply to client emails', duration: 20 },
];
renderAll();
};
loadSampleData();
updateTabs('setup'); // Set initial tab state
});
