fixed food details not loading on details tab

This commit is contained in:
2025-10-01 09:11:41 -07:00
parent 6d4b6561e0
commit 617d8f8ca1
8 changed files with 1546 additions and 56 deletions

View File

@@ -6,7 +6,8 @@ import logging
from typing import List, Optional from typing import List, Optional
# Import from the database module # Import from the database module
from app.database import get_db, Food, Meal, MealFood, Plan, Template, TemplateMeal, WeeklyMenu, WeeklyMenuDay, TrackedDay, TrackedMeal, calculate_meal_nutrition, calculate_day_nutrition from app.database import get_db, Food, Meal, MealFood, Plan, Template, TemplateMeal, WeeklyMenu, WeeklyMenuDay, TrackedDay, TrackedMeal, calculate_meal_nutrition, calculate_day_nutrition, calculate_tracked_meal_nutrition
from sqlalchemy.orm import joinedload
from main import templates from main import templates
router = APIRouter() router = APIRouter()
@@ -211,10 +212,27 @@ async def detailed(request: Request, person: str = "Sarah", plan_date: str = Non
meal_details = [] meal_details = []
for tm in template_meals: for tm in template_meals:
meal_nutrition = calculate_meal_nutrition(tm.meal, db) meal_nutrition = calculate_meal_nutrition(tm.meal, db)
foods = []
# Show individual foods in template meals
for mf in tm.meal.meal_foods:
foods.append({
'name': mf.food.name,
'quantity': mf.quantity,
'serving_size': mf.food.serving_size,
'serving_unit': mf.food.serving_unit,
'calories': mf.food.calories * mf.quantity,
'protein': mf.food.protein * mf.quantity,
'carbs': mf.food.carbs * mf.quantity,
'fat': mf.food.fat * mf.quantity,
'fiber': (mf.food.fiber or 0) * mf.quantity,
'sodium': (mf.food.sodium or 0) * mf.quantity,
})
meal_details.append({ meal_details.append({
'plan': {'meal': tm.meal, 'meal_time': tm.meal_time}, 'plan': {'meal': tm.meal, 'meal_time': tm.meal_time},
'nutrition': meal_nutrition, 'nutrition': meal_nutrition,
'foods': [] # Template view doesn't show individual foods 'foods': foods # Now includes food breakdown
}) })
for key in template_nutrition: for key in template_nutrition:
@@ -241,22 +259,105 @@ async def detailed(request: Request, person: str = "Sarah", plan_date: str = Non
logging.info(f"DEBUG: Rendering template details with context: {context}") logging.info(f"DEBUG: Rendering template details with context: {context}")
return templates.TemplateResponse(request, "detailed.html", context) return templates.TemplateResponse(request, "detailed.html", context)
# If no plan_date is provided, default to today's date # When viewing a specific date, show TRACKED meals, not planned meals
if not plan_date: if plan_date:
plan_date_obj = date.today()
else:
try: try:
plan_date_obj = datetime.fromisoformat(plan_date).date() plan_date_obj = datetime.fromisoformat(plan_date).date()
except ValueError: except ValueError:
logging.error(f"DEBUG: Invalid date format for plan_date: {plan_date}") logging.error(f"debug: invalid date format plan_date: {plan_date}")
return templates.TemplateResponse("detailed.html", { return templates.TemplateResponse("detailed.html", {
"request": request, "title": "Invalid Date", "request": request,
"title": "Invalid date",
"error": "Invalid date format. Please use YYYY-MM-DD.", "error": "Invalid date format. Please use YYYY-MM-DD.",
"day_totals": {}, "day_totals": {},
"templates": templates_list, "templates": templates_list,
"person": person "person": person
}) })
logging.info(f"debug: loading TRACKED meals for {person} on {plan_date_obj}")
# Get tracked day and meals instead of planned meals
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == plan_date_obj
).first()
meal_details = []
day_totals = {'calories': 0, 'protein': 0, 'carbs': 0, 'fat': 0, 'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0}
if tracked_day:
tracked_meals = db.query(TrackedMeal).filter(
TrackedMeal.tracked_day_id == tracked_day.id
).options(joinedload(TrackedMeal.meal).joinedload(Meal.meal_foods).joinedload(MealFood.food)).all()
logging.info(f"debug: found {len(tracked_meals)} tracked meals for {person} on {plan_date_obj}")
for tracked_meal in tracked_meals:
meal_nutrition = calculate_tracked_meal_nutrition(tracked_meal, db)
foods = []
# Show base meal foods
for mf in tracked_meal.meal.meal_foods:
foods.append({
'name': mf.food.name,
'quantity': mf.quantity,
'serving_size': mf.food.serving_size,
'serving_unit': mf.food.serving_unit,
'calories': mf.food.calories * mf.quantity,
'protein': mf.food.protein * mf.quantity,
'carbs': mf.food.carbs * mf.quantity,
'fat': mf.food.fat * mf.quantity,
'fiber': (mf.food.fiber or 0) * mf.quantity,
'sodium': (mf.food.sodium or 0) * mf.quantity,
})
# Show custom tracked foods (overrides/additions)
for tracked_food in tracked_meal.tracked_foods:
foods.append({
'name': f"{tracked_food.food.name} {'(override)' if tracked_food.is_override else '(addition)'}",
'quantity': tracked_food.quantity,
'serving_size': tracked_food.food.serving_size,
'serving_unit': tracked_food.food.serving_unit,
'calories': tracked_food.food.calories * tracked_food.quantity,
'protein': tracked_food.food.protein * tracked_food.quantity,
'carbs': tracked_food.food.carbs * tracked_food.quantity,
'fat': tracked_food.food.fat * tracked_food.quantity,
'fiber': (tracked_food.food.fiber or 0) * tracked_food.quantity,
'sodium': (tracked_food.food.sodium or 0) * tracked_food.quantity,
})
meal_details.append({
'plan': tracked_meal, # Use tracked_meal instead of plan
'nutrition': meal_nutrition,
'foods': foods
})
# Accumulate day totals
for key in day_totals:
if key in meal_nutrition:
day_totals[key] += meal_nutrition[key]
context = {
"request": request,
"title": f"Detailed Tracker - {person} - {plan_date_obj.strftime('%b %d, %Y')}",
"meal_details": meal_details,
"day_totals": day_totals,
"person": person,
"plan_date": plan_date_obj,
"templates": templates_list,
"is_tracked_view": True # Add flag to indicate this is tracked view
}
# Add message if no meals tracked
if not meal_details:
context["message"] = "No meals tracked for this day."
logging.info(f"debug: rendering tracked meal details context: {context}")
return templates.TemplateResponse("detailed.html", context)
else:
# If no plan_date is provided, default to today's date
plan_date_obj = date.today()
logging.info(f"DEBUG: Loading plan for {person} on {plan_date_obj}") logging.info(f"DEBUG: Loading plan for {person} on {plan_date_obj}")
plans = db.query(Plan).filter(Plan.person == person, Plan.date == plan_date_obj).all() plans = db.query(Plan).filter(Plan.person == person, Plan.date == plan_date_obj).all()
logging.info(f"DEBUG: Found {len(plans)} plans for {person} on {plan_date_obj}") logging.info(f"DEBUG: Found {len(plans)} plans for {person} on {plan_date_obj}")

View File

@@ -0,0 +1,860 @@
from fastapi import APIRouter, Depends, HTTPException, Request, Form, Body
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy.orm import Session, joinedload
from datetime import date, datetime, timedelta
import logging
from typing import List, Optional, Union
# Import from the database module
from app.database import get_db, Meal, Template, TemplateMeal, TrackedDay, TrackedMeal, calculate_meal_nutrition, MealFood, TrackedMealFood, Food, calculate_day_nutrition_tracked
from main import templates
router = APIRouter()
# Tracker tab - Main page
@router.get("/tracker", response_class=HTMLResponse)
async def tracker_page(request: Request, person: str = "Sarah", date: str = None, db: Session = Depends(get_db)):
logging.info(f"DEBUG: Tracker page requested with person={person}, date={date}")
from datetime import datetime, timedelta
# If no date provided, use today
if not date:
current_date = datetime.now().date()
else:
current_date = datetime.fromisoformat(date).date()
# Calculate previous and next dates
prev_date = (current_date - timedelta(days=1)).isoformat()
next_date = (current_date + timedelta(days=1)).isoformat()
# Get or create tracked day
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == current_date
).first()
if not tracked_day:
# Create new tracked day
tracked_day = TrackedDay(person=person, date=current_date, is_modified=False)
db.add(tracked_day)
db.commit()
db.refresh(tracked_day)
logging.info(f"DEBUG: Created new tracked day for {person} on {current_date}")
# Get tracked meals for this day with eager loading of meal foods
tracked_meals = db.query(TrackedMeal).options(
joinedload(TrackedMeal.meal).joinedload(Meal.meal_foods).joinedload(MealFood.food)
).filter(
TrackedMeal.tracked_day_id == tracked_day.id
).all()
# Get all meals for dropdown
meals = db.query(Meal).all()
# Get all templates for template dropdown
templates_list = db.query(Template).all()
# Get all foods for dropdown
foods = db.query(Food).all()
# Calculate day totals
day_totals = calculate_day_nutrition_tracked(tracked_meals, db)
logging.info(f"DEBUG: Rendering tracker page with {len(tracked_meals)} tracked meals")
return templates.TemplateResponse("tracker.html", {
"request": request,
"person": person,
"current_date": current_date,
"prev_date": prev_date,
"next_date": next_date,
"tracked_meals": tracked_meals,
"is_modified": tracked_day.is_modified,
"day_totals": day_totals,
"meals": meals,
"templates": templates_list,
"foods": foods
})
# Tracker API Routes
@router.post("/tracker/add_meal")
async def tracker_add_meal(request: Request, db: Session = Depends(get_db)):
"""Add a meal to the tracker"""
try:
form_data = await request.form()
person = form_data.get("person")
date_str = form_data.get("date")
meal_id = form_data.get("meal_id")
meal_time = form_data.get("meal_time")
logging.info(f"DEBUG: Adding meal to tracker - person={person}, date={date_str}, meal_id={meal_id}, meal_time={meal_time}")
# Parse date
from datetime import datetime
date = datetime.fromisoformat(date_str).date()
# Get or create tracked day
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == date
).first()
if not tracked_day:
tracked_day = TrackedDay(person=person, date=date, is_modified=True)
db.add(tracked_day)
db.commit()
db.refresh(tracked_day)
# Create tracked meal
tracked_meal = TrackedMeal(
tracked_day_id=tracked_day.id,
meal_id=int(meal_id),
meal_time=meal_time
)
db.add(tracked_meal)
# Mark day as modified
tracked_day.is_modified = True
db.commit()
logging.info(f"DEBUG: Successfully added meal to tracker")
return {"status": "success"}
except Exception as e:
db.rollback()
logging.error(f"DEBUG: Error adding meal to tracker: {e}")
return {"status": "error", "message": str(e)}
@router.delete("/tracker/remove_meal/{tracked_meal_id}")
async def tracker_remove_meal(tracked_meal_id: int, db: Session = Depends(get_db)):
"""Remove a meal from the tracker"""
try:
logging.info(f"DEBUG: Removing tracked meal with ID: {tracked_meal_id}")
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first()
if not tracked_meal:
return {"status": "error", "message": "Tracked meal not found"}
# Get the tracked day to mark as modified
tracked_day = tracked_meal.tracked_day
tracked_day.is_modified = True
db.delete(tracked_meal)
db.commit()
logging.info(f"DEBUG: Successfully removed tracked meal")
return {"status": "success"}
except Exception as e:
db.rollback()
logging.error(f"DEBUG: Error removing tracked meal: {e}")
return {"status": "error", "message": str(e)}
@router.post("/tracker/save_template")
async def tracker_save_template(request: Request, db: Session = Depends(get_db)):
"""save current day's meals as a new template"""
try:
form_data = await request.form()
person = form_data.get("person")
date_str = form_data.get("date")
template_name = form_data.get("template_name")
if not all([person, date_str, template_name]):
raise HTTPException(status_code=400, detail="Missing required form data.")
logging.info(f"debug: saving template - name={template_name}, person={person}, date={date_str}")
# 1. Check if template name already exists
existing_template = db.query(Template).filter(Template.name == template_name).first()
if existing_template:
return {"status": "error", "message": f"Template name '{template_name}' already exists."}
# 2. Find the tracked day and its meals
from datetime import datetime
target_date = datetime.fromisoformat(date_str).date()
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person, TrackedDay.date == target_date
).first()
if not tracked_day:
return {"status": "error", "message": "Tracked day not found for the given person and date."}
tracked_meals = db.query(TrackedMeal).filter(TrackedMeal.tracked_day_id == tracked_day.id).all()
if not tracked_meals:
return {"status": "error", "message": "No meals found on this day to save as a template."}
# 3. Create the new template
new_template = Template(name=template_name)
db.add(new_template)
db.flush() # Use flush to get the new_template.id before commit
# 4. Create template_meal entries for each tracked meal
for meal in tracked_meals:
template_meal_entry = TemplateMeal(
template_id=new_template.id,
meal_id=meal.meal_id,
meal_time=meal.meal_time
)
db.add(template_meal_entry)
db.commit()
logging.info(f"debug: successfully saved template '{template_name}' with {len(tracked_meals)} meals.")
return {"status": "success", "message": "Template saved successfully."}
except Exception as e:
db.rollback()
logging.error(f"debug: error saving template: {e}")
return {"status": "error", "message": str(e)}
@router.post("/tracker/apply_template")
async def tracker_apply_template(request: Request, db: Session = Depends(get_db)):
"""Apply a template to the current day"""
try:
form_data = await request.form()
person = form_data.get("person")
date_str = form_data.get("date")
template_id = form_data.get("template_id")
logging.info(f"DEBUG: Applying template - template_id={template_id}, person={person}, date={date_str}")
# Parse date
from datetime import datetime
date = datetime.fromisoformat(date_str).date()
# Get template
template = db.query(Template).filter(Template.id == int(template_id)).first()
if not template:
return {"status": "error", "message": "Template not found"}
# Get template meals
template_meals = db.query(TemplateMeal).filter(
TemplateMeal.template_id == template.id
).all()
if not template_meals:
return {"status": "error", "message": "Template has no meals"}
# Get or create tracked day
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == date
).first()
if not tracked_day:
tracked_day = TrackedDay(person=person, date=date, is_modified=True)
db.add(tracked_day)
db.commit()
db.refresh(tracked_day)
else:
# Clear existing tracked meals
db.query(TrackedMeal).filter(
TrackedMeal.tracked_day_id == tracked_day.id
).delete()
tracked_day.is_modified = True
# Add template meals to tracked day
for template_meal in template_meals:
tracked_meal = TrackedMeal(
tracked_day_id=tracked_day.id,
meal_id=template_meal.meal_id,
meal_time=template_meal.meal_time
)
db.add(tracked_meal)
db.commit()
logging.info(f"DEBUG: Successfully applied template with {len(template_meals)} meals")
return {"status": "success"}
except Exception as e:
db.rollback()
logging.error(f"DEBUG: Error applying template: {e}")
return {"status": "error", "message": str(e)}
@router.post("/tracker/update_tracked_food")
async def update_tracked_food(request: Request, data: dict = Body(...), db: Session = Depends(get_db)):
"""Update quantity of a custom food in a tracked meal"""
try:
tracked_food_id = data.get("tracked_food_id")
quantity = float(data.get("quantity", 1.0))
is_custom = data.get("is_custom", False)
logging.info(f"DEBUG: Updating tracked food {tracked_food_id} quantity to {quantity}")
if is_custom:
tracked_food = db.query(TrackedMealFood).filter(TrackedMealFood.id == tracked_food_id).first()
else:
# It's a MealFood, we need to create a TrackedMealFood for it
meal_food = db.query(MealFood).filter(MealFood.id == tracked_food_id).first()
if not meal_food:
return {"status": "error", "message": "Meal food not found"}
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.meal_id == meal_food.meal_id).first()
if not tracked_meal:
return {"status": "error", "message": "Tracked meal not found"}
tracked_food = TrackedMealFood(
tracked_meal_id=tracked_meal.id,
food_id=meal_food.food_id,
quantity=quantity
)
db.add(tracked_food)
# We can now remove the original MealFood to avoid duplication
db.delete(meal_food)
if not tracked_food:
return {"status": "error", "message": "Tracked food not found"}
# Update quantity
tracked_food.quantity = quantity
# Mark the tracked day as modified
tracked_day = tracked_food.tracked_meal.tracked_day
tracked_day.is_modified = True
db.commit()
logging.info(f"DEBUG: Successfully updated tracked food quantity")
return {"status": "success"}
except Exception as e:
db.rollback()
logging.error(f"DEBUG: Error updating tracked food: {e}")
return {"status": "error", "message": str(e)}
@router.post("/tracker/reset_to_plan")
async def tracker_reset_to_plan(request: Request, db: Session = Depends(get_db)):
"""Reset tracked day back to original plan"""
try:
form_data = await request.form()
person = form_data.get("person")
date_str = form_data.get("date")
logging.info(f"DEBUG: Resetting to plan - person={person}, date={date_str}")
# Parse date
from datetime import datetime
date = datetime.fromisoformat(date_str).date()
# Get tracked day
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == date
).first()
if not tracked_day:
return {"status": "error", "message": "No tracked day found"}
# Clear tracked meals
db.query(TrackedMeal).filter(
TrackedMeal.tracked_day_id == tracked_day.id
).delete()
# Reset modified flag
tracked_day.is_modified = False
db.commit()
logging.info(f"DEBUG: Successfully reset to plan")
return {"status": "success"}
except Exception as e:
db.rollback()
logging.error(f"DEBUG: Error resetting to plan: {e}")
return {"status": "error", "message": str(e)}
@router.get("/tracker/get_tracked_meal_foods/{tracked_meal_id}")
async def get_tracked_meal_foods(tracked_meal_id: int, db: Session = Depends(get_db)):
"""Get foods associated with a tracked meal"""
logging.info(f"DEBUG: get_tracked_meal_foods called for tracked_meal_id: {tracked_meal_id}")
try:
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first()
logging.info(f"DEBUG: Tracked meal found: {tracked_meal.id if tracked_meal else 'None'}")
if not tracked_meal:
raise HTTPException(status_code=404, detail="Tracked meal not found")
# Load the associated Meal and its foods
meal = db.query(Meal).options(joinedload(Meal.meal_foods).joinedload(MealFood.food)).filter(Meal.id == tracked_meal.meal_id).first()
logging.info(f"DEBUG: Associated meal found: {meal.id if meal else 'None'}")
if not meal:
raise HTTPException(status_code=404, detail="Associated meal not found")
# Load custom tracked foods for this tracked meal
tracked_foods = db.query(TrackedMealFood).options(joinedload(TrackedMealFood.food)).filter(TrackedMealFood.tracked_meal_id == tracked_meal_id).all()
logging.info(f"DEBUG: Found {len(tracked_foods)} custom tracked foods.")
# Combine foods from the base meal and custom tracked foods, handling overrides
meal_foods_data = []
# Keep track of food_ids that have been overridden by TrackedMealFood entries
# These should not be added from the base meal definition
overridden_food_ids = {tf.food_id for tf in tracked_foods}
logging.info(f"DEBUG: Overridden food IDs: {overridden_food_ids}")
for meal_food in meal.meal_foods:
# Only add meal_food if it hasn't been overridden by a TrackedMealFood
if meal_food.food_id not in overridden_food_ids:
meal_foods_data.append({
"id": meal_food.id,
"food_id": meal_food.food.id,
"food_name": meal_food.food.name,
"quantity": meal_food.quantity,
"serving_unit": meal_food.food.serving_unit,
"serving_size": meal_food.food.serving_size,
"is_custom": False
})
logging.info(f"DEBUG: Added {len(meal_foods_data)} meal foods (excluding overridden).")
for tracked_food in tracked_foods:
meal_foods_data.append({
"id": tracked_food.id,
"food_id": tracked_food.food.id,
"food_name": tracked_food.food.name,
"quantity": tracked_food.quantity,
"serving_unit": tracked_food.food.serving_unit,
"serving_size": tracked_food.food.serving_size,
"is_custom": True
})
logging.info(f"DEBUG: Added {len(tracked_foods)} custom tracked foods.")
logging.info(f"DEBUG: Total meal foods data items: {len(meal_foods_data)}")
return {"status": "success", "meal_foods": meal_foods_data}
except HTTPException as he:
logging.error(f"DEBUG: HTTP Error getting tracked meal foods: {he.detail}")
return {"status": "error", "message": he.detail}
except Exception as e:
logging.error(f"DEBUG: Error getting tracked meal foods: {e}")
return {"status": "error", "message": str(e)}
@router.post("/tracker/add_food_to_tracked_meal")
async def add_food_to_tracked_meal(data: dict = Body(...), db: Session = Depends(get_db)):
"""Add a food to an existing tracked meal"""
try:
tracked_meal_id = data.get("tracked_meal_id")
food_id = data.get("food_id")
quantity = float(data.get("quantity", 1.0))
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first()
if not tracked_meal:
raise HTTPException(status_code=404, detail="Tracked meal not found")
food = db.query(Food).filter(Food.id == food_id).first()
if not food:
raise HTTPException(status_code=404, detail="Food not found")
# Create a new MealFood entry for the tracked meal's associated meal
meal_food = MealFood(
meal_id=tracked_meal.meal_id,
food_id=food_id,
quantity=quantity
)
db.add(meal_food)
# Mark the tracked day as modified
tracked_meal.tracked_day.is_modified = True
db.commit()
return {"status": "success"}
except HTTPException as he:
db.rollback()
logging.error(f"DEBUG: HTTP Error adding food to tracked meal: {he.detail}")
return {"status": "error", "message": he.detail}
except Exception as e:
db.rollback()
logging.error(f"DEBUG: Error adding food to tracked meal: {e}")
return {"status": "error", "message": str(e)}
@router.post("/tracker/update_tracked_meal_foods")
async def update_tracked_meal_foods(data: dict = Body(...), db: Session = Depends(get_db)):
"""Update quantities of multiple foods in a tracked meal"""
logging.info(f"DEBUG: update_tracked_meal_foods called for tracked_meal_id: {data.get('tracked_meal_id')}")
try:
tracked_meal_id = data.get("tracked_meal_id")
foods_data = data.get("foods", [])
logging.info(f"DEBUG: Foods data received: {foods_data}")
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first()
logging.info(f"DEBUG: Tracked meal found: {tracked_meal.id if tracked_meal else 'None'}")
if not tracked_meal:
raise HTTPException(status_code=404, detail="Tracked meal not found")
for food_data in foods_data:
food_id = food_data.get("food_id")
grams = float(food_data.get("quantity", 1.0)) # Assuming quantity is now grams
is_custom = food_data.get("is_custom", False)
item_id = food_data.get("id") # This could be MealFood.id or TrackedMealFood.id
logging.info(f"DEBUG: Processing food_id: {food_id}, quantity: {grams}, is_custom: {is_custom}, item_id: {item_id}")
quantity = grams
if is_custom:
tracked_food = db.query(TrackedMealFood).filter(TrackedMealFood.id == item_id).first()
if tracked_food:
tracked_food.quantity = quantity
logging.info(f"DEBUG: Updated existing custom tracked food {item_id} to quantity {quantity}")
else:
# If it's a new custom food being added
new_tracked_food = TrackedMealFood(
tracked_meal_id=tracked_meal.id,
food_id=food_id,
quantity=quantity
)
db.add(new_tracked_food)
logging.info(f"DEBUG: Added new custom tracked food for food_id {food_id} with quantity {quantity}")
else:
# This is a food from the original meal definition
# We need to check if it's already a TrackedMealFood (meaning it was overridden)
# Or if it's still a MealFood
existing_tracked_food = db.query(TrackedMealFood).filter(
TrackedMealFood.tracked_meal_id == tracked_meal.id,
TrackedMealFood.food_id == food_id
).first()
logging.info(f"DEBUG: Checking for existing TrackedMealFood for food_id {food_id}: {existing_tracked_food.id if existing_tracked_food else 'None'}")
if existing_tracked_food:
existing_tracked_food.quantity = quantity
logging.info(f"DEBUG: Updated existing TrackedMealFood {existing_tracked_food.id} (override) to quantity {quantity}")
else:
# If it's not a TrackedMealFood, it must be a MealFood
meal_food = db.query(MealFood).filter(
MealFood.meal_id == tracked_meal.meal_id,
MealFood.food_id == food_id
).first()
logging.info(f"DEBUG: Checking for existing MealFood for food_id {food_id}: {meal_food.id if meal_food else 'None'}")
if meal_food:
# If quantity changed, convert to TrackedMealFood
# NOTE: meal_food.quantity is already a multiplier,
# but the incoming 'quantity' is a multiplier derived from grams.
# So, we compare the incoming multiplier with the existing multiplier.
if meal_food.quantity != quantity:
new_tracked_food = TrackedMealFood(
tracked_meal_id=tracked_meal.id,
food_id=food_id,
quantity=quantity,
is_override=True
)
db.add(new_tracked_food)
db.delete(meal_food) # Remove original MealFood
logging.info(f"DEBUG: Converted MealFood {meal_food.id} to new TrackedMealFood for food_id {food_id} with quantity {quantity} and deleted original MealFood.")
else:
logging.info(f"DEBUG: MealFood {meal_food.id} quantity unchanged, no override needed.")
else:
# This case should ideally not happen if data is consistent,
# but as a fallback, add as a new TrackedMealFood
new_tracked_food = TrackedMealFood(
tracked_meal_id=tracked_meal.id,
food_id=food_id,
quantity=quantity
)
db.add(new_tracked_food)
logging.warning(f"DEBUG: Fallback: Added new TrackedMealFood for food_id {food_id} with quantity {quantity}. Original MealFood not found.")
# Mark the tracked day as modified
tracked_meal.tracked_day.is_modified = True
db.commit()
return {"status": "success"}
except HTTPException as he:
db.rollback()
logging.error(f"DEBUG: HTTP Error updating tracked meal foods: {he.detail}")
return {"status": "error", "message": he.detail}
except Exception as e:
db.rollback()
logging.error(f"DEBUG: Error updating tracked meal foods: {e}")
return {"status": "error", "message": str(e)}
@router.delete("/tracker/remove_food_from_tracked_meal/{meal_food_id}")
async def remove_food_from_tracked_meal(meal_food_id: int, db: Session = Depends(get_db)):
"""Remove a food from a tracked meal"""
try:
meal_food = db.query(MealFood).filter(MealFood.id == meal_food_id).first()
if not meal_food:
raise HTTPException(status_code=404, detail="Meal food not found")
# Mark the tracked day as modified
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.meal_id == meal_food.meal_id).first()
if tracked_meal:
tracked_meal.tracked_day.is_modified = True
db.delete(meal_food)
db.commit()
return {"status": "success"}
except HTTPException as he:
db.rollback()
logging.error(f"DEBUG: HTTP Error removing food from tracked meal: {he.detail}")
return {"status": "error", "message": he.detail}
except Exception as e:
db.rollback()
logging.error(f"DEBUG: Error removing food from tracked meal: {e}")
return {"status": "error", "message": str(e)}
@router.delete("/tracker/remove_custom_food_from_tracked_meal/{tracked_meal_food_id}")
async def remove_custom_food_from_tracked_meal(tracked_meal_food_id: int, db: Session = Depends(get_db)):
"""Remove a custom food from a tracked meal"""
try:
tracked_meal_food = db.query(TrackedMealFood).filter(TrackedMealFood.id == tracked_meal_food_id).first()
if not tracked_meal_food:
raise HTTPException(status_code=404, detail="Tracked meal food not found")
# Mark the tracked day as modified
tracked_meal = tracked_meal_food.tracked_meal
if tracked_meal:
tracked_meal.tracked_day.is_modified = True
db.delete(tracked_meal_food)
db.commit()
return {"status": "success"}
except HTTPException as he:
db.rollback()
logging.error(f"DEBUG: HTTP Error removing custom food from tracked meal: {he.detail}")
return {"status": "error", "message": he.detail}
except Exception as e:
db.rollback()
logging.error(f"DEBUG: Error removing custom food from tracked meal: {e}")
return {"status": "error", "message": str(e)}
@router.post("/tracker/save_as_new_meal")
async def save_as_new_meal(data: dict = Body(...), db: Session = Depends(get_db)):
"""Save an edited tracked meal as a new meal/variant"""
try:
tracked_meal_id = data.get("tracked_meal_id")
new_meal_name = data.get("new_meal_name")
foods_data = data.get("foods", [])
if not new_meal_name:
raise HTTPException(status_code=400, detail="New meal name is required")
tracked_meal = db.query(TrackedMeal).options(
joinedload(TrackedMeal.meal).joinedload(Meal.meal_foods).joinedload(MealFood.food),
joinedload(TrackedMeal.tracked_foods).joinedload(TrackedMealFood.food)
).filter(TrackedMeal.id == tracked_meal_id).first()
if not tracked_meal:
raise HTTPException(status_code=404, detail="Tracked meal not found")
# Create a new meal
new_meal = Meal(name=new_meal_name, meal_type="custom", meal_time=tracked_meal.meal_time)
db.add(new_meal)
db.flush() # Flush to get the new meal ID
# Add foods to the new meal
for food_data in foods_data:
meal_food = MealFood(
meal_id=new_meal.id,
food_id=food_data["food_id"],
quantity=food_data["quantity"]
)
db.add(meal_food)
# Update the original tracked meal to point to the new meal
tracked_meal.meal_id = new_meal.id
# Clear custom tracked foods from the original tracked meal
for tf in tracked_meal.tracked_foods:
db.delete(tf)
# Mark the tracked day as modified
tracked_meal.tracked_day.is_modified = True
db.commit()
db.refresh(new_meal)
db.refresh(tracked_meal)
return {"status": "success", "new_meal_id": new_meal.id}
except HTTPException as he:
db.rollback()
logging.error(f"DEBUG: HTTP Error saving as new meal: {he.detail}")
return {"status": "error", "message": he.detail}
except Exception as e:
db.rollback()
logging.error(f"DEBUG: Error saving as new meal: {e}")
return {"status": "error", "message": str(e)}
@router.post("/tracker/add_food")
async def tracker_add_food(data: dict = Body(...), db: Session = Depends(get_db)):
"""Add a single food item to the tracker"""
try:
person = data.get("person")
date_str = data.get("date")
food_id = data.get("food_id")
grams = float(data.get("quantity", 1.0)) # Assuming quantity is now grams
meal_time = data.get("meal_time")
logging.info(f"DEBUG: Adding single food to tracker - person={person}, date={date_str}, food_id={food_id}, grams={grams}, meal_time={meal_time}")
# Parse date
from datetime import datetime
date = datetime.fromisoformat(date_str).date()
# Get or create tracked day
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == date
).first()
if not tracked_day:
tracked_day = TrackedDay(person=person, date=date, is_modified=True)
db.add(tracked_day)
db.commit()
db.refresh(tracked_day)
# The quantity is already in grams, so no conversion needed
quantity = grams
# Create a new Meal for this single food entry
# This allows it to be treated like any other meal in the tracker view
food_item = db.query(Food).filter(Food.id == food_id).first()
if not food_item:
return {"status": "error", "message": "Food not found"}
new_meal = Meal(name=food_item.name, meal_type="single_food", meal_time=meal_time)
db.add(new_meal)
db.flush() # Flush to get the new meal ID
# Link the food to the new meal
meal_food = MealFood(meal_id=new_meal.id, food_id=food_id, quantity=quantity)
db.add(meal_food)
# Create tracked meal entry
tracked_meal = TrackedMeal(
tracked_day_id=tracked_day.id,
meal_id=new_meal.id,
meal_time=meal_time
)
db.add(tracked_meal)
# Mark day as modified
tracked_day.is_modified = True
db.commit()
logging.info(f"DEBUG: Successfully added single food to tracker")
return {"status": "success"}
except ValueError as ve:
db.rollback()
logging.error(f"DEBUG: Error adding single food to tracker: {ve}")
return {"status": "error", "message": str(ve)}
except Exception as e:
db.rollback()
logging.error(f"DEBUG: Error adding single food to tracker: {e}")
return {"status": "error", "message": str(e)}
@router.get("/detailed_tracked_day", response_class=HTMLResponse, name="detailed_tracked_day")
async def detailed_tracked_day(request: Request, person: str = "Sarah", date: Optional[str] = None, db: Session = Depends(get_db)):
"""
Displays a detailed view of a tracked day, including all meals and their food breakdowns.
"""
logging.info(f"DEBUG: Detailed tracked day page requested with person={person}, date={date}")
# If no date is provided, default to today's date
if not date:
current_date = date.today()
else:
try:
current_date = datetime.fromisoformat(date).date()
except ValueError:
logging.error(f"DEBUG: Invalid date format for date: {date}")
return templates.TemplateResponse("detailed.html", {
"request": request, "title": "Invalid Date",
"error": "Invalid date format. Please use YYYY-MM-DD.",
"day_totals": {},
"person": person
})
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == current_date
).first()
if not tracked_day:
return templates.TemplateResponse("detailed_tracked_day.html", {
"request": request, "title": "No Tracked Day Found",
"error": "No tracked meals found for this day.",
"day_totals": {},
"person": person,
"plan_date": current_date # Pass current_date for consistent template behavior
})
tracked_meals = db.query(TrackedMeal).options(
joinedload(TrackedMeal.meal).joinedload(Meal.meal_foods).joinedload(MealFood.food),
joinedload(TrackedMeal.tracked_foods).joinedload(TrackedMealFood.food)
).filter(
TrackedMeal.tracked_day_id == tracked_day.id
).all()
day_totals = calculate_day_nutrition_tracked(tracked_meals, db)
meal_details = []
for tracked_meal in tracked_meals:
meal_nutrition = calculate_meal_nutrition(tracked_meal.meal, db) # Base meal nutrition
foods = []
# Add foods from the base meal definition
for mf in tracked_meal.meal.meal_foods:
foods.append({
'name': mf.food.name,
'quantity': mf.quantity,
'serving_size': mf.food.serving_size,
'serving_unit': mf.food.serving_unit,
'calories': mf.food.calories * mf.quantity,
'protein': mf.food.protein * mf.quantity,
'carbs': mf.food.carbs * mf.quantity,
'fat': mf.food.fat * mf.quantity,
'fiber': (mf.food.fiber or 0) * mf.quantity,
'sugar': (mf.food.sugar or 0) * mf.quantity,
'sodium': (mf.food.sodium or 0) * mf.quantity,
'calcium': (mf.food.calcium or 0) * mf.quantity,
})
# Add custom tracked foods (overrides or additions)
for tmf in tracked_meal.tracked_foods:
foods.append({
'name': tmf.food.name,
'quantity': tmf.quantity,
'serving_size': tmf.food.serving_size,
'serving_unit': tmf.food.serving_unit,
'calories': tmf.food.calories * tmf.quantity,
'protein': tmf.food.protein * tmf.quantity,
'carbs': tmf.food.carbs * tmf.quantity,
'fat': tmf.food.fat * tmf.quantity,
'fiber': (tmf.food.fiber or 0) * tmf.quantity,
'sugar': (tmf.food.sugar or 0) * tmf.quantity,
'sodium': (tmf.food.sodium or 0) * tmf.quantity,
'calcium': (tmf.food.calcium or 0) * tmf.quantity,
})
meal_details.append({
'plan': {'meal': tracked_meal.meal, 'meal_time': tracked_meal.meal_time},
'nutrition': meal_nutrition,
'foods': foods
})
context = {
"request": request,
"title": f"Detailed Day for {person} on {current_date.strftime('%B %d, %Y')}",
"meal_details": meal_details,
"day_totals": day_totals,
"person": person,
"plan_date": current_date # Renamed from current_date to plan_date for consistency with detailed.html
}
if not meal_details:
context["message"] = "No meals tracked for this day."
logging.info(f"DEBUG: Rendering tracked day details with context: {context}")
return templates.TemplateResponse("detailed_tracked_day.html", context)

