${entry.notes ? `
Notes: ${entry.notes}
` : ''}
`).join('');
};
const renderDashboard = () => {
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const recentEntries = logEntries.filter(e => new Date(e.datetime) >= sevenDaysAgo);
if (recentEntries.length > 0) {
const values = recentEntries.map(e => e.value);
avgReadingEl.textContent = (values.reduce((a, b) => a + b, 0) / values.length).toFixed(0);
lowReadingEl.textContent = Math.min(...values);
highReadingEl.textContent = Math.max(...values);
} else {
avgReadingEl.textContent = '--';
lowReadingEl.textContent = '--';
highReadingEl.textContent = '--';
}
// Chart.js rendering
const ctx = document.getElementById('glucoseChart').getContext('2d');
if (glucoseChart) {
glucoseChart.destroy();
}
glucoseChart = new Chart(ctx, {
type: 'line',
data: {
labels: recentEntries.map(e => formatDateTime(e.datetime)),
datasets: [{
label: 'Glucose (mg/dL)',
data: recentEntries.map(e => e.value),
borderColor: '#4f46e5',
backgroundColor: 'rgba(79, 70, 229, 0.1)',
fill: true,
tension: 0.1
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
footer: (tooltipItems) => {
const entry = recentEntries[tooltipItems[0].dataIndex];
return `Context: ${entry.context}`;
}
}
}
},
scales: {
x: { display: false },
y: { beginAtZero: false }
}
}
});
};
// --- EVENT LISTENERS ---
const attachEventListeners = () => {
tabs.forEach(tab => tab.addEventListener('click', () => switchTab(tab.dataset.tab)));
prevBtn.addEventListener('click', navigatePrev);
nextBtn.addEventListener('click', navigateNext);
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 value = parseInt(glucoseValueInput.value);
if (!readingDateInput.value || !readingTimeInput.value || !value) {
alert("Please fill in the date, time, and glucose level.");
return;
}
const entry = {
datetime: `${readingDateInput.value}T${readingTimeInput.value}`,
value: value,
context: readingContextSelect.value,
notes: readingNotesInput.value,
};
logEntries.push(entry);
saveToLocalStorage();
renderAll();
alert("Reading saved successfully!");
switchTab('dashboard');
};
const saveToLocalStorage = () => {
localStorage.setItem('glucoseTrackerEntries', JSON.stringify(logEntries));
};
const loadFromLocalStorage = () => {
const saved = localStorage.getItem('glucoseTrackerEntries');
if (saved) {
logEntries = JSON.parse(saved);
}
};
// --- API & INSIGHTS LOGIC ---
const handleGetInsights = async () => {
if (logEntries.length < 5) {
alert("Please add at least 5 readings 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 = logEntries
.sort((a, b) => new Date(b.datetime) - new Date(a.datetime))
.slice(0, 20); // Analyze last 20 entries
const formattedEntries = recentEntries.map(entry => {
return `Date: ${formatDateTime(entry.datetime)}, Level: ${entry.value} mg/dL, Context: ${entry.context}, Notes: ${entry.notes || 'None'}`;
}).join('\n');
return `
As a health data analysis AI (not a medical professional), analyze the following blood glucose log entries.
**Log Data:**
${formattedEntries}
**Task:**
1. **Identify Key Observations:** Look for patterns. Are fasting numbers consistently high? Are there significant spikes after certain meals (check notes)? Are there periods of stability? Mention 2-3 key observations.
2. **Highlight Potential Correlations:** Point out possible links between notes (like specific foods or activities) and glucose levels. For example, "It looks like your reading was higher after noting 'pizza dinner'."
3. **Suggest Questions for a Doctor:** Based on the data, formulate 2-3 thoughtful questions the user could ask their healthcare provider. For example, "Given my fasting glucose levels, what should be my target range?" or "I noticed a spike after eating X, is this a typical response?"
**Formatting:**
- Use '##' for the main title: "Summary & AI-Powered Analysis".
- Use '###' for section headings: "Key Observations", "Potential Correlations", "Questions for Your Doctor".
- 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 = `
Full Log History
| Date & Time |
Level (mg/dL) |
Context |
Notes |
${logEntries.sort((a,b) => new Date(b.datetime) - new Date(a.datetime)).map(e => `
| ${formatDateTime(e.datetime)} |
${e.value} |
${e.context} |
${e.notes || ''} |
`).join('')}
`;
const insightsHtml = insightsText
.replace(/## (.*?)\n/g, '
$1
')
.replace(/### (.*?)\n/g, '
$1
')
.replace(/- (.*?)\n/g, '
')
.replace(/<\/ul>\s*
/g, '');
summaryContentContainer.innerHTML = `${insightsHtml}
${historyHtml}`;
};
// --- 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-Blood-Glucose-Report.pdf');
})
.catch(err => console.error("PDF Generation Error:", err));
};
// --- UTILITIES ---
const formatDateTime = (datetimeString) => {
const date = new Date(datetimeString);
return date.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', hour12: true });
};
// --- START THE APP ---
initialize();
});