No entries yet. Go to the "Log New Entry" tab to start tracking.
`;
return;
}
let tableHTML = `
| Date |
Pain |
Stiffness |
Joints |
Notes |
Actions |
`;
symptomData.forEach((entry, index) => {
tableHTML += `
| ${new Date(entry.date).toLocaleDateString()} |
${entry.pain} |
${entry.stiffness} |
${entry.joints.join(', ') || 'None'} |
${entry.notes || '-'} |
|
`;
});
tableHTML += `
`;
historyTableContainer.innerHTML = tableHTML;
};
const renderSymptomChart = () => {
if (symptomChart) {
symptomChart.destroy();
}
if (symptomData.length === 0) {
chartCanvas.style.display = 'none';
return;
}
chartCanvas.style.display = 'block';
// Prepare data for chart (show last 30 entries)
const chartData = [...symptomData].reverse().slice(-30);
const labels = chartData.map(d => new Date(d.date).toLocaleDateString());
const painData = chartData.map(d => d.pain);
const stiffnessData = chartData.map(d => d.stiffness);
symptomChart = new Chart(chartCanvas, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Pain Level',
data: painData,
borderColor: '#ef4444', // red-500
backgroundColor: '#ef4444',
tension: 0.1
},
{
label: 'Stiffness Level',
data: stiffnessData,
borderColor: '#3b82f6', // blue-500
backgroundColor: '#3b82f6',
tension: 0.1
}
]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
max: 10
}
}
}
});
};
const saveEntry = () => {
const selectedJoints = Array.from(document.querySelectorAll('input[name="joint"]:checked')).map(cb => cb.value);
const entry = {
date: logDateInput.value,
pain: painSlider.value,
stiffness: stiffnessSlider.value,
joints: selectedJoints,
notes: notesInput.value
};
// Prevent duplicate entries for the same day
const existingIndex = symptomData.findIndex(d => d.date === entry.date);
if (existingIndex > -1) {
if (!confirm('An entry for this date already exists. Do you want to overwrite it?')) {
return;
}
symptomData[existingIndex] = entry;
} else {
symptomData.push(entry);
}
localStorage.setItem('symptomTrackerData', JSON.stringify(symptomData));
alert('Entry saved successfully!');
resetForm();
renderDashboard();
};
const deleteEntry = (index) => {
if (confirm('Are you sure you want to delete this entry?')) {
symptomData.splice(index, 1);
localStorage.setItem('symptomTrackerData', JSON.stringify(symptomData));
renderDashboard();
}
};
const resetForm = () => {
logDateInput.valueAsDate = new Date();
painSlider.value = 5;
painValue.textContent = 5;
stiffnessSlider.value = 5;
stiffnessValue.textContent = 5;
document.querySelectorAll('input[name="joint"]:checked').forEach(cb => cb.checked = false);
notesInput.value = '';
};
// IV. Tab Navigation
const switchTab = (targetTabId) => {
tabs.forEach(tab => {
const isTarget = tab.dataset.tab === targetTabId;
tab.classList.toggle('active', isTarget);
tab.classList.toggle('inactive', !isTarget);
});
tabContents.forEach(content => {
content.classList.toggle('hidden', content.id !== targetTabId);
});
updateNavButtons();
};
const updateNavButtons = () => {
const activeTab = document.querySelector('.tab-button.active').dataset.tab;
prevBtn.disabled = activeTab === 'log-entry';
nextBtn.disabled = activeTab === 'dashboard';
prevBtn.classList.toggle('opacity-50', prevBtn.disabled);
nextBtn.classList.toggle('opacity-50', nextBtn.disabled);
};
// V. Event Listeners
tabs.forEach(tab => {
tab.addEventListener('click', () => switchTab(tab.dataset.tab));
});
nextBtn.addEventListener('click', () => {
const activeTab = document.querySelector('.tab-button.active').dataset.tab;
if (activeTab === 'log-entry') switchTab('dashboard');
});
prevBtn.addEventListener('click', () => {
const activeTab = document.querySelector('.tab-button.active').dataset.tab;
if (activeTab === 'dashboard') switchTab('log-entry');
});
painSlider.addEventListener('input', (e) => painValue.textContent = e.target.value);
stiffnessSlider.addEventListener('input', (e) => stiffnessValue.textContent = e.target.value);
saveEntryBtn.addEventListener('click', saveEntry);
historyTableContainer.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-btn')) {
const index = e.target.dataset.index;
deleteEntry(index);
}
});
// VI. PDF Generation
pdfDownloadBtn.addEventListener('click', () => {
if (symptomData.length === 0) {
alert('No data to generate a report.');
return;
}
const { jsPDF } = jspdf;
const doc = new jsPDF();
const pageW = doc.internal.pageSize.getWidth();
const margin = 15;
// Header
doc.setFillColor(30, 41, 59);
doc.rect(0, 0, pageW, 25, 'F');
doc.setFontSize(16);
doc.setFont('helvetica', 'bold');
doc.setTextColor(255, 255, 255);
doc.text('Joint Health & Arthritis Symptom Report', margin, 16);
// Report Date
doc.setFontSize(10);
doc.setFont('helvetica', 'normal');
doc.setTextColor(150, 150, 150);
doc.text(`Report Generated: ${new Date().toLocaleString()}`, margin, 22);
// Chart Image
if (symptomChart && chartCanvas.style.display !== 'none') {
const chartImage = chartCanvas.toDataURL('image/png', 1.0);
doc.setFontSize(12);
doc.setFont('helvetica', 'bold');
doc.setTextColor(30, 41, 59);
doc.text('Symptom Trend Chart', margin, 40);
doc.addImage(chartImage, 'PNG', margin, 45, pageW - (margin * 2), (pageW - (margin * 2)) * 0.5);
}
// Data Table
const tableStartY = doc.lastAutoTable ? doc.lastAutoTable.finalY + 15 : 120;
doc.setFontSize(12);
doc.setFont('helvetica', 'bold');
doc.setTextColor(30, 41, 59);
doc.text('Detailed Symptom Log', margin, tableStartY);
const tableData = symptomData.map(entry => [
new Date(entry.date).toLocaleDateString(),
entry.pain,
entry.stiffness,
entry.joints.join(', ') || 'N/A',
entry.notes || 'N/A'
]);
doc.autoTable({
startY: tableStartY + 5,
head: [['Date', 'Pain', 'Stiffness', 'Affected Joints', 'Notes']],
body: tableData,
headStyles: { fillColor: [71, 85, 105] }, // slate-600
theme: 'grid'
});
doc.save('Symptom_Tracker_Report.pdf');
});
// Initial Render
renderDashboard();
updateNavButtons();
});