No entries for this day.
`;
return;
}
entriesForDay.forEach(entry => {
const item = document.createElement('div');
item.className = 'p-3 bg-white rounded-lg shadow-sm border border-gray-200 flex items-center justify-between';
item.innerHTML = `
${entry.activity} (${entry.hours} hrs)
${entry.category}
`;
dailyLogList.appendChild(item);
});
};
const renderReport = () => {
const today = new Date(entryDateInput.value + 'T00:00:00');
const dayOfWeek = today.getDay();
const mondayOffset = (dayOfWeek === 0) ? -6 : 1 - dayOfWeek;
const monday = new Date(today);
monday.setDate(today.getDate() + mondayOffset);
const sunday = new Date(monday);
sunday.setDate(monday.getDate() + 6);
reportWeekDates.textContent = `${formatDate(getIsoDate(monday))} - ${formatDate(getIsoDate(sunday))}`;
const weekEntries = timeEntries.filter(e => {
const entryDate = new Date(e.date + 'T00:00:00');
return entryDate >= monday && entryDate <= sunday;
});
const categoryTotals = categories.reduce((acc, cat) => ({...acc, [cat]: 0}), {});
weekEntries.forEach(e => {
categoryTotals[e.category] = (categoryTotals[e.category] || 0) + parseFloat(e.hours);
});
// Render summary list
categorySummary.innerHTML = '';
categories.forEach(cat => {
const total = categoryTotals[cat] || 0;
const li = document.createElement('li');
li.className = 'flex justify-between items-center text-sm';
li.innerHTML = `
${cat}
${total.toFixed(1)} hrs
`;
categorySummary.appendChild(li);
});
// Render Chart
if (myChart) {
myChart.destroy();
}
myChart = new Chart(chartCanvas, {
type: 'doughnut',
data: {
labels: categories,
datasets: [{
data: categories.map(cat => categoryTotals[cat] || 0),
backgroundColor: categories.map(cat => categoryColors[cat]),
borderColor: '#ffffff',
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
boxWidth: 12
}
}
}
}
});
};
// --- FORM & DATA HANDLING ---
const clearForm = () => {
logForm.reset();
entryIdInput.value = '';
entryDateInput.value = getIsoDate(new Date());
};
const handleFormSubmit = (e) => {
e.preventDefault();
const id = entryIdInput.value ? parseInt(entryIdInput.value) : Date.now();
const entryData = {
id,
date: entryDateInput.value,
activity: activityNameInput.value,
category: activityCategoryInput.value,
hours: activityHoursInput.value
};
const existingIndex = timeEntries.findIndex(entry => entry.id === id);
if (existingIndex > -1) {
timeEntries[existingIndex] = entryData;
} else {
timeEntries.push(entryData);
}
renderDailyLog(entryDateInput.value);
clearForm();
};
const handleDailyLogClick = (e) => {
const editBtn = e.target.closest('.edit-btn');
const deleteBtn = e.target.closest('.delete-btn');
if (editBtn) {
const id = parseInt(editBtn.dataset.id);
const entry = timeEntries.find(e => e.id === id);
if (entry) {
entryIdInput.value = entry.id;
entryDateInput.value = entry.date;
activityNameInput.value = entry.activity;
activityCategoryInput.value = entry.category;
activityHoursInput.value = entry.hours;
}
} else if (deleteBtn) {
const id = parseInt(deleteBtn.dataset.id);
timeEntries = timeEntries.filter(e => e.id !== id);
renderDailyLog(entryDateInput.value);
}
};
// --- TAB NAVIGATION ---
const switchTab = (tabName) => {
const isLog = tabName === 'log';
tabLog.classList.toggle('active', isLog);
tabLog.classList.toggle('inactive', !isLog);
tabReport.classList.toggle('active', !isLog);
tabReport.classList.toggle('inactive', isLog);
contentLog.classList.toggle('hidden', !isLog);
contentReport.classList.toggle('hidden', isLog);
prevBtn.disabled = isLog;
nextBtn.disabled = !isLog;
if (!isLog) {
renderReport();
}
};
// --- PDF DOWNLOAD ---
const handleDownloadPdf = () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.setFontSize(18);
doc.text("Work-Life Balance Weekly Report", 14, 22);
doc.setFontSize(11);
doc.setTextColor(100);
doc.text(reportWeekDates.textContent, 14, 30);
const chartImage = chartCanvas.toDataURL('image/png');
doc.addImage(chartImage, 'PNG', 15, 40, 90, 90);
const summaryData = categories.map(cat => {
const total = (timeEntries.filter(e => e.category === cat)
.reduce((sum, e) => sum + parseFloat(e.hours), 0)).toFixed(1);
return [cat, `${total} hrs`];
});
doc.autoTable({
startY: 40,
startX: 115,
head: [['Category', 'Total Hours']],
body: summaryData,
theme: 'grid',
headStyles: { fillColor: [79, 70, 229] },
});
doc.save('Work-Life-Balance-Report.pdf');
};
// --- EVENT LISTENERS ---
tabLog.addEventListener('click', () => switchTab('log'));
tabReport.addEventListener('click', () => switchTab('report'));
prevBtn.addEventListener('click', () => switchTab('log'));
nextBtn.addEventListener('click', () => switchTab('report'));
logForm.addEventListener('submit', handleFormSubmit);
clearFormBtn.addEventListener('click', clearForm);
entryDateInput.addEventListener('change', (e) => renderDailyLog(e.target.value));
dailyLogList.addEventListener('click', handleDailyLogClick);
downloadPdfBtn.addEventListener('click', handleDownloadPdf);
// --- INITIALIZATION ---
const initializeTool = () => {
const today = new Date();
const todayStr = getIsoDate(today);
const yesterdayStr = getIsoDate(new Date(today.setDate(today.getDate() - 1)));
timeEntries = [
{ id: 1, date: todayStr, activity: 'Team Sync Meeting', category: 'Work', hours: 1 },
{ id: 2, date: todayStr, activity: 'Coding new feature', category: 'Work', hours: 4.5 },
{ id: 3, date: todayStr, activity: 'Lunch Break', category: 'Leisure & Hobbies', hours: 1 },
{ id: 4, date: todayStr, activity: 'Gym Session', category: 'Health & Fitness', hours: 1.5 },
{ id: 5, date: yesterdayStr, activity: 'Client Presentation Prep', category: 'Work', hours: 3 },
{ id: 6, date: yesterdayStr, activity: 'Sleep', category: 'Sleep', hours: 8 },
{ id: 7, date: yesterdayStr, activity: 'Grocery Shopping', category: 'Chores & Errands', hours: 1 },
];
clearForm();
renderDailyLog(entryDateInput.value);
switchTab('log');
};
initializeTool();
});