View File

@@ -0,0 +1,98 @@
--- tracker.py
+++ tracker.py
@@ -110,4 +110,94 @@
except Exception as e:
db.rollback()
logging.error(f"debug: error removing tracked meal: {e}")
- return {"status": "error", "message": str(e)}
+ return {"status": "error", "message": str(e)}
+
+@router.post("/tracker/save_template")
+async def tracker_save_template(request: Request, db: Session = Depends(get_db)):
+ """save current day's meals as template"""
+ try:
+ form_data = await request.form()
+ person = form_data.get("person")
+ date_str = form_data.get("date")
+ template_name = form_data.get("template_name")
+ logging.info(f"debug: saving template - name={template_name}, person={person}, date={date_str}")
+
+ # Parse date
+ from datetime import datetime
+ date = datetime.fromisoformat(date_str).date()
+
+ # Get tracked day and meals
+ tracked_day = db.query(TrackedDay).filter(
+ TrackedDay.person == person,
+ TrackedDay.date == date
+ ).first()
+ if not tracked_day:
+ return {"status": "error", "message": "No tracked day found"}
+
+ tracked_meals = db.query(TrackedMeal).filter(
+ TrackedMeal.tracked_day_id == tracked_day.id
+ ).all()
+
+ if not tracked_meals:
+ return {"status": "error", "message": "No tracked meals found"}
+
+ # Create new template
+ template = Template(name=template_name)
+ db.add(template)
+ db.flush()
+
+ # Add meals to template
+ for tracked_meal in tracked_meals:
+ template_meal = TemplateMeal(
+ template_id=template.id,
+ meal_id=tracked_meal.meal_id,
+ meal_time=tracked_meal.meal_time
+ )
+ db.add(template_meal)
+
+ db.commit()
+ return {"status": "success", "message": "Template saved successfully"}
+ except Exception as e:
+ db.rollback()
+ logging.error(f"debug: error saving template: {e}")
+ return {"status": "error", "message": str(e)}
+
+@router.post("/tracker/apply_template")
+async def tracker_apply_template(request: Request, db: Session = Depends(get_db)):
+ """apply template to current day"""
+ try:
+ form_data = await request.form()
+ person = form_data.get("person")
+ date_str = form_data.get("date")
+ template_id = form_data.get("template_id")
+ logging.info(f"debug: applying template - template_id={template_id}, person={person}, date={date_str}")
+
+ # Parse date
+ from datetime import datetime
+ date = datetime.fromisoformat(date_str).date()
+
+ # Get template meals
+ template_meals = db.query(TemplateMeal).filter(
+ TemplateMeal.template_id == template_id
+ ).all()
+
+ if not template_meals:
+ return {"status": "error", "message": "Template has no meals"}
+
+ # Get or create tracked day
+ tracked_day = db.query(TrackedDay).filter(
+ TrackedDay.person == person,
+ TrackedDay.date == date
+ ).first()
+ if not tracked_day:
+ tracked_day = TrackedDay(person=person, date=date, is_modified=True)
+ db.add(tracked_day)
+ db.flush()
+
+ # Clear existing meals and add template meals
+ db.query(TrackedMeal).filter(TrackedMeal.tracked_day_id == tracked_day.id).delete()
+
+ for template_meal in template_meals:
+ tracked_meal = TrackedMeal(
+ tracked_day_id=tracked_day.id,
+ meal_id=template_meal.meal_id,

21
detailed.patch Normal file
View File

@@ -0,0 +1,21 @@
--- templates/detailed.html
+++ templates/detailed.html
@@ -1,3 +1,7 @@
+{# Look for the meal details section and ensure it shows food breakdown #}
+{% for meal_detail in meal_details %}
+ {# Existing meal header code... #}
+
+ {# ADD FOOD BREAKDOWN SECTION: #}
+ <div class="food-breakdown">
+ <h4>Food Breakdown:</h4>
+ <ul>
+ {% for food in meal_detail.foods %}
+ <li>{{ food.quantity }}g {{ food.name }}
+ {% if food.serving_size and food.serving_unit %}
+ ({{ food.serving_size }}{{ food.serving_unit }})
+ {% endif %}
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+{% endfor %}

125
fix_tracer.py Normal file
View File

@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""
Script to fix the incomplete tracker.py file
"""
def fix_tracker_file():
file_path = "app/api/routes/tracker.py"
with open(file_path, 'r') as f:
content = f.read()
# Check if file is incomplete (ends abruptly)
if content.strip().endswith('@router.post("/tracker/save_template")'):
print("File is incomplete, adding missing content...")
missing_content = '''async def tracker_save_template(request: Request, db: Session = Depends(get_db)):
"""save current day's meals as template"""
try:
form_data = await request.form()
person = form_data.get("person")
date_str = form_data.get("date")
template_name = form_data.get("template_name")
logging.info(f"debug: saving template - name={template_name}, person={person}, date={date_str}")
# Parse date
from datetime import datetime
date = datetime.fromisoformat(date_str).date()
# Get tracked day and meals
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == date
).first()
if not tracked_day:
return {"status": "error", "message": "No tracked day found"}
tracked_meals = db.query(TrackedMeal).filter(
TrackedMeal.tracked_day_id == tracked_day.id
).all()
if not tracked_meals:
return {"status": "error", "message": "No tracked meals found"}
# Create new template
template = Template(name=template_name)
db.add(template)
db.flush()
# Add meals to template
for tracked_meal in tracked_meals:
template_meal = TemplateMeal(
template_id=template.id,
meal_id=tracked_meal.meal_id,
meal_time=tracked_meal.meal_time
)
db.add(template_meal)
db.commit()
return {"status": "success", "message": "Template saved successfully"}
except Exception as e:
db.rollback()
logging.error(f"debug: error saving template: {e}")
return {"status": "error", "message": str(e)}
@router.post("/tracker/apply_template")
async def tracker_apply_template(request: Request, db: Session = Depends(get_db)):
"""apply template to current day"""
try:
form_data = await request.form()
person = form_data.get("person")
date_str = form_data.get("date")
template_id = form_data.get("template_id")
logging.info(f"debug: applying template - template_id={template_id}, person={person}, date={date_str}")
# Parse date
from datetime import datetime
date = datetime.fromisoformat(date_str).date()
# Get template meals
template_meals = db.query(TemplateMeal).filter(
TemplateMeal.template_id == template_id
).all()
if not template_meals:
return {"status": "error", "message": "Template has no meals"}
# Get or create tracked day
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == date
).first()
if not tracked_day:
tracked_day = TrackedDay(person=person, date=date, is_modified=True)
db.add(tracked_day)
db.flush()
# Clear existing meals and add template meals
db.query(TrackedMeal).filter(TrackedMeal.tracked_day_id == tracked_day.id).delete()
for template_meal in template_meals:
tracked_meal = TrackedMeal(
tracked_day_id=tracked_day.id,
meal_id=template_meal.meal_id,
meal_time=template_meal.meal_time
)
db.add(tracked_meal)
tracked_day.is_modified = True
db.commit()
return {"status": "success", "message": "Template applied successfully"}
except Exception as e:
db.rollback()
logging.error(f"debug: error applying template: {e}")
return {"status": "error", "message": str(e)}'''
# Append the missing content
with open(file_path, 'a') as f:
f.write('\n' + missing_content)
print("Tracker.py file fixed successfully!")
else:
print("Tracker.py file appears to be complete")
if __name__ == "__main__":
fix_tracker_file()

160
plans.patch Normal file
View File

@@ -0,0 +1,160 @@
--- app/api/routes/plans.py.orig
+++ app/api/routes/plans.py.fixed
@@ -1,7 +1,7 @@
from fastapi import APIRouter, Depends, HTTPException, Request, Form, Body
from fastapi.responses import HTMLResponse, RedirectResponse
-from sqlalchemy.orm import Session
+from sqlalchemy.orm import Session, joinedload
from datetime import date, datetime, timedelta
import logging
from typing import List, Optional
@@ -9,7 +9,7 @@
# Import database module
from app.database import get_db, Food, Meal, MealFood, Plan, Template, TemplateMeal, WeeklyMenu, WeeklyMenuDay, TrackedDay, TrackedMeal, calculate_meal_nutrition, calculate_day_nutrition
from main import templates
-
+from app.database import calculate_tracked_meal_nutrition, calculate_day_nutrition_tracked
router = APIRouter()
# Plan tab
@@ -156,6 +156,7 @@
"""render detailed view for a specific day or template"""
from datetime import datetime, date
import logging
+ from sqlalchemy.orm import joinedload
logging.info(f"debug: detailed page requested url: {request.url.path}, query_params: {request.query_params}")
logging.info(f"debug: detailed page requested person={person}, plan_date={plan_date}, template_id={template_id}")
@@ -189,6 +190,20 @@
for tm in template_meals:
meal_nutrition = calculate_meal_nutrition(tm.meal, db)
meal_details.append({
+ 'plan': {'meal': tm.meal, 'meal_time': tm.meal_time},
+ 'nutrition': meal_nutrition,
+ 'foods': [] # Template view should show individual foods
+ })
+ # ADD FOOD BREAKDOWN FOR TEMPLATES
+ foods = []
+ for mf in tm.meal.meal_foods:
+ foods.append({
+ 'name': mf.food.name,
+ 'quantity': mf.quantity,
+ 'serving_size': mf.food.serving_size,
+ 'serving_unit': mf.food.serving_unit,
+ })
+ meal_details[-1]['foods'] = foods
+ # Accumulate nutrition totals
+ for key in template_nutrition:
+ if key in meal_nutrition:
+ template_nutrition[key] += meal_nutrition[key]
@@ -232,42 +247,64 @@
plan_date_obj = datetime.fromisoformat(plan_date).date()
except ValueError:
logging.error(f"debug: invalid date format plan_date: {plan_date}")
- return templates.TemplateResponse("detailed.html", {
+ return templates.TemplateResponse(request, "detailed.html", {
"request": request,
"title": "Invalid date",
"error": "Invalid date format. Please use YYYY-MM-DD.",
"day_totals": {},
"templates": templates_list,
- "person": person
+ "person": person,
+ "is_tracked_view": True
})
- logging.info(f"debug: loading plan for {person} on {plan_date_obj}")
- plans = db.query(Plan).filter(Plan.person == person, Plan.date == plan_date_obj).all()
- logging.info(f"debug: found {len(plans)} plans for {person} on {plan_date_obj}")
-
- day_totals = calculate_day_nutrition(plans, db)
+ logging.info(f"debug: loading TRACKED meals for {person} on {plan_date_obj}")
+
+ # Get tracked day and meals instead of planned meals
+ tracked_day = db.query(TrackedDay).filter(
+ TrackedDay.person == person,
+ TrackedDay.date == plan_date_obj
+ ).first()
+
meal_details = []
- for plan in plans:
- meal_nutrition = calculate_meal_nutrition(plan.meal, db)
- foods = []
- for mf in plan.meal.meal_foods:
- foods.append({
- 'name': mf.food.name,
- 'quantity': mf.quantity,
- 'serving_size': mf.food.serving_size,
- 'serving_unit': mf.food.serving_unit,
- 'calories': mf.food.calories * mf.quantity,
- 'protein': mf.food.protein * mf.quantity,
- 'carbs': mf.food.carbs * mf.quantity,
- 'fat': mf.food.fat * mf.quantity,
- 'fiber': (mf.food.fiber or 0) * mf.quantity,
- 'sodium': (mf.food.sodium or 0) * mf.quantity,
- })
- meal_details.append({
- 'plan': plan,
- 'nutrition': meal_nutrition,
- 'foods': foods
- })
+ day_totals = {'calories': 0, 'protein': 0, 'carbs': 0, 'fat': 0, 'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0}
+
+ if tracked_day:
+ tracked_meals = db.query(TrackedMeal).filter(
+ TrackedMeal.tracked_day_id == tracked_day.id
+ ).options(joinedload(TrackedMeal.meal).joinedload(Meal.meal_foods).joinedload(MealFood.food)).all()
+
+ logging.info(f"debug: found {len(tracked_meals)} tracked meals for {person} on {plan_date_obj}")
+
+ for tracked_meal in tracked_meals:
+ meal_nutrition = calculate_tracked_meal_nutrition(tracked_meal, db)
+ foods = []
+
+ # Show base meal foods
+ for mf in tracked_meal.meal.meal_foods:
+ foods.append({
+ 'name': mf.food.name,
+ 'quantity': mf.quantity,
+ 'serving_size': mf.food.serving_size,
+ 'serving_unit': mf.food.serving_unit,
+ })
+
+ # Show custom tracked foods (overrides/additions)
+ for tracked_food in tracked_meal.tracked_foods:
+ foods.append({
+ 'name': f"{tracked_food.food.name} {'(override)' if tracked_food.is_override else '(addition)'}",
+ 'quantity': tracked_food.quantity,
+ 'serving_size': tracked_food.food.serving_size,
+ 'serving_unit': tracked_food.food.serving_unit,
+ })
+
+ meal_details.append({
+ 'plan': tracked_meal, # Use tracked_meal instead of plan
+ 'nutrition': meal_nutrition,
+ 'foods': foods
+ })
+
+ # Accumulate day totals
+ for key in day_totals:
+ if key in meal_nutrition:
+ day_totals[key] += meal_nutrition[key]
context = {
"request": request,
@@ -276,10 +313,11 @@
"day_totals": day_totals,
"person": person,
"plan_date": plan_date_obj,
- "templates": templates_list
+ "templates": templates_list,
+ "is_tracked_view": True # Add flag to indicate this is tracked view
}
-
- if not meal_details:
- context["message"] = "No meals planned for this day."
+
+ if not meal_details and tracked_day:
+ context["message"] = "No meals tracked for this day."
logging.info(f"debug: rendering plan details context: {context}")

View File

@@ -6,7 +6,7 @@
<div id="daySelector"> <div id="daySelector">
<form action="{{ url_for('detailed') }}" method="get" class="d-flex"> <form action="{{ url_for('detailed') }}" method="get" class="d-flex">
<input type="hidden" name="person" value="{{ person }}"> <input type="hidden" name="person" value="{{ person }}">
<input type="date" class="form-control me-2" name="plan_date" value="{{ plan_date.isoformat() if plan_date else '' }}" required> <input type="date" class="form-control me-2" name="plan_date" value="{{ plan_date.isoformat() if plan_date else '' }}" onchange="this.form.submit()" required>
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit">
<i class="bi bi-search"></i> View Day <i class="bi bi-search"></i> View Day
</button> </button>
@@ -129,10 +129,18 @@
<div class="meal-card"> <div class="meal-card">
<div class="meal-header"> <div class="meal-header">
<span> <span>
<i class="bi bi-egg-fried"></i> {{ meal_detail.plan.meal.name }} - {{ meal_detail.plan.meal.meal_type.title() }} <i class="bi bi-egg-fried"></i>
{% if is_tracked_view %}
{{ meal_detail.plan.meal.name }} - {{ meal_detail.plan.meal.meal_type.title() }}
{% if meal_detail.plan.meal_time %} {% if meal_detail.plan.meal_time %}
<small class="text-muted">({{ meal_detail.plan.meal_time }})</small> <small class="text-muted">({{ meal_detail.plan.meal_time }})</small>
{% endif %} {% endif %}
{% else %}
{{ meal_detail.plan.meal.name }} - {{ meal_detail.plan.meal.meal_type.title() }}
{% if meal_detail.plan.meal_time %}
<small class="text-muted">({{ meal_detail.plan.meal_time }})</small>
{% endif %}
{% endif %}
</span> </span>
<span class="badge bg-light text-dark">{{ "%.0f"|format(meal_detail.nutrition.calories) }} cal</span> <span class="badge bg-light text-dark">{{ "%.0f"|format(meal_detail.nutrition.calories) }} cal</span>
</div> </div>
@@ -276,7 +284,9 @@
{% elif not meal_details %} {% elif not meal_details %}
<div class="alert alert-info mt-3"> <div class="alert alert-info mt-3">
<i class="bi bi-info-circle"></i> <i class="bi bi-info-circle"></i>
{% if view_mode == 'template' %} {% if is_tracked_view %}
No meals tracked for this day.
{% elif selected_template_id %}
This template has no meals. This template has no meals.
{% else %} {% else %}
No meals planned for this day. No meals planned for this day.

115
tracker.patch Normal file
View File

@@ -0,0 +1,115 @@
--- app/api/routes/tracker.py
+++ app/api/routes/tracker.py
@@ -1,4 +1,4 @@
-from fastapi import APIRouter, Depends, HTTPException, Request, Form, Body
+from fastapi import APIRouter, Depends, HTTPException, Request, Form, Body, UploadFile, File
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy.orm import Session, joinedload
from datetime import date, datetime, timedelta
@@ -110,4 +110,94 @@
except Exception as e:
db.rollback()
logging.error(f"debug: error removing tracked meal: {e}")
- return {"status": "error", "message": str(e)}
+ return {"status": "error", "message": str(e)}
+
+@router.post("/tracker/save_template")
+async def tracker_save_template(request: Request, db: Session = Depends(get_db)):
+ """save current day's meals as template"""
+ try:
+ form_data = await request.form()
+ person = form_data.get("person")
+ date_str = form_data.get("date")
+ template_name = form_data.get("template_name")
+ logging.info(f"debug: saving template - name={template_name}, person={person}, date={date_str}")
+
+ # Parse date
+ from datetime import datetime
+ date = datetime.fromisoformat(date_str).date()
+
+ # Get tracked day and meals
+ tracked_day = db.query(TrackedDay).filter(
+ TrackedDay.person == person,
+ TrackedDay.date == date
+ ).first()
+ if not tracked_day:
+ return {"status": "error", "message": "No tracked day found"}
+
+ tracked_meals = db.query(TrackedMeal).filter(
+ TrackedMeal.tracked_day_id == tracked_day.id
+ ).all()
+
+ if not tracked_meals:
+ return {"status": "error", "message": "No tracked meals found"}
+
+ # Create new template
+ template = Template(name=template_name)
+ db.add(template)
+ db.flush()
+
+ # Add meals to template
+ for tracked_meal in tracked_meals:
+ template_meal = TemplateMeal(
+ template_id=template.id,
+ meal_id=tracked_meal.meal_id,
+ meal_time=tracked_meal.meal_time
+ )
+ db.add(template_meal)
+
+ db.commit()
+ return {"status": "success", "message": "Template saved successfully"}
+ except Exception as e:
+ db.rollback()
+ logging.error(f"debug: error saving template: {e}")
+ return {"status": "error", "message": str(e)}
+
+@router.post("/tracker/apply_template")
+async def tracker_apply_template(request: Request, db: Session = Depends(get_db)):
+ """apply template to current day"""
+ try:
+ form_data = await request.form()
+ person = form_data.get("person")
+ date_str = form_data.get("date")
+ template_id = form_data.get("template_id")
+ logging.info(f"debug: applying template - template_id={template_id}, person={person}, date={date_str}")
+
+ # Parse date
+ from datetime import datetime
+ date = datetime.fromisoformat(date_str).date()
+
+ # Get template meals
+ template_meals = db.query(TemplateMeal).filter(
+ TemplateMeal.template_id == template_id
+ ).all()
+
+ if not template_meals:
+ return {"status": "error", "message": "Template has no meals"}
+
+ # Get or create tracked day
+ tracked_day = db.query(TrackedDay).filter(
+ TrackedDay.person == person,
+ TrackedDay.date == date
+ ).first()
+ if not tracked_day:
+ tracked_day = TrackedDay(person=person, date=date, is_modified=True)
+ db.add(tracked_day)
+ db.flush()
+
+ # Clear existing meals and add template meals
+ db.query(TrackedMeal).filter(TrackedMeal.tracked_day_id == tracked_day.id).delete()
+
+ for template_meal in template_meals:
+ tracked_meal = TrackedMeal(
+ tracked_day_id=tracked_day.id,
+ meal_id=template_meal.meal_id,
+ meal_time=template_meal.meal_time
+ )
+ db.add(tracked_meal)
+
+ tracked_day.is_modified = True
+ db.commit()
+ return {"status": "success", "message": "Template applied successfully"}
+ except Exception as e:
+ db.rollback()
+ logging.error(f"debug: error applying template: {e}")
+ return {"status": "error", "message": str(e)}