Long COVID Symptom Journal

Long COVID Symptom Journal

Track your journey and find patterns for better management.

How are you feeling today?

Journal History

Your saved entries will appear here.

Triggers: ${entry.pem}

` : ''} ${entry.notes ? `

Notes: ${entry.notes}

` : ''}
`).join(''); }; // --- EVENT LISTENERS --- const attachEventListeners = () => { tabs.forEach(tab => tab.addEventListener('click', () => switchTab(tab.dataset.tab))); prevBtn.addEventListener('click', navigatePrev); nextBtn.addEventListener('click', navigateNext); symptomSlidersContainer.addEventListener('input', e => { if (e.target.classList.contains('symptom-slider')) { document.getElementById(`value-${e.target.dataset.symptomId}`).textContent = e.target.value; } }); addEntryBtn.addEventListener('click', handleAddEntry); getInsightsBtn.addEventListener('click', handleGetInsights); downloadPdfBtn.addEventListener('click', handleDownloadPdf); }; // --- NAVIGATION --- const switchTab = (targetTab) => { tabs.forEach(tab => tab.classList.toggle('active', tab.dataset.tab === targetTab)); tabContents.forEach(content => content.classList.toggle('active', content.id === targetTab)); updateNavButtons(); }; const updateNavButtons = () => { const activeTabIndex = Array.from(tabs).findIndex(tab => tab.classList.contains('active')); prevBtn.style.visibility = activeTabIndex === 0 ? 'hidden' : 'visible'; nextBtn.style.visibility = activeTabIndex === tabs.length - 1 ? 'hidden' : 'visible'; }; const navigatePrev = () => { const activeTabIndex = Array.from(tabs).findIndex(tab => tab.classList.contains('active')); if (activeTabIndex > 0) switchTab(tabs[activeTabIndex - 1].dataset.tab); }; const navigateNext = () => { const activeTabIndex = Array.from(tabs).findIndex(tab => tab.classList.contains('active')); if (activeTabIndex < tabs.length - 1) switchTab(tabs[activeTabIndex + 1].dataset.tab); }; // --- DATA HANDLING --- const handleAddEntry = () => { const entry = { date: entryDateInput.value, symptoms: {}, pem: pemTriggersInput.value, notes: generalNotesInput.value, }; document.querySelectorAll('.symptom-slider').forEach(slider => { entry.symptoms[slider.dataset.symptomId] = slider.value; }); // Avoid duplicate entries for the same day journalEntries = journalEntries.filter(e => e.date !== entry.date); journalEntries.push(entry); saveToLocalStorage(); renderHistory(); alert("Entry saved successfully!"); switchTab('history'); }; const saveToLocalStorage = () => { localStorage.setItem('longCovidJournalEntries', JSON.stringify(journalEntries)); }; const loadFromLocalStorage = () => { const saved = localStorage.getItem('longCovidJournalEntries'); if (saved) { journalEntries = JSON.parse(saved); } }; // --- API & INSIGHTS LOGIC --- const handleGetInsights = async () => { if (journalEntries.length < 3) { alert("Please add at least 3 journal entries to get meaningful insights."); return; } insightsLoader.classList.remove('hidden'); insightsOutput.classList.add('hidden'); getInsightsBtn.disabled = true; const prompt = buildPrompt(); const insightsText = await callGeminiApi(prompt); renderSummary(insightsText); insightsLoader.classList.add('hidden'); insightsOutput.classList.remove('hidden'); getInsightsBtn.disabled = false; }; const buildPrompt = () => { const recentEntries = journalEntries .sort((a, b) => new Date(b.date) - new Date(a.date)) .slice(0, 14); // Analyze last 14 entries const formattedEntries = recentEntries.map(entry => { const symptomStrings = Object.entries(entry.symptoms).map(([id, val]) => { const symptomName = SYMPTOMS.find(s => s.id === id).name; return `${symptomName}: ${val}/10`; }).join(', '); return `Date: ${entry.date}\nSymptoms: ${symptomStrings}\nTriggers: ${entry.pem || 'None noted'}\nNotes: ${entry.notes || 'None'}`; }).join('\n\n'); return ` As a health data analyst AI (not a medical professional), analyze the following Long COVID journal entries. The user is tracking symptoms on a 0-10 scale. For Fatigue, a higher score is better (more energy). For Brain Fog and Sleep Quality, higher is better. For all other symptoms, a higher score is worse. **Journal Data:** ${formattedEntries} **Task:** 1. **Identify Potential Patterns:** Look for correlations between entries. For example, does poor sleep correlate with worse brain fog the next day? Does a specific PEM trigger lead to higher fatigue? Mention at least 2-3 potential patterns you observe. 2. **Highlight Positive Trends:** Point out any positive signs, like days with high energy or good mood, and what factors might have contributed. 3. **Provide Gentle Suggestions:** Based on the patterns, offer 2-3 gentle, non-medical suggestions for the user to consider or discuss with their doctor. **Formatting:** - Use '##' for the main title: "Summary & AI-Powered Insights". - Use '###' for section headings: "Potential Patterns", "Positive Trends", "Gentle Suggestions". - Use bullet points (-) for lists. - Conclude with a clear medical disclaimer. `; }; const callGeminiApi = async (prompt) => { const apiKey = ""; // Provided by environment const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${apiKey}`; const payload = { contents: [{ role: "user", parts: [{ text: prompt }] }] }; try { const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) throw new Error(`API Error: ${response.status}`); const data = await response.json(); return data.candidates[0].content.parts[0].text; } catch (error) { console.error("Gemini API Error:", error); return "## Error\nCould not generate insights. Please try again later.\n\n**Disclaimer:** This is not medical advice."; } }; const renderSummary = (insightsText) => { document.getElementById('summary-date').textContent = `Report generated on ${new Date().toLocaleDateString('en-US')}`; const historyHtml = journalEntries .sort((a,b) => new Date(b.date) - new Date(a.date)) .map(entry => `

${formatDate(entry.date)}

    ${Object.entries(entry.symptoms).map(([id, value]) => `
  • ${SYMPTOMS.find(s=>s.id===id).name}: ${value}/10
  • `).join('')} ${entry.pem ? `
  • Triggers: ${entry.pem}
  • ` : ''} ${entry.notes ? `
  • Notes: ${entry.notes}
  • ` : ''}
`).join(''); const insightsHtml = insightsText .replace(/## (.*?)\n/g, '

$1

') .replace(/### (.*?)\n/g, '

$1

') .replace(/- (.*?)\n/g, '
  • $1
') .replace(/<\/ul>\s*
    /g, ''); summaryContentContainer.innerHTML = `

    Full Journal History

    ${historyHtml}
    ${insightsHtml}`; }; // --- PDF GENERATION --- const handleDownloadPdf = () => { const content = document.getElementById('pdf-content'); if (!content) return; html2canvas(content, { scale: 2, useCORS: true, backgroundColor: '#ffffff' }) .then(canvas => { const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' }); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); const ratio = canvas.width / canvas.height; const imgWidth = pdfWidth - 20; let imgHeight = imgWidth / ratio; let heightLeft = imgHeight; let position = 10; pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight); heightLeft -= (pdfHeight - 20); while (heightLeft > 0) { position = -heightLeft - 10; pdf.addPage(); pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight); heightLeft -= (pdfHeight - 20); } pdf.save('My-Long-COVID-Journal.pdf'); }) .catch(err => console.error("PDF Generation Error:", err)); }; // --- UTILITIES --- const formatDate = (dateString) => { const date = new Date(dateString + 'T00:00:00'); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); }; // --- START THE APP --- initialize(); });
Scroll to Top