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

      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