`;
todaysDistractionsListUL.appendChild(li);
});
todaysDistractionsListUL.querySelectorAll('.edit-log-btn').forEach(btn => btn.addEventListener('click', (e) => openLogModal(e.target.dataset.id)));
todaysDistractionsListUL.querySelectorAll('.delete-log-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
if (confirm('Delete this log entry?')) {
distractionLog = distractionLog.filter(log => log.id !== e.target.dataset.id);
saveLog();
renderTodaysDistractions();
}
});
});
}
// --- Analysis & Report Logic ---
reportDateRangeSelect.addEventListener('change', function() {
customReportRangeGroup.style.display = this.value === 'custom' ? 'flex' : 'none';
if (this.value !== 'custom') generateAnalysisReportBtn.click(); // Auto-generate for predefined ranges
});
function setDefaultReportDates() {
const today = new Date();
const todayISO = new Date(today.getTime() - today.getTimezoneOffset() * 60000).toISOString().slice(0,10);
reportEndDateInput.value = todayISO;
const sevenDaysAgo = new Date(today);
sevenDaysAgo.setDate(today.getDate() - 6);
reportStartDateInput.value = new Date(sevenDaysAgo.getTime() - sevenDaysAgo.getTimezoneOffset() * 60000).toISOString().slice(0,10);
}
generateAnalysisReportBtn.addEventListener('click', () => {
let startDate, endDate;
const period = reportDateRangeSelect.value;
const todayObj = new Date(todayStr + "T00:00:00");
switch(period) {
case 'today': startDate = new Date(todayObj); endDate = new Date(todayObj); break;
case 'yesterday':
startDate = new Date(todayObj); startDate.setDate(todayObj.getDate() - 1);
endDate = new Date(startDate);
break;
case 'last7days': endDate = new Date(todayObj); startDate = new Date(todayObj); startDate.setDate(todayObj.getDate() - 6); break;
case 'last30days': endDate = new Date(todayObj); startDate = new Date(todayObj); startDate.setDate(todayObj.getDate() - 29); break;
case 'custom':
if (!reportStartDateInput.value || !reportEndDateInput.value) { alert('Select custom date range.'); return; }
startDate = new Date(reportStartDateInput.value + "T00:00:00");
endDate = new Date(reportEndDateInput.value + "T00:00:00");
if (startDate > endDate) { alert('Start date cannot be after end date.'); return; }
break;
default: return;
}
endDate.setHours(23,59,59,999); // Inclusive end date
reportPeriodDisplaySpan.textContent = `${formatDate(startDate.toISOString().slice(0,10))} - ${formatDate(endDate.toISOString().slice(0,10))}`;
const filteredLogs = distractionLog.filter(log => {
const logDate = new Date(log.timestamp);
return logDate >= startDate && logDate <= endDate;
});
reportTotalDistractionsSpan.textContent = filteredLogs.length;
// By Category Chart
const categoryCounts = {};
filteredLogs.forEach(log => {
categoryCounts[log.categoryName] = (categoryCounts[log.categoryName] || 0) + 1;
});
renderAnalysisChart(categoryDistractionChartDiv, categoryCounts, "Category");
// By Hour of Day Chart
const hourCounts = Array(24).fill(0); // 0-23 hours
filteredLogs.forEach(log => {
const hour = new Date(log.timestamp).getHours();
hourCounts[hour]++;
});
const hourDataForChart = {};
hourCounts.forEach((count, hr) => { if(count > 0) hourDataForChart[`${String(hr).padStart(2,'0')}:00`] = count; });
renderAnalysisChart(timeOfDayDistractionChartDiv, hourDataForChart, "Hour of Day");
// Detailed Log (last 100 for UI, all for PDF)
reportDetailedLogUL.innerHTML = '';
const recentLogs = filteredLogs.sort((a,b) => new Date(b.timestamp) - new Date(a.timestamp)).slice(0,100);
if(recentLogs.length === 0){ reportDetailedLogUL.innerHTML = '
No distractions logged in this period.';}
recentLogs.forEach(log => {
const li = document.createElement('li');
li.className = 'list-item';
li.innerHTML = `
${formatDate(log.timestamp.slice(0,10))} ${formatTime(log.timestamp)} - ${log.categoryName}
${log.taskBeingWorkedOn ? `
While working on: ${log.taskBeingWorkedOn}
` : ''}
${log.notes ? `
Notes: ${log.notes}
` : ''}
`;
reportDetailedLogUL.appendChild(li);
});
reportOutputDiv.style.display = 'block';
});
function renderAnalysisChart(chartElement, data, labelPrefix = "") {
chartElement.innerHTML = '';
let maxValue = 0;
Object.values(data).forEach(val => { if (val > maxValue) maxValue = val; });
if (maxValue === 0) { chartElement.innerHTML = `
No data for this period.
`; return;}
const sortedKeys = Object.keys(data).sort((a,b) => { // Sort for consistent chart display
if (labelPrefix === "Hour of Day") return parseInt(a.split(':')[0]) - parseInt(b.split(':')[0]); // Sort hours numerically
return data[b] - data[a]; // Sort categories by count descending
});
sortedKeys.forEach(key => {
const value = data[key];
const barGroup = document.createElement('div');
barGroup.className = 'chart-bar-group';
const bar = document.createElement('div');
bar.className = 'chart-bar';
bar.style.height = `${(value / maxValue) * 100}%`;
bar.style.backgroundColor = `hsl(${(Object.keys(data).indexOf(key) * 40) % 360}, 70%, 60%)`; // Cycle colors
bar.textContent = value;
bar.title = `${key}: ${value}`;
const label = document.createElement('div');
label.className = 'chart-label';
label.textContent = key;
barGroup.appendChild(bar);
barGroup.appendChild(label);
chartElement.appendChild(barGroup);
});
}
// --- PDF Download ---
downloadDistractionReportPdfBtn.addEventListener('click', async () => {
if (reportOutputDiv.style.display === 'none') { alert('Please generate a report first.'); return; }
const { jsPDF } = window.jspdf;
const doc = new jsPDF('p', 'pt', 'a4');
const margin = 40; let yPos = margin; const pageWidth = doc.internal.pageSize.getWidth();
doc.setFontSize(18); doc.setTextColor(varToRGB('--primary-color').r, varToRGB('--primary-color').g, varToRGB('--primary-color').b);
doc.text('Distraction Analysis Report', pageWidth / 2, yPos, { align: 'center' }); yPos += 20;
doc.setFontSize(11); doc.setTextColor(100);
doc.text(`Period: ${reportPeriodDisplaySpan.textContent}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 15;
doc.text(`Generated: ${new Date().toLocaleString()}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 25;
doc.setFontSize(10); doc.text(`Total Distractions: ${reportTotalDistractionsSpan.textContent}`, margin, yPos); yPos += 20;
async function addChartToPdf(chartContainerId, titleText, currentY) {
const chartContainer = document.getElementById(chartContainerId);
if (html2canvas && chartContainer && chartContainer.offsetHeight > 0) {
if (currentY > doc.internal.pageSize.getHeight() - 180) { doc.addPage(); currentY = margin; }
doc.setFontSize(12); doc.setTextColor(varToRGB('--primary-color').r, varToRGB('--primary-color').g, varToRGB('--primary-color').b);
doc.text(titleText, margin, currentY); currentY += 15;
try {
const canvas = await html2canvas(chartContainer.querySelector('.chart'), { scale: 1.5, backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--light-bg').trim() });
const imgData = canvas.toDataURL('image/png');
const imgProps = doc.getImageProperties(imgData);
const pdfImgWidth = pageWidth - 2 * margin;
const pdfImgHeight = Math.min(150, (imgProps.height * pdfImgWidth) / imgProps.width); // Max height for chart
doc.addImage(imgData, 'PNG', margin, currentY, pdfImgWidth, pdfImgHeight);
currentY += pdfImgHeight + 10;
} catch (e) { console.error("Chart to PDF error:", e); doc.setTextColor(255,0,0).text("Chart could not be rendered.", margin, currentY); currentY+=15; }
}
return currentY;
}
yPos = await addChartToPdf('categoryChartContainer', 'Distractions by Category', yPos);
yPos = await addChartToPdf('timeOfDayChartContainer', 'Distractions by Hour of Day', yPos);
if (yPos > doc.internal.pageSize.getHeight() - 80) { doc.addPage(); yPos = margin; }
doc.setFontSize(12); doc.setTextColor(varToRGB('--primary-color').r, varToRGB('--primary-color').g, varToRGB('--primary-color').b);
doc.text('Detailed Distraction Log (Filtered Period)', margin, yPos); yPos += 18;
const detailedBody = [];
// Re-filter for PDF to ensure it matches current view, not just UI slice
let startDatePdf, endDatePdf;
const periodPdf = reportDateRangeSelect.value;
const todayObjPdf = new Date(todayStr + "T00:00:00");
switch(periodPdf) { /* Same logic as generateAnalysisReportBtn */
case 'today': startDatePdf = new Date(todayObjPdf); endDatePdf = new Date(todayObjPdf); break;
case 'yesterday': startDatePdf = new Date(todayObjPdf); startDatePdf.setDate(todayObjPdf.getDate() - 1); endDatePdf = new Date(startDatePdf); break;
case 'last7days': endDatePdf = new Date(todayObjPdf); startDatePdf = new Date(todayObjPdf); startDatePdf.setDate(todayObjPdf.getDate() - 6); break;
case 'last30days': endDatePdf = new Date(todayObjPdf); startDatePdf = new Date(todayObjPdf); startDatePdf.setDate(todayObjPdf.getDate() - 29); break;
case 'custom': startDatePdf = new Date(reportStartDateInput.value + "T00:00:00"); endDatePdf = new Date(reportEndDateInput.value + "T00:00:00"); break;
}
if(endDatePdf) endDatePdf.setHours(23,59,59,999);
const logsForPdf = distractionLog.filter(log => {
const logDate = new Date(log.timestamp);
return logDate >= startDatePdf && logDate <= endDatePdf;
}).sort((a,b) => new Date(a.timestamp) - new Date(b.timestamp)); // Chronological for PDF table
logsForPdf.forEach(log => {
detailedBody.push([
formatDate(log.timestamp.slice(0,10)) + " " + formatTime(log.timestamp),
log.categoryName,
log.taskBeingWorkedOn || '',
log.notes || ''
]);
});
if (detailedBody.length > 0) {
doc.autoTable({
startY: yPos, head: [['Timestamp', 'Category', 'Task Worked On', 'Notes']],
body: detailedBody, theme: 'grid',
headStyles: { fillColor: varToRGB('--primary-color', true), textColor: varToRGB('--light-text', true) },
styles:{fontSize:8, cellPadding:3},
columnStyles: { 0:{cellWidth:80}, 1:{cellWidth:80}, 2:{cellWidth:100}, 3:{cellWidth:'auto'} }
});
} else { doc.text("No detailed logs for this period.", margin, yPos); }
doc.save(`Distraction_Analysis_${reportPeriodDisplaySpan.textContent.replace(/\s-\s/g,'_to_')}.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 ---
if (distractionCategories.length === 0) { // First time use
distractionCategories = [...DEFAULT_CATEGORIES];
saveCategories();
}
renderCategoriesList();
renderTodaysDistractions();
setDefaultReportDates();
// generateAnalysisReportBtn.click(); // Optionally generate report on load for default period
})();