document.addEventListener('DOMContentLoaded', function() { // Elements const statsContainer = document.getElementById('stats-container'); const logsList = document.getElementById('recent-logs-list'); const addLogForm = document.getElementById('addLogForm'); const updateGoalsForm = document.getElementById('updateGoalsForm'); const weightForm = document.getElementById('weightForm'); // AI Elements const btnAnalyzeAI = document.getElementById('btnAnalyzeAI'); const aiInput = document.getElementById('aiInput'); const aiFeedback = document.getElementById('aiFeedback'); const aiBtnText = document.getElementById('aiBtnText'); const aiBtnSpinner = document.getElementById('aiBtnSpinner'); const manualTab = document.getElementById('manual-tab'); // Tab Elements const analysisTab = document.getElementById('analysis-tab'); const periodWeekly = document.getElementById('periodWeekly'); const periodMonthly = document.getElementById('periodMonthly'); let weightChart = null; let caloriesChart = null; // Initial fetch refreshAll(); function refreshAll() { fetchStats(); fetchRecentLogs(); fetchWater(); fetchWeight(); } // --- NUTRITION & STATS --- function fetchStats() { fetch('api/logs.php?action=get_stats') .then(res => res.json()) .then(data => { const { goals, consumed, remaining } = data; // Update Goals (UI) setText('cal-goal', goals.calories); setText('pro-goal', goals.protein); setText('cre-goal', goals.creatine || 0); // Update Consumed (UI) setText('cal-consumed', consumed.calories); setText('pro-consumed', consumed.protein); setText('cre-consumed', consumed.creatine || 0); // Update Left (UI) setText('cal-left', Math.max(0, remaining.calories)); setText('pro-left', Math.max(0, remaining.protein)); // setText('cre-left', Math.max(0, (goals.creatine || 0) - (consumed.creatine || 0))); // Update Progress (UI) updateProgress('cal-progress', consumed.calories, goals.calories); updateProgress('pro-progress', consumed.protein, goals.protein); // Supplements status const creGoal = parseFloat(goals.creatine || 0); const creCons = parseFloat(consumed.creatine || 0); updateProgress('cre-progress-health', creCons, creGoal); const creBadge = document.getElementById('cre-status'); if (creCons >= creGoal && creGoal > 0) { creBadge.innerText = 'Taken'; creBadge.className = 'badge bg-success'; } else { creBadge.innerText = 'Not taken'; creBadge.className = 'badge bg-danger'; } // Pre-fill goal form document.getElementById('goalCalories').value = goals.calories; document.getElementById('goalProtein').value = goals.protein; document.getElementById('goalCreatine').value = goals.creatine; document.getElementById('goalWater').value = goals.water || 2.5; updateReminders(consumed, goals); }); } function updateProgress(id, consumed, goal) { const el = document.getElementById(id); if (!el) return; const percent = goal > 0 ? Math.min(100, (consumed / goal) * 100) : 0; el.style.width = percent + '%'; if (percent >= 100) { el.style.backgroundColor = 'var(--success)'; } else { // Keep original if not success (some bars have specific colors) if (!el.classList.contains('bg-info')) { el.style.backgroundColor = 'var(--primary)'; } } } function setText(id, val) { const el = document.getElementById(id); if (el) el.innerText = val; } function fetchRecentLogs() { fetch('api/logs.php?action=get_recent') .then(res => res.json()) .then(data => { if (data.length === 0) { logsList.innerHTML = '

No entries today yet.

'; return; } logsList.innerHTML = data.map(log => `
${log.entry_name}
${log.calories} kcal · ${log.protein}g protein ${log.creatine > 0 ? '· ' + log.creatine + 'g creatine' : ''}
${new Date(log.created_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
`).join(''); }); } // --- WATER --- function fetchWater() { fetch('api/water.php') .then(res => res.json()) .then(data => { const amount = data.amount || 0; setText('water-consumed', amount.toFixed(2)); fetch('api/logs.php?action=get_stats') .then(res => res.json()) .then(stats => { const goal = stats.goals.water || 2.5; setText('water-goal', goal); updateProgress('water-progress', amount, goal); }); }); } document.querySelectorAll('.water-btn').forEach(btn => { btn.addEventListener('click', () => { const amount = parseFloat(btn.dataset.amount); logWater(amount); }); }); document.getElementById('btnAddWater').addEventListener('click', () => { logWater(0.25); // Default add 250ml }); function logWater(amount) { fetch('api/water.php', { method: 'POST', body: JSON.stringify({ amount: amount }) }).then(() => fetchWater()); } // --- WEIGHT --- function fetchWeight() { fetch('api/weight.php') .then(res => res.json()) .then(data => { if (data.length > 0) { const last = data[0]; setText('weight-current', last.weight); setText('weight-last-date', 'Last logged: ' + last.logged_at); document.getElementById('weightInput').value = last.weight; } }); } weightForm.addEventListener('submit', function(e) { e.preventDefault(); const weight = document.getElementById('weightInput').value; fetch('api/weight.php', { method: 'POST', body: JSON.stringify({ weight: weight }) }).then(res => res.json()) .then(res => { if (res.success) { bootstrap.Modal.getInstance(document.getElementById('weightModal')).hide(); fetchWeight(); } }); }); // --- REMINDERS --- function updateReminders(consumed, goals) { const row = document.getElementById('reminders-row'); row.innerHTML = ''; const items = [ { label: 'Water', current: consumed.water || 0, goal: goals.water || 2.5, unit: 'L', color: 'info' }, { label: 'Protein', current: consumed.protein, goal: goals.protein, unit: 'g', color: 'primary' }, { label: 'Creatine', current: consumed.creatine || 0, goal: goals.creatine || 5, unit: 'g', color: 'warning' } ]; // Fetch water separately for reminders since it's not in get_stats usually fetch('api/water.php').then(res => res.json()).then(waterData => { items[0].current = waterData.amount || 0; items.forEach(item => { const done = item.current >= item.goal && item.goal > 0; const div = document.createElement('div'); div.className = `badge ${done ? 'bg-success-subtle text-success' : 'bg-secondary-subtle text-muted'} border border-opacity-10 px-3 py-2 rounded-pill flex-shrink-0 d-flex align-items-center gap-2`; div.innerHTML = ` ${item.label}: ${done ? 'Done' : (item.goal - item.current).toFixed(1) + item.unit + ' left'} `; row.appendChild(div); }); }); } // --- ANALYSIS & CHARTS --- analysisTab.addEventListener('shown.bs.tab', () => { renderAnalysis(); }); periodWeekly.addEventListener('change', renderAnalysis); periodMonthly.addEventListener('change', renderAnalysis); function renderAnalysis() { const type = periodMonthly.checked ? 'monthly' : 'weekly'; fetch(`api/analysis.php?type=${type}`) .then(res => res.json()) .then(data => { initWeightChart(data.weight); initCaloriesChart(data.nutrition); }); } function initWeightChart(weightData) { const ctx = document.getElementById('weightChart').getContext('2d'); if (weightChart) weightChart.destroy(); weightChart = new Chart(ctx, { type: 'line', data: { labels: weightData.map(d => d.logged_at), datasets: [{ label: 'Weight (kg)', data: weightData.map(d => d.weight), borderColor: '#f59e0b', backgroundColor: 'rgba(245, 158, 11, 0.1)', fill: true, tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { grid: { color: '#334155' }, ticks: { color: '#94a3b8' } }, x: { grid: { display: false }, ticks: { color: '#94a3b8' } } } } }); } function initCaloriesChart(nutritionData) { const ctx = document.getElementById('caloriesChart').getContext('2d'); if (caloriesChart) caloriesChart.destroy(); // Get goal from stats fetch('api/logs.php?action=get_stats').then(res => res.json()).then(stats => { const goal = stats.goals.calories; caloriesChart = new Chart(ctx, { type: 'bar', data: { labels: nutritionData.map(d => d.log_date), datasets: [{ label: 'Consumed', data: nutritionData.map(d => d.total_calories), backgroundColor: nutritionData.map(d => d.total_calories >= goal ? '#10b981' : '#3b82f6'), borderRadius: 4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, annotation: { annotations: { line1: { type: 'line', yMin: goal, yMax: goal, borderColor: 'rgba(255, 255, 255, 0.5)', borderWidth: 2, borderDash: [6, 6] } } } }, scales: { y: { grid: { color: '#334155' }, ticks: { color: '#94a3b8' } }, x: { grid: { display: false }, ticks: { color: '#94a3b8' } } } } }); }); } // --- FORM HANDLERS --- addLogForm.addEventListener('submit', function(e) { e.preventDefault(); const data = { entry_name: document.getElementById('logName').value, calories: document.getElementById('logCalories').value || 0, protein: document.getElementById('logProtein').value || 0, creatine: document.getElementById('logCreatine').value || 0 }; fetch('api/logs.php?action=add_log', { method: 'POST', body: JSON.stringify(data) }).then(res => res.json()) .then(res => { if (res.success) { bootstrap.Modal.getInstance(document.getElementById('addLogModal')).hide(); addLogForm.reset(); aiInput.value = ''; refreshAll(); } }); }); btnAnalyzeAI.addEventListener('click', function() { const text = aiInput.value.trim(); if (!text) return; aiBtnText.textContent = 'Analyzing...'; aiBtnSpinner.classList.remove('d-none'); btnAnalyzeAI.disabled = true; fetch('api/ai_analyze.php', { method: 'POST', body: JSON.stringify({ text: text }) }) .then(res => res.json()) .then(data => { document.getElementById('logName').value = data.entry_name; document.getElementById('logCalories').value = data.calories; document.getElementById('logProtein').value = data.protein; document.getElementById('logCreatine').value = data.creatine; new bootstrap.Tab(document.getElementById('manual-tab')).show(); resetAIButton(); }) .catch(() => resetAIButton()); }); function resetAIButton() { aiBtnText.textContent = 'Analyze with AI'; aiBtnSpinner.classList.add('d-none'); btnAnalyzeAI.disabled = false; } updateGoalsForm.addEventListener('submit', function(e) { e.preventDefault(); const data = { calories: document.getElementById('goalCalories').value, protein: document.getElementById('goalProtein').value, creatine: document.getElementById('goalCreatine').value, water: document.getElementById('goalWater').value, weight: 75 // placeholder or add input if needed }; fetch('api/logs.php?action=update_goals', { method: 'POST', body: JSON.stringify(data) }).then(res => res.json()) .then(res => { if (res.success) { bootstrap.Modal.getInstance(document.getElementById('goalsModal')).hide(); refreshAll(); } }); }); });