Tinnitus Symptom Tracker

Tinnitus Symptom Tracker

Log, track, and visualize your tinnitus symptoms over time.

Loudness Trend (Last 30 Entries)

Sound Type Frequency

Log some symptoms to see your loudness trend.

'; soundTypeCtx.parentElement.innerHTML = '

Log some symptoms to see sound type frequency.

'; return; } else { if (loudnessCtx.classList.contains('hidden')) { loudnessCtx.parentElement.innerHTML = '

Loudness Trend (Last 30 Entries)

'; } if (soundTypeCtx.classList.contains('hidden')) { soundTypeCtx.parentElement.innerHTML = '

Sound Type Frequency

'; } } loudnessChart = new Chart(document.getElementById('loudnessChart'), { type: 'line', data: { labels: recentData.map(e => new Date(e.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })), datasets: [{ label: 'Loudness (1-10)', data: recentData.map(e => e.loudness), borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', tension: 0.3, fill: true, }] }, options: { responsive: true, maintainAspectRatio: true, scales: { y: { beginAtZero: true, max: 10 } } } }); const soundTypeCounts = data.reduce((acc, e) => { acc[e.soundType] = (acc[e.soundType] || 0) + 1; return acc; }, {}); soundTypeChart = new Chart(document.getElementById('soundTypeChart'), { type: 'doughnut', data: { labels: Object.keys(soundTypeCounts), datasets: [{ label: 'Sound Type', data: Object.values(soundTypeCounts), backgroundColor: ['rgba(255, 99, 132, 0.8)', 'rgba(54, 162, 235, 0.8)', 'rgba(255, 206, 86, 0.8)', 'rgba(75, 192, 192, 0.8)', 'rgba(153, 102, 255, 0.8)', 'rgba(255, 159, 64, 0.8)', 'rgba(199, 199, 199, 0.8)'], hoverOffset: 4 }] }, options: { responsive: true, maintainAspectRatio: true } }); } // --- EVENT HANDLERS AND LOGIC --- function setDefaultDateTime() { const now = new Date(); now.setMinutes(now.getMinutes() - now.getTimezoneOffset()); const entryDateInput = document.getElementById('entry-date'); if (entryDateInput) entryDateInput.value = now.toISOString().slice(0, 16); } const loudnessSlider = document.getElementById('loudness'); if (loudnessSlider) { loudnessSlider.addEventListener('input', (event) => { const display = document.getElementById('loudness-value'); if(display) display.textContent = event.target.value; }); } window.addSymptomEntry = function() { const form = document.getElementById('tinnitus-form'); if (!form) return; const entryDate = document.getElementById('entry-date').value; if (!entryDate) { alert('Please select a date and time.'); return; } const newEntry = { id: Date.now(), date: new Date(entryDate).toISOString(), duration: document.getElementById('duration').value, loudness: document.getElementById('loudness').value, pitch: document.getElementById('pitch').value, soundType: document.getElementById('sound-type').value, triggers: Array.from(form.querySelectorAll('input[name="triggers"]:checked')).map(el => el.value), notes: document.getElementById('notes').value.trim() }; const data = getSymptomData(); data.push(newEntry); saveSymptomData(data); form.reset(); setDefaultDateTime(); const display = document.getElementById('loudness-value'); if(display) display.textContent = '5'; alert('Symptom logged successfully!'); refreshUI(); changeTab('history'); }; window.deleteSymptomEntry = function(id) { if (confirm('Are you sure you want to delete this entry?')) { let data = getSymptomData(); saveSymptomData(data.filter(entry => entry.id !== id)); refreshUI(); } }; window.changeTab = function(tabName) { currentTab = tabName; tabs.forEach(tab => { const tabContent = document.getElementById(`${tab}-tab`); const tabButton = document.getElementById(`tab-btn-${tab}`); if (tabContent && tabButton) { tabContent.style.display = (tab === tabName) ? 'block' : 'none'; tabButton.classList.toggle('active', tab === tabName); tabButton.classList.toggle('border-blue-500', tab === tabName); tabButton.classList.toggle('text-blue-600', tab === tabName); tabButton.classList.toggle('text-slate-600', tab !== tabName); } }); }; window.navigateTabs = function(direction) { const currentIndex = tabs.indexOf(currentTab); const nextIndex = direction === 'next' ? (currentIndex + 1) % tabs.length : (currentIndex - 1 + tabs.length) % tabs.length; changeTab(tabs[nextIndex]); }; // --- V2: MEDICAL REPORT PDF DOWNLOAD --- window.downloadPDF = async function() { const { jsPDF } = window.jspdf; const data = getSymptomData(); if (data.length === 0) { alert("No symptom data to generate a report. Please log some entries first."); return; } if (typeof jsPDF.API.autoTable !== 'function') { alert("PDF generation library is not loaded. Please try again."); return; } alert("Generating your beautiful medical report... Please wait."); // --- 1. PREPARE DATA --- const userName = document.getElementById('user-name').value || 'User'; const reportDate = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); const sortedData = [...data].sort((a, b) => new Date(a.date) - new Date(b.date)); // Calculate summary statistics const totalLoudness = sortedData.reduce((sum, entry) => sum + parseInt(entry.loudness, 10), 0); const avgLoudness = (totalLoudness / sortedData.length).toFixed(1); const soundTypeCounts = sortedData.reduce((acc, e) => { acc[e.soundType] = (acc[e.soundType] || 0) + 1; return acc; }, {}); const mostCommonSound = Object.keys(soundTypeCounts).reduce((a, b) => soundTypeCounts[a] > soundTypeCounts[b] ? a : b, 'N/A'); const allTriggers = sortedData.flatMap(e => e.triggers); const triggerCounts = allTriggers.reduce((acc, t) => { acc[t] = (acc[t] || 0) + 1; return acc; }, {}); const mostCommonTrigger = Object.keys(triggerCounts).reduce((a, b) => triggerCounts[a] > triggerCounts[b] ? a : b, 'N/A'); // --- 2. INITIALIZE PDF DOCUMENT --- const doc = new jsPDF('p', 'mm', 'a4'); const pageHeight = doc.internal.pageSize.height; let yPos = 20; // Initial Y position // --- 3. ADD HEADER & FOOTER --- const addHeader = () => { doc.setFontSize(18); doc.setFont('helvetica', 'bold'); doc.text('Tinnitus Symptom Report', 105, 15, { align: 'center' }); }; const addFooter = () => { const pageCount = doc.internal.getNumberOfPages(); doc.setFontSize(8); doc.setFont('helvetica', 'italic'); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.text(`Page ${i} of ${pageCount}`, 105, pageHeight - 10, { align: 'center' }); doc.text(`Report for: ${userName} | Generated on: ${reportDate}`, 15, pageHeight - 10); } }; // --- 4. BUILD PDF CONTENT --- addHeader(); // Report Details doc.setFontSize(11); doc.setFont('helvetica', 'normal'); doc.text(`Prepared for: ${userName}`, 15, yPos + 5); doc.text(`Date Range: ${new Date(sortedData[0].date).toLocaleDateString()} - ${new Date(sortedData[sortedData.length - 1].date).toLocaleDateString()}`, 15, yPos + 10); yPos += 20; // Summary Section doc.setFontSize(14); doc.setFont('helvetica', 'bold'); doc.text('Symptom Summary', 15, yPos); yPos += 8; doc.setFontSize(11); doc.setFont('helvetica', 'normal'); doc.text(`- Total Entries Logged: ${sortedData.length}`, 20, yPos); doc.text(`- Average Loudness: ${avgLoudness} / 10`, 20, yPos + 5); doc.text(`- Most Common Sound: ${mostCommonSound}`, 20, yPos + 10); doc.text(`- Most Frequent Trigger: ${mostCommonTrigger}`, 20, yPos + 15); yPos += 25; // Visualizations (Charts) doc.setFontSize(14); doc.setFont('helvetica', 'bold'); doc.text('Visualizations', 15, yPos); yPos += 8; try { const loudnessChartImg = loudnessChart.toDataURL('image/png', 1.0); const soundTypeChartImg = soundTypeChart.toDataURL('image/png', 1.0); // Add charts side-by-side doc.addImage(loudnessChartImg, 'PNG', 15, yPos, 90, 60); doc.addImage(soundTypeChartImg, 'PNG', 110, yPos, 80, 60); yPos += 70; } catch (e) { console.error("Could not add charts to PDF:", e); doc.setFont('helvetica', 'italic'); doc.text("Charts could not be rendered in this report.", 15, yPos); yPos += 10; } // Detailed Log Table const tableHead = [['Date', 'Loudness', 'Pitch', 'Sound', 'Duration (min)', 'Triggers', 'Notes']]; const tableBody = sortedData.map(entry => [ new Date(entry.date).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }), `${entry.loudness}/10`, entry.pitch, entry.soundType, entry.duration || '-', entry.triggers.join(', ') || '-', entry.notes || '-' ]); doc.autoTable({ head: tableHead, body: tableBody, startY: yPos, theme: 'grid', headStyles: { fillColor: [41, 128, 185], textColor: 255 }, styles: { fontSize: 8 }, columnStyles: { 6: { cellWidth: 'auto' } } // Notes column }); // --- 5. FINALIZE AND SAVE --- addFooter(); doc.save(`Tinnitus-Report-${userName.replace(/\s/g, '_')}-${new Date().toISOString().slice(0,10)}.pdf`); }; // --- INITIAL LOAD --- function refreshUI() { renderHistoryTable(); renderDashboard(); } initializeSampleData(); renderTriggers(); setDefaultDateTime(); refreshUI(); changeTab('dashboard'); });
Scroll to Top