v1 ft tracker
This commit is contained in:
parent
7f7c6e873d
commit
bb9eef0fb8
30
api/photos.php
Normal file
30
api/photos.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// Handle photo upload
|
||||
if (isset($_FILES['photo'])) {
|
||||
$file = $_FILES['photo'];
|
||||
$weight = $_POST['weight'] ?? null;
|
||||
$date = $_POST['date'] ?? date('Y-m-d');
|
||||
|
||||
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
|
||||
$filename = uniqid('photo_') . '.' . $ext;
|
||||
$target = '../assets/images/progress/' . $filename;
|
||||
|
||||
if (move_uploaded_file($file['tmp_name'], $target)) {
|
||||
$stmt = $pdo->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));
|
||||
40
api/supplements.php
Normal file
40
api/supplements.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
$pdo = db();
|
||||
|
||||
$action = $_GET['action'] ?? 'get_status';
|
||||
$today = date('Y-m-d');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if ($action === 'log') {
|
||||
$id = $input['id'] ?? null;
|
||||
$name = $input['name'] ?? '';
|
||||
|
||||
$stmt = $pdo->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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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 => `
|
||||
<button class="btn btn-sm ${sup.taken ? 'btn-success' : 'btn-outline-secondary'} py-2 px-3 rounded-pill supplement-toggle-btn"
|
||||
data-id="${sup.id}" data-name="${sup.name}" ${sup.taken ? 'disabled' : ''}>
|
||||
${sup.name} ${sup.taken ? '✓' : ''}
|
||||
<div class="x-small text-opacity-50">${sup.default_amount || ''}</div>
|
||||
</button>
|
||||
`).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 = '<div class="col-12 text-center py-5"><p class="text-muted small">No photos uploaded yet.</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = data.map(photo => `
|
||||
<div class="col-6 col-md-4">
|
||||
<div class="card bg-dark border-secondary overflow-hidden h-100">
|
||||
<img src="${photo.photo_path}" class="card-img-top" style="height: 180px; object-fit: cover;" alt="Progress">
|
||||
<div class="card-body p-2">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="x-small fw-bold">${photo.logged_at}</span>
|
||||
${photo.weight ? `<span class="badge bg-secondary x-small">${photo.weight} kg</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).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() {
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
60
index.php
60
index.php
@ -44,6 +44,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow-lg border-0">
|
||||
<li><a class="dropdown-item small py-2" href="#" data-bs-toggle="modal" data-bs-target="#goalsModal">Daily Goals</a></li>
|
||||
<li><a class="dropdown-item small py-2" href="#" data-bs-toggle="modal" data-bs-target="#weightModal">Log Weight</a></li>
|
||||
<li><a class="dropdown-item small py-2" href="#" data-bs-toggle="modal" data-bs-target="#photoModal">Upload Progress Photo</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><p class="dropdown-item-text text-muted mb-0" style="font-size: 0.7rem;">Bulgarian AI support enabled</p></li>
|
||||
</ul>
|
||||
@ -57,6 +58,9 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<li class="nav-item flex-fill" role="presentation">
|
||||
<button class="nav-link w-100" id="health-tab" data-bs-toggle="pill" data-bs-target="#health" type="button" role="tab">Health</button>
|
||||
</li>
|
||||
<li class="nav-item flex-fill" role="presentation">
|
||||
<button class="nav-link w-100" id="gallery-tab" data-bs-toggle="pill" data-bs-target="#gallery" type="button" role="tab">Gallery</button>
|
||||
</li>
|
||||
<li class="nav-item flex-fill" role="presentation">
|
||||
<button class="nav-link w-100" id="analysis-tab" data-bs-toggle="pill" data-bs-target="#analysis" type="button" role="tab">Trends</button>
|
||||
</li>
|
||||
@ -93,6 +97,27 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Water Intake (Quick Access) -->
|
||||
<div class="card-stat mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="stat-label text-info">Water Intake</span>
|
||||
<span class="text-muted small"><span class="today-water-consumed fw-bold text-white">0</span> / <span class="today-water-goal">0</span> L</span>
|
||||
</div>
|
||||
<div class="progress-thin mb-3"><div class="today-water-progress-bar progress-bar-inner bg-info" style="width: 0%"></div></div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-outline-info flex-fill py-2 small water-btn" data-amount="0.25">+250ml</button>
|
||||
<button class="btn btn-outline-info flex-fill py-2 small water-btn" data-amount="0.5">+500ml</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Supplements (Quick Check) -->
|
||||
<div class="card-stat mb-4">
|
||||
<span class="stat-label mb-3 d-block">Supplements</span>
|
||||
<div id="today-supplements-list" class="d-flex flex-wrap gap-2">
|
||||
<!-- Filled by JS -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary-custom mb-4" data-bs-toggle="modal" data-bs-target="#addLogModal">Log Food / Supplement</button>
|
||||
|
||||
<!-- Recent History -->
|
||||
@ -141,7 +166,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
|
||||
<!-- Supplement Status -->
|
||||
<div class="card-stat">
|
||||
<span class="stat-label">Supplements</span>
|
||||
<span class="stat-label">Creatine Status</span>
|
||||
<div class="mt-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<span class="small">Creatine</span>
|
||||
@ -152,6 +177,20 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GALLERY TAB -->
|
||||
<div class="tab-pane fade" id="gallery" role="tabpanel">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="h6 fw-bold mb-0">Progress Gallery</h2>
|
||||
<button class="btn btn-primary-custom btn-sm" data-bs-toggle="modal" data-bs-target="#photoModal">+ Add Photo</button>
|
||||
</div>
|
||||
<div id="gallery-container" class="row g-3">
|
||||
<!-- Photos filled by JS -->
|
||||
<div class="col-12 text-center py-5">
|
||||
<p class="text-muted small">No photos uploaded yet.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ANALYSIS TAB -->
|
||||
<div class="tab-pane fade" id="analysis" role="tabpanel">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
@ -252,6 +291,25 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="photoModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content p-4">
|
||||
<h2 class="h5 fw-bold mb-4">Progress Photo</h2>
|
||||
<form id="photoForm">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">Select Photo</label>
|
||||
<input type="file" id="photoInput" class="form-control" accept="image/*" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">Current Weight (kg - optional)</label>
|
||||
<input type="number" step="0.1" id="photoWeight" class="form-control">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary-custom mt-3">Upload Photo</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="goalsModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content p-4">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user