first commit
This commit is contained in:
221
calorie_tracker_app/templates/add_meal.html
Normal file
221
calorie_tracker_app/templates/add_meal.html
Normal file
@@ -0,0 +1,221 @@
|
||||
{% 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 %}
|
||||
92
calorie_tracker_app/templates/base.html
Normal file
92
calorie_tracker_app/templates/base.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Calorie Tracker{% endblock %}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#D62828',
|
||||
secondary: '#003F87',
|
||||
success: '#06D6A0',
|
||||
warning: '#FFB703',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50">
|
||||
{% if current_user.is_authenticated %}
|
||||
<!-- Navigation -->
|
||||
<nav class="bg-primary text-white shadow-lg">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between items-center py-4">
|
||||
<div class="flex items-center space-x-8">
|
||||
<a href="{{ url_for('index') }}" class="text-2xl font-bold">🍽️ Calorie Tracker</a>
|
||||
<div class="hidden md:flex space-x-6">
|
||||
<a href="{{ url_for('index') }}" class="hover:text-gray-200 transition {% if request.endpoint == 'index' %}font-bold{% endif %}">
|
||||
🏠 Dashboard
|
||||
</a>
|
||||
<a href="{{ url_for('meal_planner') }}" class="hover:text-gray-200 transition {% if request.endpoint == 'meal_planner' %}font-bold{% endif %}">
|
||||
📅 Meal Planner
|
||||
</a>
|
||||
<a href="{{ url_for('foods') }}" class="hover:text-gray-200 transition {% if request.endpoint == 'foods' %}font-bold{% endif %}">
|
||||
🍛 Foods
|
||||
</a>
|
||||
<a href="{{ url_for('progress') }}" class="hover:text-gray-200 transition {% if request.endpoint == 'progress' %}font-bold{% endif %}">
|
||||
📊 Progress
|
||||
</a>
|
||||
<a href="{{ url_for('goals') }}" class="hover:text-gray-200 transition {% if request.endpoint == 'goals' %}font-bold{% endif %}">
|
||||
🎯 Goals
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="hidden md:inline">👤 {{ current_user.name or current_user.username }}</span>
|
||||
<a href="{{ url_for('logout') }}" class="bg-white text-primary px-4 py-2 rounded hover:bg-gray-100 transition">
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
<!-- Flash Messages -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="container mx-auto px-4 mt-4">
|
||||
{% for category, message in messages %}
|
||||
<div class="{% if category == 'error' %}bg-red-100 border-red-400 text-red-700{% else %}bg-green-100 border-green-400 text-green-700{% endif %} border px-4 py-3 rounded relative mb-4" role="alert">
|
||||
<span class="block sm:inline">{{ message }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-gray-800 text-white text-center py-4 mt-12">
|
||||
<p>© 2026 Calorie Tracker - Filipino Food Edition</p>
|
||||
</footer>
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
300
calorie_tracker_app/templates/dashboard.html
Normal file
300
calorie_tracker_app/templates/dashboard.html
Normal file
@@ -0,0 +1,300 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Dashboard - Calorie Tracker{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-800">Today's Summary</h1>
|
||||
<p class="text-gray-600">{{ today.strftime('%A, %B %d, %Y') }}</p>
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<a href="{{ url_for('add_meal') }}" class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-red-700 transition inline-block">
|
||||
➕ Add Meal
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top Cards Row -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
|
||||
<!-- Calories Card -->
|
||||
<div class="glass rounded-xl p-6 shadow-lg">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<p class="text-gray-600 text-sm">Calories</p>
|
||||
<h2 class="text-3xl font-bold text-primary">{{ nutrition.calories|round|int }}</h2>
|
||||
<p class="text-sm text-gray-500">/ {{ current_user.target_daily_calories }}</p>
|
||||
</div>
|
||||
<span class="text-4xl">🔥</span>
|
||||
</div>
|
||||
<!-- Progress Bar -->
|
||||
<div class="w-full bg-gray-200 rounded-full h-3">
|
||||
{% set cal_percent = (nutrition.calories / current_user.target_daily_calories * 100)|int if current_user.target_daily_calories > 0 else 0 %}
|
||||
<div class="bg-primary h-3 rounded-full transition-all" style="width: {{ [cal_percent, 100]|min }}%"></div>
|
||||
</div>
|
||||
<p class="text-sm mt-2 {% if remaining.calories >= 0 %}text-success{% else %}text-red-600{% endif %}">
|
||||
{{ remaining.calories|round|int }} remaining
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Protein Card -->
|
||||
<div class="glass rounded-xl p-6 shadow-lg">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<p class="text-gray-600 text-sm">Protein</p>
|
||||
<h2 class="text-3xl font-bold text-secondary">{{ nutrition.protein|round|int }}g</h2>
|
||||
<p class="text-sm text-gray-500">/ {{ goals.target_protein_g }}g</p>
|
||||
</div>
|
||||
<span class="text-4xl">💪</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-3">
|
||||
{% set prot_percent = (nutrition.protein / goals.target_protein_g * 100)|int if goals.target_protein_g > 0 else 0 %}
|
||||
<div class="bg-secondary h-3 rounded-full" style="width: {{ [prot_percent, 100]|min }}%"></div>
|
||||
</div>
|
||||
<p class="text-sm mt-2 {% if remaining.protein >= 0 %}text-success{% else %}text-red-600{% endif %}">
|
||||
{{ remaining.protein|round|int }}g remaining
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Carbs & Fat Card -->
|
||||
<div class="glass rounded-xl p-6 shadow-lg">
|
||||
<div class="mb-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600 text-sm">Carbs</span>
|
||||
<span class="font-bold text-warning">{{ nutrition.carbs|round|int }}g / {{ goals.target_carbs_g }}g</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2 mt-1">
|
||||
{% set carb_percent = (nutrition.carbs / goals.target_carbs_g * 100)|int if goals.target_carbs_g > 0 else 0 %}
|
||||
<div class="bg-warning h-2 rounded-full" style="width: {{ [carb_percent, 100]|min }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600 text-sm">Fat</span>
|
||||
<span class="font-bold text-orange-600">{{ nutrition.fat|round|int }}g / {{ goals.target_fat_g }}g</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2 mt-1">
|
||||
{% set fat_percent = (nutrition.fat / goals.target_fat_g * 100)|int if goals.target_fat_g > 0 else 0 %}
|
||||
<div class="bg-orange-600 h-2 rounded-full" style="width: {{ [fat_percent, 100]|min }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Water & Weight Card -->
|
||||
<div class="glass rounded-xl p-6 shadow-lg">
|
||||
<div class="mb-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600 text-sm">Water</span>
|
||||
<span class="font-bold text-blue-600">{{ water.total_ml }}ml / {{ goals.target_water_ml }}ml</span>
|
||||
</div>
|
||||
<div class="flex space-x-1 mt-2">
|
||||
{% set glasses_filled = (water.total_ml / 250)|int %}
|
||||
{% for i in range(8) %}
|
||||
<span class="{% if i < glasses_filled %}text-blue-500{% else %}text-gray-300{% endif %}">💧</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<!-- Quick add water buttons -->
|
||||
<form method="POST" action="{{ url_for('add_water') }}" class="flex space-x-1 mt-2">
|
||||
<button type="submit" name="amount_ml" value="250" class="bg-blue-100 text-blue-700 px-2 py-1 rounded text-xs hover:bg-blue-200">+250ml</button>
|
||||
<button type="submit" name="amount_ml" value="500" class="bg-blue-100 text-blue-700 px-2 py-1 rounded text-xs hover:bg-blue-200">+500ml</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="pt-3 border-t">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600 text-sm">Weight</span>
|
||||
{% if weight_today %}
|
||||
<div class="text-right">
|
||||
<span class="font-bold">{{ weight_today.weight_kg }}kg</span>
|
||||
{% if weight_change %}
|
||||
<span class="text-xs {% if weight_change < 0 %}text-success{% else %}text-red-600{% endif %}">
|
||||
{{ weight_change|round(1) }}kg
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<form method="POST" action="{{ url_for('add_weight') }}" class="flex items-center space-x-2">
|
||||
<input type="number" step="0.1" name="weight_kg" placeholder="kg" class="w-20 px-2 py-1 border rounded text-sm" required>
|
||||
<button type="submit" class="bg-green-500 text-white px-2 py-1 rounded text-xs hover:bg-green-600">Log</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Macro Distribution Pie Chart -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||||
<div class="glass rounded-xl p-6 shadow-lg">
|
||||
<h3 class="text-lg font-bold mb-4">Macro Distribution</h3>
|
||||
<canvas id="macroChart" height="200"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Suggestions -->
|
||||
<div class="glass rounded-xl p-6 shadow-lg lg:col-span-2">
|
||||
<h3 class="text-lg font-bold mb-4">💡 Smart Suggestions</h3>
|
||||
{% if suggestions %}
|
||||
<div class="space-y-3">
|
||||
{% for suggestion in suggestions %}
|
||||
<div class="bg-blue-50 border-l-4 border-blue-500 p-3 rounded">
|
||||
<p class="font-semibold text-blue-900">{{ suggestion.category }}</p>
|
||||
<p class="text-sm text-blue-700">Try: {{ suggestion.examples|join(', ') }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-gray-600">You're on track! Keep up the good work! 🎉</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Today's Meals -->
|
||||
<div class="glass rounded-xl p-6 shadow-lg mb-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold">Today's Meals</h3>
|
||||
<a href="{{ url_for('add_meal') }}" class="text-primary hover:underline">+ Add Meal</a>
|
||||
</div>
|
||||
|
||||
{% if nutrition.meals %}
|
||||
<div class="space-y-4">
|
||||
{% for meal in nutrition.meals %}
|
||||
<div class="border-l-4 {% if meal.type == 'breakfast' %}border-yellow-500{% elif meal.type == 'lunch' %}border-green-500{% elif meal.type == 'dinner' %}border-blue-500{% else %}border-purple-500{% endif %} pl-4 py-2">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-2xl">
|
||||
{% if meal.type == 'breakfast' %}🌅{% elif meal.type == 'lunch' %}🌞{% elif meal.type == 'dinner' %}🌙{% else %}🍪{% endif %}
|
||||
</span>
|
||||
<h4 class="font-bold text-lg capitalize">{{ meal.type }}</h4>
|
||||
{% if meal.time %}
|
||||
<span class="text-sm text-gray-500">{{ meal.time }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mt-2 space-y-1">
|
||||
{% for food in meal.foods %}
|
||||
<p class="text-sm text-gray-700">• {{ food.name }} ({{ food.quantity }}x) - {{ food.calories|round|int }} cal</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-2xl font-bold text-primary">{{ meal.totals.calories|round|int }}</p>
|
||||
<p class="text-xs text-gray-500">calories</p>
|
||||
<p class="text-xs text-gray-600 mt-1">
|
||||
P: {{ meal.totals.protein|round|int }}g |
|
||||
C: {{ meal.totals.carbs|round|int }}g |
|
||||
F: {{ meal.totals.fat|round|int }}g
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-12 text-gray-500">
|
||||
<p class="text-4xl mb-4">🍽️</p>
|
||||
<p>No meals logged yet today.</p>
|
||||
<a href="{{ url_for('add_meal') }}" class="text-primary hover:underline mt-2 inline-block">Add your first meal</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Weekly Trends -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Calorie Trend -->
|
||||
<div class="glass rounded-xl p-6 shadow-lg">
|
||||
<h3 class="text-lg font-bold mb-4">📈 Calorie Trend (7 Days)</h3>
|
||||
<canvas id="calorieChart" height="200"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Weight Trend -->
|
||||
<div class="glass rounded-xl p-6 shadow-lg">
|
||||
<h3 class="text-lg font-bold mb-4">⚖️ Weight Trend (7 Days)</h3>
|
||||
<canvas id="weightChart" height="200"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Macro Distribution Chart
|
||||
const macroCtx = document.getElementById('macroChart').getContext('2d');
|
||||
new Chart(macroCtx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['Protein', 'Carbs', 'Fat'],
|
||||
datasets: [{
|
||||
data: [{{ macro_percentages.protein }}, {{ macro_percentages.carbs }}, {{ macro_percentages.fat }}],
|
||||
backgroundColor: ['#003F87', '#FFB703', '#FF6B35']
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Calorie Trend Chart
|
||||
const calorieCtx = document.getElementById('calorieChart').getContext('2d');
|
||||
new Chart(calorieCtx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [{% for day in calorie_trend %}'{{ day.date }}'{% if not loop.last %}, {% endif %}{% endfor %}],
|
||||
datasets: [{
|
||||
label: 'Calories',
|
||||
data: [{% for day in calorie_trend %}{{ day.calories }}{% if not loop.last %}, {% endif %}{% endfor %}],
|
||||
borderColor: '#D62828',
|
||||
backgroundColor: 'rgba(214, 40, 40, 0.1)',
|
||||
tension: 0.4
|
||||
}, {
|
||||
label: 'Target',
|
||||
data: Array(7).fill({{ current_user.target_daily_calories }}),
|
||||
borderColor: '#06D6A0',
|
||||
borderDash: [5, 5],
|
||||
pointRadius: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Weight Trend Chart
|
||||
{% if weight_trend %}
|
||||
const weightCtx = document.getElementById('weightChart').getContext('2d');
|
||||
new Chart(weightCtx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [{% for day in weight_trend %}'{{ day.date }}'{% if not loop.last %}, {% endif %}{% endfor %}],
|
||||
datasets: [{
|
||||
label: 'Weight (kg)',
|
||||
data: [{% for day in weight_trend %}{{ day.weight_kg }}{% if not loop.last %}, {% endif %}{% endfor %}],
|
||||
borderColor: '#003F87',
|
||||
backgroundColor: 'rgba(0, 63, 135, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
{% endif %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
14
calorie_tracker_app/templates/foods.html
Normal file
14
calorie_tracker_app/templates/foods.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Foods Database{% endblock %}
|
||||
{% block content %}
|
||||
<h1 class="text-3xl font-bold mb-6">Filipino Foods Database</h1>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{% for food in filipino_foods %}
|
||||
<div class="glass rounded-lg p-4">
|
||||
<h3 class="font-bold">{{ food.name }}</h3>
|
||||
<p class="text-sm text-gray-600">{{ food.name_tagalog }}</p>
|
||||
<p class="text-primary font-bold">{{ food.calories|round|int }} cal</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
21
calorie_tracker_app/templates/goals.html
Normal file
21
calorie_tracker_app/templates/goals.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Goals & Settings{% endblock %}
|
||||
{% block content %}
|
||||
<h1 class="text-3xl font-bold mb-6">Goals & Settings</h1>
|
||||
<form method="POST" class="glass rounded-xl p-6 max-w-2xl">
|
||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-2">Age</label>
|
||||
<input type="number" name="age" value="{{ user.age or 25 }}" class="w-full px-4 py-2 border rounded" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-2">Gender</label>
|
||||
<select name="gender" class="w-full px-4 py-2 border rounded" required>
|
||||
<option value="male" {% if user.gender == 'male' %}selected{% endif %}>Male</option>
|
||||
<option value="female" {% if user.gender == 'female' %}selected{% endif %}>Female</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="bg-primary text-white px-6 py-3 rounded hover:bg-red-700">Save</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
32
calorie_tracker_app/templates/login.html
Normal file
32
calorie_tracker_app/templates/login.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Login - Calorie Tracker{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-md mx-auto">
|
||||
<div class="glass rounded-xl p-8 shadow-lg">
|
||||
<h1 class="text-3xl font-bold text-center text-gray-800 mb-6">🍽️ Calorie Tracker</h1>
|
||||
<p class="text-center text-gray-600 mb-8">Filipino Food Edition</p>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Username</label>
|
||||
<input type="text" name="username" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Password</label>
|
||||
<input type="password" name="password" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full bg-primary text-white py-3 rounded-lg hover:bg-red-700 transition font-semibold">
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="text-center mt-6 text-gray-600">
|
||||
Don't have an account? <a href="{{ url_for('register') }}" class="text-primary hover:underline font-semibold">Register</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
6
calorie_tracker_app/templates/meal_planner.html
Normal file
6
calorie_tracker_app/templates/meal_planner.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Meal Planner{% endblock %}
|
||||
{% block content %}
|
||||
<h1 class="text-3xl font-bold mb-6">Meal Planner</h1>
|
||||
<p class="text-gray-600">Coming soon! Plan your meals for the week ahead.</p>
|
||||
{% endblock %}
|
||||
8
calorie_tracker_app/templates/progress.html
Normal file
8
calorie_tracker_app/templates/progress.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Progress{% endblock %}
|
||||
{% block content %}
|
||||
<h1 class="text-3xl font-bold mb-6">Your Progress</h1>
|
||||
<div class="glass rounded-xl p-6">
|
||||
<canvas id="progressChart"></canvas>
|
||||
</div>
|
||||
{% endblock %}
|
||||
36
calorie_tracker_app/templates/register.html
Normal file
36
calorie_tracker_app/templates/register.html
Normal file
@@ -0,0 +1,36 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Register - Calorie Tracker{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-md mx-auto">
|
||||
<div class="glass rounded-xl p-8 shadow-lg">
|
||||
<h1 class="text-3xl font-bold text-center text-gray-800 mb-6">Create Account</h1>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Name</label>
|
||||
<input type="text" name="name" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Username</label>
|
||||
<input type="text" name="username" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Password</label>
|
||||
<input type="password" name="password" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full bg-primary text-white py-3 rounded-lg hover:bg-red-700 transition font-semibold">
|
||||
Register
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="text-center mt-6 text-gray-600">
|
||||
Already have an account? <a href="{{ url_for('login') }}" class="text-primary hover:underline font-semibold">Login</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user