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'); const photoForm = document.getElementById('photoForm'); // AI Elements const btnAnalyzeAI = document.getElementById('btnAnalyzeAI'); const aiInput = document.getElementById('aiInput'); const aiBtnText = document.getElementById('aiBtnText'); const aiBtnSpinner = document.getElementById('aiBtnSpinner'); // Tab Elements const periodWeekly = document.getElementById('periodWeekly'); const periodMonthly = document.getElementById('periodMonthly'); let weightChart = null; let caloriesChart = null; // --- PWA SERVICE WORKER --- if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js').then(() => { console.log('Service Worker Registered'); }); } // --- BOTTOM NAV & TAB HANDLING --- const bottomNavItems = document.querySelectorAll('.bottom-nav .nav-item'); bottomNavItems.forEach(item => { item.addEventListener('click', function(e) { e.preventDefault(); const tabId = this.dataset.tab; // Update UI bottomNavItems.forEach(i => i.classList.remove('active')); this.classList.add('active'); // Show Tab const tabTrigger = new bootstrap.Tab(document.querySelector(`#${tabId}-tab`)); tabTrigger.show(); // Special handling for specific tabs if (tabId === 'gallery') fetchPhotos(); if (tabId === 'analysis') renderAnalysis(); // Scroll to top window.scrollTo({ top: 0, behavior: 'smooth' }); }); }); // Initial fetch refreshAll(); function refreshAll() { fetchStats(); fetchRecentLogs(); fetchWater(); fetchWeight(); fetchSupplements(); } // --- 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); // Update Consumed (UI) setText('cal-consumed', consumed.calories); setText('pro-consumed', consumed.protein); // Update Left (UI) setText('cal-left', Math.max(0, remaining.calories)); setText('pro-left', Math.max(0, remaining.protein)); // Update Progress (UI) updateProgress('cal-progress', consumed.calories, goals.calories); updateProgress('pro-progress', consumed.protein, goals.protein); // Creatine status in Health tab 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 (creBadge) { 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 + '%'; } 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; fetch('api/logs.php?action=get_stats') .then(res => res.json()) .then(stats => { const goal = stats.goals.water || 2.5; // Health Tab setText('water-consumed', amount.toFixed(2)); setText('water-goal', goal); updateProgress('water-progress', amount, goal); // Today Tab document.querySelectorAll('.today-water-consumed').forEach(el => el.innerText = amount.toFixed(2)); document.querySelectorAll('.today-water-goal').forEach(el => el.innerText = goal); document.querySelectorAll('.today-water-progress-bar').forEach(el => { const pct = goal > 0 ? Math.min(100, (amount / goal) * 100) : 0; el.style.width = pct + '%'; }); }); }); } document.querySelectorAll('.water-btn').forEach(btn => { btn.addEventListener('click', () => { const amount = parseFloat(btn.dataset.amount); logWater(amount); }); }); const btnAddWater = document.getElementById('btnAddWater'); if (btnAddWater) { btnAddWater.addEventListener('click', () => { logWater(0.25); }); } function logWater(amount) { fetch('api/water.php', { method: 'POST', body: JSON.stringify({ amount: amount }) }).then(() => fetchWater()); } // --- SUPPLEMENTS --- function fetchSupplements() { fetch('api/supplements.php?action=get_status') .then(res => res.json()) .then(data => { const container = document.getElementById('today-supplements-list'); if (!container) return; container.innerHTML = data.map(sup => ` `).join(''); document.querySelectorAll('.supplement-toggle-btn').forEach(btn => { btn.addEventListener('click', () => { logSupplement(btn.dataset.id, btn.dataset.name); }); }); }); } function logSupplement(id, name) { fetch('api/supplements.php?action=log', { method: 'POST', body: JSON.stringify({ id: id, name: name }) }).then(() => { fetchSupplements(); fetchStats(); // Update reminders too }); } // --- 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); const weightInput = document.getElementById('weightInput'); if (weightInput) weightInput.value = last.weight; const photoWeight = document.getElementById('photoWeight'); if (photoWeight) photoWeight.value = last.weight; } }); } if (weightForm) { 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(); } }); }); } // --- PHOTO GALLERY --- function fetchPhotos() { fetch('api/photos.php') .then(res => res.json()) .then(data => { const container = document.getElementById('gallery-container'); if (!container) return; if (data.length === 0) { container.innerHTML = '

No photos uploaded yet.

'; return; } container.innerHTML = data.map(photo => `
Progress
${photo.logged_at} ${photo.weight ? `${photo.weight} kg` : ''}
`).join(''); }); } if (photoForm) { photoForm.addEventListener('submit', function(e) { e.preventDefault(); const formData = new FormData(); formData.append('photo', document.getElementById('photoInput').files[0]); formData.append('weight', document.getElementById('photoWeight').value); fetch('api/photos.php', { method: 'POST', body: formData }).then(res => res.json()) .then(res => { if (res.success) { bootstrap.Modal.getInstance(document.getElementById('photoModal')).hide(); photoForm.reset(); fetchPhotos(); } }); }); } // --- REMINDERS --- function updateReminders(consumed, goals) { const row = document.getElementById('reminders-row'); if (!row) return; row.innerHTML = ''; const items = [ { label: 'Water', current: 0, goal: goals.water || 2.5, unit: 'L', color: 'info' }, { label: 'Protein', current: consumed.protein, goal: goals.protein, unit: 'g', color: 'primary' } ]; 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 --- 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(); 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 } }, 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(); 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 }; 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(); } }); }); });