Distraction Tracker

Distraction Tracker

Today's Distractions (0)

Use the "Log Distraction Now" button above or edit entries below.

    Add New Distraction Category

    My Distraction Categories

      Filter Report Data

      While working on: ${log.taskBeingWorkedOn}

      ` : ''} ${log.notes ? `

      Notes: ${log.notes}

      ` : ''}
      `; 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 })();
      Scroll to Top