Files
foodplanner/templates/tracker.html

547 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-md-8">
<!-- Date Navigation -->
<div class="d-flex justify-content-between align-items-center mb-4">
<button class="btn btn-outline-secondary" onclick="navigateDate('{{ prev_date }}')">
<i class="bi bi-chevron-left"></i> Yesterday
</button>
<div class="text-center">
<h3>{{ current_date.strftime('%A, %B %d, %Y') }}</h3>
{% if is_modified %}
<span class="badge bg-warning text-dark">Custom</span>
{% else %}
<span class="badge bg-success">As Planned</span>
{% endif %}
</div>
<button class="btn btn-outline-secondary" onclick="navigateDate('{{ next_date }}')">
Tomorrow <i class="bi bi-chevron-right"></i>
</button>
</div>
<!-- Reset to Plan Button (only show if modified) -->
{% if is_modified %}
<div class="d-flex justify-content-center mb-4">
<button class="btn btn-outline-primary" onclick="resetToPlan()">
<i class="bi bi-arrow-counterclockwise"></i> Reset to Plan
</button>
</div>
{% endif %}
<!-- Template Actions -->
<div class="d-flex gap-2 mb-4">
<button class="btn btn-success" onclick="saveAsTemplate()">
<i class="bi bi-save"></i> Save as Template
</button>
<button class="btn btn-primary" onclick="applyTemplate()">
<i class="bi bi-upload"></i> Apply Template
</button>
</div>
<!-- Meal Times -->
{% set meal_times = ["Breakfast", "Lunch", "Dinner", "Snack 1", "Snack 2", "Beverage 1", "Beverage 2"] %}
{% for meal_time in meal_times %}
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">{{ meal_time }}</h5>
</div>
<div class="card-body">
<div id="meals-{{ meal_time|lower|replace(' ', '-') }}">
{% set meals_for_time = [] %}
{% for tracked_meal in tracked_meals %}
{% if tracked_meal.meal_time == meal_time %}
{% set _ = meals_for_time.append(tracked_meal) %}
{% endif %}
{% endfor %}
{% if meals_for_time %}
{% for tracked_meal in meals_for_time %}
<div class="mb-3 p-3 bg-light rounded">
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<strong>{{ tracked_meal.meal.name }}</strong>
{% if tracked_meal.quantity != 1.0 %}
<span class="text-muted">({{ "%.1f"|format(tracked_meal.quantity) }}x)</span>
{% endif %}
</div>
<div>
<button class="btn btn-sm btn-outline-secondary me-1" onclick="editTrackedMeal('{{ tracked_meal.id }}')" title="Edit Meal">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="removeMeal('{{ tracked_meal.id }}')">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
<!-- Food Breakdown -->
<div class="ms-3 row row-cols-2 g-1">
{% for meal_food in tracked_meal.meal.meal_foods %}
{% set effective_quantity = meal_food.quantity * tracked_meal.quantity %}
<div class="col">
<div class="small text-muted">
• {{ meal_food.food.name }}
</div>
</div>
<div class="col text-end">
<div class="small text-muted">
{{ "%.1f"|format(effective_quantity) }} {{ meal_food.food.serving_unit }}
{% if meal_food.food.serving_size %}
({{ meal_food.food.serving_size }})
{% endif %}
</div>
</div>
{% endfor %}
{% if not tracked_meal.meal.meal_foods %}
<div class="col-12">
<div class="small text-muted">No foods in this meal</div>
</div>
{% endif %}
</div>
</div>
{% endfor %}
{% else %}
<p class="text-muted mb-0">No meals tracked</p>
{% endif %}
</div>
<!-- Add Meal Button -->
<div class="d-flex gap-2 mt-2">
<button class="btn btn-sm btn-outline-success" onclick="addMealToTime('{{ meal_time }}')">
<i class="bi bi-plus"></i> Add Meal
</button>
<button class="btn btn-sm btn-info text-white" onclick="addSingleFoodToTime('{{ meal_time }}')">
<i class="bi bi-plus-circle"></i> Add Food
</button>
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Right Column - Nutrition Totals -->
<div class="col-md-4">
<div class="card sticky-top" style="top: 20px;">
<div class="card-header">
<h5 class="mb-0">Daily Totals</h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong class="h4 text-primary">{{ "%.0f"|format(day_totals.calories) }}</strong>
<div class="small text-muted">Calories</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong class="h4 text-success">{{ "%.1f"|format(day_totals.protein) }}g</strong>
<div class="small text-muted">Protein ({{ day_totals.protein_pct }}%)</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong>{{ "%.1f"|format(day_totals.carbs) }}g</strong>
<div class="small text-muted">Carbs ({{ day_totals.carbs_pct }}%)</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong class="h4 text-danger">{{ "%.1f"|format(day_totals.fat) }}g</strong>
<div class="small text-muted">Fat ({{ day_totals.fat_pct }}%)</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong>{{ "%.1f"|format(day_totals.fiber) }}g</strong>
<div class="small text-muted">Fiber</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong class="h4 text-warning">{{ "%.1f"|format(day_totals.net_carbs) }}g</strong>
<div class="small text-muted">Net Carbs</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong>{{ "%.0f"|format(day_totals.sugar) }}g</strong>
<div class="small text-muted">Sugar</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong>{{ "%.0f"|format(day_totals.sodium) }}mg</strong>
<div class="small text-muted">Sodium</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% include 'modals/add_meal.html' %}
{% include 'modals/save_template.html' %}
{% include 'modals/apply_template.html' %}
{% include 'modals/edit_tracked_meal.html' %}
{% include 'modals/add_single_food.html' %}
<script>
// Date navigation
function navigateDate(date) {
const url = new URL(window.location);
url.searchParams.set('date', date);
window.location.href = url.toString();
}
// Add meal to specific time
function addMealToTime(mealTime) {
document.getElementById('mealTimeDisplay').textContent = mealTime;
document.getElementById('addMealTime').value = mealTime;
new bootstrap.Modal(document.getElementById('addMealModal')).show();
}
// Submit add meal form
async function submitAddMeal() {
const form = document.getElementById('addMealForm');
const formData = new FormData(form);
try {
const response = await fetch('/tracker/add_meal', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('addMealModal')).hide();
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
// Remove meal
async function removeMeal(trackedMealId) {
if (confirm('Remove this meal from the tracker?')) {
try {
const response = await fetch(`/tracker/remove_meal/${trackedMealId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.status === 'success') {
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
}
// Save as template
function saveAsTemplate() {
new bootstrap.Modal(document.getElementById('saveTemplateModal')).show();
}
// Submit save template
async function submitSaveTemplate() {
const form = document.getElementById('saveTemplateForm');
const formData = new FormData(form);
try {
const response = await fetch('/tracker/save_template', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('saveTemplateModal')).hide();
alert('Template saved successfully!');
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
// Apply template
function applyTemplate() {
new bootstrap.Modal(document.getElementById('applyTemplateModal')).show();
}
// Submit apply template
async function submitApplyTemplate() {
const form = document.getElementById('applyTemplateForm');
const formData = new FormData(form);
try {
const response = await fetch('/tracker/apply_template', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('applyTemplateModal')).hide();
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
// Reset to plan
async function resetToPlan() {
if (confirm('Reset this day back to the original plan? All custom changes will be lost.')) {
const formData = new FormData();
formData.append('person', '{{ person }}');
formData.append('date', '{{ current_date.isoformat() }}');
try {
const response = await fetch('/tracker/reset_to_plan', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
}
// Edit tracked meal
function editTrackedMeal(trackedMealId) {
document.getElementById('editTrackedMealId').value = trackedMealId;
loadTrackedMealFoods(trackedMealId);
new bootstrap.Modal(document.getElementById('editTrackedMealModal')).show();
}
// Load foods for editing
async function loadTrackedMealFoods(trackedMealId) {
document.getElementById('tracked_meal_id_for_food').value = trackedMealId;
try {
const response = await fetch(`/tracker/get_tracked_meal_foods/${trackedMealId}`);
const data = await response.json();
const container = document.getElementById('editMealFoodsList');
container.innerHTML = '';
if (data.status === 'success') {
if (data.meal_foods.length === 0) {
container.innerHTML = '<em>No foods added yet</em>';
} else {
data.meal_foods.forEach(food => {
const foodDiv = document.createElement('div');
foodDiv.className = 'd-flex justify-content-between align-items-center mb-2 p-2 bg-light rounded';
foodDiv.innerHTML = `
<span>${food.quantity} × ${food.food_name}</span>
<button class="btn btn-sm btn-outline-danger" onclick="removeFoodFromTrackedMeal(${food.id})">
<i class="bi bi-trash"></i>
</button>
`;
container.appendChild(foodDiv);
});
}
}
} catch (error) {
console.error('Error loading meal foods:', error);
}
}
// Add food to tracked meal
async function addFoodToTrackedMeal() {
const form = document.getElementById('addFoodToTrackedMealForm');
const formData = new FormData(form);
const trackedMealId = formData.get('tracked_meal_id');
try {
const response = await fetch('/tracker/add_food_to_tracked_meal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tracked_meal_id: parseInt(formData.get('tracked_meal_id')),
food_id: parseInt(formData.get('food_id')),
quantity: parseFloat(formData.get('quantity'))
})
});
const result = await response.json();
if (result.status === 'success') {
// Reset form and reload current foods
form.reset();
document.getElementById('tracked_meal_id_for_food').value = trackedMealId;
await loadTrackedMealFoods(trackedMealId);
} else {
alert('Error adding food: ' + result.message);
}
} catch (error) {
alert('Error adding food: ' + error.message);
}
}
// Remove food from tracked meal
async function removeFoodFromTrackedMeal(mealFoodId) {
if (confirm('Remove this food from the tracked meal?')) {
try {
const response = await fetch(`/tracker/remove_food_from_tracked_meal/${mealFoodId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.status === 'success') {
const trackedMealId = document.getElementById('editTrackedMealId').value;
await loadTrackedMealFoods(trackedMealId);
} else {
alert('Error removing food: ' + result.message);
}
} catch (error) {
alert('Error removing food: ' + error.message);
}
}
}
// Save tracked meal changes
async function saveTrackedMeal() {
const trackedMealId = document.getElementById('editTrackedMealId').value;
const inputs = document.querySelectorAll('#editMealFoodsList input[type="number"]');
const updates = [];
inputs.forEach(input => {
updates.push({
tracked_food_id: input.dataset.foodId,
quantity: parseFloat(input.value)
});
});
try {
const response = await fetch('/tracker/update_tracked_meal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tracked_meal_id: trackedMealId, updates: updates })
});
const result = await response.json();
if (result.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('editTrackedMealModal')).hide();
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
// Save as new meal
async function saveAsNewMeal() {
const mealName = prompt('Enter name for new meal:');
if (!mealName) return;
const trackedMealId = document.getElementById('editTrackedMealId').value;
const inputs = document.querySelectorAll('#editMealFoodsList input[type="number"]');
const foods = [];
inputs.forEach(input => {
foods.push({
food_id: input.dataset.foodId,
quantity: parseFloat(input.value)
});
});
try {
const response = await fetch('/tracker/save_as_new_meal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tracked_meal_id: trackedMealId,
new_meal_name: mealName,
foods: foods
})
});
const result = await response.json();
if (result.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('editTrackedMealModal')).hide();
alert('New meal saved successfully!');
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
// Update quantity on input change (real-time update)
document.addEventListener('input', function(e) {
if (e.target.type === 'number' && e.target.dataset.foodId) {
const trackedFoodId = e.target.dataset.foodId;
const quantity = parseFloat(e.target.value);
fetch('/tracker/update_tracked_food', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tracked_food_id: trackedFoodId, quantity: quantity })
}).catch(error => console.error('Error updating quantity:', error));
}
});
// Show add single food modal and pre-select meal time
function addSingleFoodToTime(mealTime) {
document.querySelector('#addSingleFoodModal select[name="meal_time"]').value = mealTime;
new bootstrap.Modal(document.getElementById('addSingleFoodModal')).show();
}
// Submit add single food form
async function submitAddSingleFood() {
const form = document.getElementById('addSingleFoodForm');
const formData = new FormData(form);
try {
const response = await fetch('/tracker/add_food', {
method: 'POST',
body: JSON.stringify(Object.fromEntries(formData)), // Convert FormData to JSON
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
if (result.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('addSingleFoodModal')).hide();
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
</script>
{% endblock %}