diff --git a/api/photos.php b/api/photos.php new file mode 100644 index 0000000..f709501 --- /dev/null +++ b/api/photos.php @@ -0,0 +1,30 @@ +prepare("INSERT INTO progress_photos (photo_path, weight, logged_at) VALUES (?, ?, ?)"); + $stmt->execute(['assets/images/progress/' . $filename, $weight, $date]); + echo json_encode(['success' => true]); + } else { + echo json_encode(['error' => 'Failed to save file']); + } + exit; + } +} + +// Default action: list photos +$stmt = $pdo->query("SELECT * FROM progress_photos ORDER BY logged_at DESC, created_at DESC"); +echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); diff --git a/api/supplements.php b/api/supplements.php new file mode 100644 index 0000000..4b6e0fd --- /dev/null +++ b/api/supplements.php @@ -0,0 +1,40 @@ +prepare("INSERT INTO supplement_logs (supplement_id, name, taken_at) VALUES (?, ?, ?)"); + $stmt->execute([$id, $name, $today]); + echo json_encode(['success' => true]); + exit; + } +} + +if ($action === 'get_status') { + // Get all supplements + $stmt = $pdo->query("SELECT * FROM supplement_list ORDER BY name ASC"); + $list = $stmt->fetchAll(PDO::FETCH_ASSOC); + + // Get today's logs + $stmt = $pdo->prepare("SELECT name FROM supplement_logs WHERE taken_at = ?"); + $stmt->execute([$today]); + $taken = $stmt->fetchAll(PDO::FETCH_COLUMN); + + $results = []; + foreach ($list as $sup) { + $sup['taken'] = in_array($sup['name'], $taken); + $results[] = $sup; + } + + echo json_encode($results); + exit; +} diff --git a/assets/css/custom.css b/assets/css/custom.css index 1dd9c4d..eb5aaf0 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -166,3 +166,25 @@ header { canvas { max-width: 100% !important; } + +.x-small { font-size: 0.65rem; } + +#gallery-container .card { + transition: transform 0.3s ease; +} + +#gallery-container .card:hover { + transform: scale(1.02); +} + +.supplement-toggle-btn { + transition: all 0.2s; + font-size: 0.8rem !important; + text-align: center; +} + +.supplement-toggle-btn.btn-success { + background-color: var(--success); + border-color: var(--success) !important; + color: white; +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index 0b027fc..62ae921 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -5,17 +5,17 @@ document.addEventListener('DOMContentLoaded', function() { 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 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 galleryTab = document.getElementById('gallery-tab'); const periodWeekly = document.getElementById('periodWeekly'); const periodMonthly = document.getElementById('periodMonthly'); @@ -30,6 +30,7 @@ document.addEventListener('DOMContentLoaded', function() { fetchRecentLogs(); fetchWater(); fetchWeight(); + fetchSupplements(); } // --- NUTRITION & STATS --- @@ -42,33 +43,32 @@ document.addEventListener('DOMContentLoaded', function() { // 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 + // 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 (creCons >= creGoal && creGoal > 0) { - creBadge.innerText = 'Taken'; - creBadge.className = 'badge bg-success'; - } else { - creBadge.innerText = 'Not taken'; - creBadge.className = 'badge bg-danger'; + 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 @@ -86,14 +86,6 @@ document.addEventListener('DOMContentLoaded', function() { 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) { @@ -127,13 +119,23 @@ document.addEventListener('DOMContentLoaded', function() { .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; + + // 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 + '%'; + }); }); }); } @@ -145,9 +147,12 @@ document.addEventListener('DOMContentLoaded', function() { }); }); - document.getElementById('btnAddWater').addEventListener('click', () => { - logWater(0.25); // Default add 250ml - }); + const btnAddWater = document.getElementById('btnAddWater'); + if (btnAddWater) { + btnAddWater.addEventListener('click', () => { + logWater(0.25); + }); + } function logWater(amount) { fetch('api/water.php', { @@ -156,6 +161,40 @@ document.addEventListener('DOMContentLoaded', function() { }).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') @@ -165,38 +204,92 @@ document.addEventListener('DOMContentLoaded', function() { const last = data[0]; setText('weight-current', last.weight); setText('weight-last-date', 'Last logged: ' + last.logged_at); - document.getElementById('weightInput').value = last.weight; + const weightInput = document.getElementById('weightInput'); + if (weightInput) weightInput.value = last.weight; + const photoWeight = document.getElementById('photoWeight'); + if (photoWeight) photoWeight.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(); - } + 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.
No photos uploaded yet.
+