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');
});