301 lines
14 KiB
HTML
301 lines
14 KiB
HTML
{% 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 %}
|