222 lines
8.8 KiB
HTML
222 lines
8.8 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Add Meal - Calorie Tracker{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="max-w-4xl mx-auto">
|
|
<h1 class="text-3xl font-bold text-gray-800 mb-6">Add Meal</h1>
|
|
|
|
<form method="POST" id="mealForm" class="glass rounded-xl p-8 shadow-lg">
|
|
<!-- Meal Details -->
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Date</label>
|
|
<input type="date" name="date" value="{{ today }}" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary" required>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Meal Type</label>
|
|
<select name="meal_type" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary" required>
|
|
<option value="breakfast">🌅 Breakfast</option>
|
|
<option value="lunch">🌞 Lunch</option>
|
|
<option value="dinner">🌙 Dinner</option>
|
|
<option value="snack">🍪 Snack</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Time (optional)</label>
|
|
<input type="time" name="time" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Food Search -->
|
|
<div class="mb-6">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Search Foods</label>
|
|
<input type="text" id="foodSearch" placeholder="Search Filipino or international foods..." class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary">
|
|
<div id="searchResults" class="mt-2 max-h-64 overflow-y-auto hidden"></div>
|
|
</div>
|
|
|
|
<!-- Selected Foods -->
|
|
<div class="mb-6">
|
|
<h3 class="text-lg font-bold mb-3">Selected Foods</h3>
|
|
<div id="selectedFoods" class="space-y-3">
|
|
<p class="text-gray-500 text-center py-8" id="emptyMessage">No foods added yet. Search and add foods above.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Summary -->
|
|
<div class="bg-blue-50 border-l-4 border-blue-500 p-4 rounded mb-6">
|
|
<h4 class="font-bold text-blue-900 mb-2">Meal Summary</h4>
|
|
<div class="grid grid-cols-4 gap-4 text-center">
|
|
<div>
|
|
<p class="text-sm text-blue-700">Calories</p>
|
|
<p class="text-xl font-bold text-blue-900" id="totalCalories">0</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-blue-700">Protein</p>
|
|
<p class="text-xl font-bold text-blue-900" id="totalProtein">0g</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-blue-700">Carbs</p>
|
|
<p class="text-xl font-bold text-blue-900" id="totalCarbs">0g</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-blue-700">Fat</p>
|
|
<p class="text-xl font-bold text-blue-900" id="totalFat">0g</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="flex justify-between">
|
|
<a href="{{ url_for('index') }}" class="px-6 py-3 border border-gray-300 rounded-lg hover:bg-gray-100 transition">
|
|
Cancel
|
|
</a>
|
|
<button type="submit" class="bg-primary text-white px-8 py-3 rounded-lg hover:bg-red-700 transition">
|
|
Save Meal
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
let selectedFoods = [];
|
|
let searchTimeout;
|
|
|
|
// Food Search
|
|
document.getElementById('foodSearch').addEventListener('input', function(e) {
|
|
clearTimeout(searchTimeout);
|
|
const query = e.target.value;
|
|
|
|
if (query.length < 2) {
|
|
document.getElementById('searchResults').classList.add('hidden');
|
|
return;
|
|
}
|
|
|
|
searchTimeout = setTimeout(() => {
|
|
fetch(`/api/search-food?q=${encodeURIComponent(query)}`)
|
|
.then(response => response.json())
|
|
.then(data => displaySearchResults(data));
|
|
}, 300);
|
|
});
|
|
|
|
function displaySearchResults(results) {
|
|
const container = document.getElementById('searchResults');
|
|
|
|
if (results.length === 0) {
|
|
container.innerHTML = '<p class="p-4 text-gray-500">No foods found</p>';
|
|
container.classList.remove('hidden');
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = results.map(food => `
|
|
<div class="p-3 border rounded hover:bg-gray-50 cursor-pointer" onclick='addFood(${JSON.stringify(food)})'>
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<p class="font-semibold">${food.name}</p>
|
|
${food.name_tagalog ? `<p class="text-sm text-gray-600">${food.name_tagalog}</p>` : ''}
|
|
<p class="text-xs text-gray-500">${food.serving_description || '1 serving'}</p>
|
|
</div>
|
|
<div class="text-right">
|
|
<p class="font-bold text-primary">${Math.round(food.calories)} cal</p>
|
|
<p class="text-xs text-gray-600">P:${Math.round(food.protein_g)}g C:${Math.round(food.carbs_g)}g F:${Math.round(food.fat_g)}g</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
container.classList.remove('hidden');
|
|
}
|
|
|
|
function addFood(food) {
|
|
// Save to database if from API
|
|
if (food.source === 'api' && !food.id) {
|
|
fetch('/api/add-food', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(food)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
food.id = data.food_id;
|
|
addFoodToList(food);
|
|
});
|
|
} else {
|
|
addFoodToList(food);
|
|
}
|
|
|
|
// Clear search
|
|
document.getElementById('foodSearch').value = '';
|
|
document.getElementById('searchResults').classList.add('hidden');
|
|
}
|
|
|
|
function addFoodToList(food) {
|
|
selectedFoods.push({...food, quantity: 1});
|
|
renderSelectedFoods();
|
|
}
|
|
|
|
function removeFood(index) {
|
|
selectedFoods.splice(index, 1);
|
|
renderSelectedFoods();
|
|
}
|
|
|
|
function updateQuantity(index, quantity) {
|
|
selectedFoods[index].quantity = parseFloat(quantity);
|
|
updateSummary();
|
|
}
|
|
|
|
function renderSelectedFoods() {
|
|
const container = document.getElementById('selectedFoods');
|
|
const emptyMessage = document.getElementById('emptyMessage');
|
|
|
|
if (selectedFoods.length === 0) {
|
|
emptyMessage.classList.remove('hidden');
|
|
return;
|
|
}
|
|
|
|
emptyMessage.classList.add('hidden');
|
|
|
|
container.innerHTML = selectedFoods.map((food, index) => `
|
|
<div class="flex items-center justify-between p-4 border rounded-lg bg-white">
|
|
<div class="flex-1">
|
|
<p class="font-semibold">${food.name}</p>
|
|
<p class="text-sm text-gray-600">${Math.round(food.calories * food.quantity)} cal | P:${Math.round(food.protein_g * food.quantity)}g C:${Math.round(food.carbs_g * food.quantity)}g F:${Math.round(food.fat_g * food.quantity)}g</p>
|
|
<input type="hidden" name="food_id[]" value="${food.id}">
|
|
</div>
|
|
<div class="flex items-center space-x-3">
|
|
<label class="text-sm text-gray-600">Servings:</label>
|
|
<input type="number" step="0.5" min="0.5" name="quantity[]" value="${food.quantity}"
|
|
class="w-20 px-2 py-1 border rounded" onchange="updateQuantity(${index}, this.value)">
|
|
<button type="button" onclick="removeFood(${index})" class="text-red-600 hover:text-red-800">
|
|
❌
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
updateSummary();
|
|
}
|
|
|
|
function updateSummary() {
|
|
const totals = selectedFoods.reduce((acc, food) => ({
|
|
calories: acc.calories + (food.calories * food.quantity),
|
|
protein: acc.protein + (food.protein_g * food.quantity),
|
|
carbs: acc.carbs + (food.carbs_g * food.quantity),
|
|
fat: acc.fat + (food.fat_g * food.quantity)
|
|
}), {calories: 0, protein: 0, carbs: 0, fat: 0});
|
|
|
|
document.getElementById('totalCalories').textContent = Math.round(totals.calories);
|
|
document.getElementById('totalProtein').textContent = Math.round(totals.protein) + 'g';
|
|
document.getElementById('totalCarbs').textContent = Math.round(totals.carbs) + 'g';
|
|
document.getElementById('totalFat').textContent = Math.round(totals.fat) + 'g';
|
|
}
|
|
|
|
// Prevent form submission if no foods
|
|
document.getElementById('mealForm').addEventListener('submit', function(e) {
|
|
if (selectedFoods.length === 0) {
|
|
e.preventDefault();
|
|
alert('Please add at least one food item');
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|