From bb9eef0fb864e62d108c124b1035b359ccc654b9 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 21 Feb 2026 16:43:49 +0000 Subject: [PATCH] v1 ft tracker --- api/photos.php | 30 ++++++ api/supplements.php | 40 ++++++++ assets/css/custom.css | 22 +++++ assets/js/main.js | 206 +++++++++++++++++++++++++++++------------- index.php | 60 +++++++++++- 5 files changed, 292 insertions(+), 66 deletions(-) create mode 100644 api/photos.php create mode 100644 api/supplements.php 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.

'; + 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: 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' } + { 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 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; @@ -213,10 +306,9 @@ document.addEventListener('DOMContentLoaded', function() { }); } - // --- ANALYSIS & CHARTS --- - analysisTab.addEventListener('shown.bs.tab', () => { - renderAnalysis(); - }); + // --- TABS SWITCHING --- + analysisTab.addEventListener('shown.bs.tab', renderAnalysis); + galleryTab.addEventListener('shown.bs.tab', fetchPhotos); periodWeekly.addEventListener('change', renderAnalysis); periodMonthly.addEventListener('change', renderAnalysis); @@ -264,7 +356,6 @@ document.addEventListener('DOMContentLoaded', function() { 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, { @@ -281,21 +372,7 @@ document.addEventListener('DOMContentLoaded', function() { 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] - } - } - } - }, + plugins: { legend: { display: false } }, scales: { y: { grid: { color: '#334155' }, ticks: { color: '#94a3b8' } }, x: { grid: { display: false }, ticks: { color: '#94a3b8' } } @@ -323,7 +400,6 @@ document.addEventListener('DOMContentLoaded', function() { if (res.success) { bootstrap.Modal.getInstance(document.getElementById('addLogModal')).hide(); addLogForm.reset(); - aiInput.value = ''; refreshAll(); } }); @@ -366,7 +442,7 @@ document.addEventListener('DOMContentLoaded', function() { protein: document.getElementById('goalProtein').value, creatine: document.getElementById('goalCreatine').value, water: document.getElementById('goalWater').value, - weight: 75 // placeholder or add input if needed + weight: 75 }; fetch('api/logs.php?action=update_goals', { @@ -380,4 +456,4 @@ document.addEventListener('DOMContentLoaded', function() { } }); }); -}); +}); \ No newline at end of file diff --git a/index.php b/index.php index 0f7cfb5..f5bc4a1 100644 --- a/index.php +++ b/index.php @@ -44,6 +44,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; @@ -57,6 +58,9 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; + @@ -93,6 +97,27 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; + +
+
+ Water Intake + 0 / 0 L +
+
+
+ + +
+
+ + +
+ Supplements +
+ +
+
+ @@ -141,7 +166,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
- Supplements + Creatine Status
Creatine @@ -152,6 +177,20 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
+ + +
@@ -252,6 +291,25 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
+ +