Attention Span Tracker

Attention Span Tracker

Dashboard

Your attention span metrics at a glance.

Focus Duration by Task (minutes)

Log New Session

Enter the details of your focus session below.

Data Configuration & Log

View, edit, or delete your past session logs. All changes will update the dashboard.

Date Task Name Category Duration (min) Focus Actions
×

Edit Session Log

'; } else { categories.sort((a, b) => categoryDurations[b] - categoryDurations[a]).forEach(category => { const duration = categoryDurations[category]; const barHeight = maxDuration > 0 ? (duration / maxDuration) * 100 : 0; const barGroup = document.createElement('div'); barGroup.className = 'ast-chart-bar-group'; barGroup.innerHTML = `
${duration}
${category} `; dashboardChart.appendChild(barGroup); }); } }, renderDataTable() { if (!dataTableBody) return; dataTableBody.innerHTML = ''; // Clear existing table if (attentionData.length === 0) { dataTableBody.innerHTML = 'No data logged yet.'; return; } // Sort by date, most recent first const sortedData = [...attentionData].sort((a, b) => new Date(b.date) - new Date(a.date)); sortedData.forEach((entry, index) => { // Find original index to allow editing/deleting unsorted data const originalIndex = attentionData.indexOf(entry); const row = document.createElement('tr'); row.innerHTML = ` ${entry.date} ${entry.task} ${entry.category} ${entry.duration} ${entry.focus} `; dataTableBody.appendChild(row); }); }, // --- DATA MANIPULATION --- logSession() { if (!logTaskName || !logDuration || !logDate) return; const newEntry = { date: logDate.value, task: logTaskName.value, category: logTaskCategory.value, duration: parseInt(logDuration.value, 10), focus: logFocusLevel.value }; // Validation if (!newEntry.date || !newEntry.task || isNaN(newEntry.duration) || newEntry.duration <= 0) { alert("Please fill in all fields correctly. Duration must be a positive number."); return; } attentionData.push(newEntry); // Re-render this.renderDashboard(); this.renderDataTable(); // Reset form if (logForm) logForm.reset(); logDate.value = new Date().toISOString().split('T')[0]; // Navigate to Dashboard currentTabIndex = 0; showAstTab(null, 'ast-tab-1'); // Manually trigger the class change on the first tab link document.querySelector('.ast-container .ast-tab-link').classList.add('active'); }, showEditModal(index) { if (index < 0 || index >= attentionData.length || !editModal) return; currentEditIndex = index; const entry = attentionData[index]; editIndexInput.value = index; editTaskName.value = entry.task; editTaskCategory.value = entry.category; editDuration.value = entry.duration; editFocusLevel.value = entry.focus; editDate.value = entry.date; editModal.style.display = "block"; }, hideEditModal() { if (editModal) editModal.style.display = "none"; currentEditIndex = null; }, saveChanges() { if (currentEditIndex === null || !editTaskName) return; const updatedEntry = { date: editDate.value, task: editTaskName.value, category: editTaskCategory.value, duration: parseInt(editDuration.value, 10), focus: editFocusLevel.value }; // Validation if (!updatedEntry.date || !updatedEntry.task || isNaN(updatedEntry.duration) || updatedEntry.duration <= 0) { alert("Please fill in all fields correctly. Duration must be a positive number."); return; } attentionData[currentEditIndex] = updatedEntry; this.renderDashboard(); this.renderDataTable(); this.hideEditModal(); }, deleteEntry(index) { if (index < 0 || index >= attentionData.length) return; if (confirm("Are you sure you want to delete this log entry?")) { attentionData.splice(index, 1); this.renderDashboard(); this.renderDataTable(); } }, // --- NAVIGATION & PDF --- navigateTabs(direction) { let newIndex = currentTabIndex + direction; // Clamp index if (newIndex < 0) newIndex = 0; if (newIndex >= tabs.length) newIndex = tabs.length - 1; if (newIndex !== currentTabIndex) { currentTabIndex = newIndex; tabs[currentTabIndex].click(); // Programmatically click the tab } }, downloadPDF() { // This method uses the browser's print functionality, // which allows "Save as PDF" and respects print CSS. // 1. Create a temporary wrapper to satisfy the @media print rules const printContainer = document.createElement('div'); printContainer.className = 'ast-print-container'; const toolContainer = document.getElementById('ast-container'); // 2. Move the tool into the print wrapper document.body.appendChild(printContainer); printContainer.appendChild(toolContainer); // 3. Add the printing class to the body document.body.classList.add('ast-printing'); // 4. Trigger the print dialog window.print(); // 5. Clean up after print dialog (whether printed or cancelled) // 'onafterprint' is the most reliable way window.onafterprint = () => { document.body.classList.remove('ast-printing'); // 6. Move the tool *back* to its original location // We assume the original location was just before this