mirror of
https://github.com/sstent/foodplanner.git
synced 2026-01-25 11:11:36 +00:00
fixed food details not loading on details tab
This commit is contained in:
@@ -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,66 +259,149 @@ 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 plan for {person} on {plan_date_obj}")
|
logging.info(f"debug: loading TRACKED meals 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)
|
|
||||||
|
|
||||||
meal_details = []
|
|
||||||
for plan in plans:
|
|
||||||
meal_nutrition = calculate_meal_nutrition(plan.meal, db)
|
|
||||||
|
|
||||||
foods = []
|
# Get tracked day and meals instead of planned meals
|
||||||
for mf in plan.meal.meal_foods:
|
tracked_day = db.query(TrackedDay).filter(
|
||||||
foods.append({
|
TrackedDay.person == person,
|
||||||
'name': mf.food.name,
|
TrackedDay.date == plan_date_obj
|
||||||
'quantity': mf.quantity,
|
).first()
|
||||||
'serving_size': mf.food.serving_size,
|
|
||||||
'serving_unit': mf.food.serving_unit,
|
meal_details = []
|
||||||
'calories': mf.food.calories * mf.quantity,
|
day_totals = {'calories': 0, 'protein': 0, 'carbs': 0, 'fat': 0, 'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0}
|
||||||
'protein': mf.food.protein * mf.quantity,
|
|
||||||
'carbs': mf.food.carbs * mf.quantity,
|
if tracked_day:
|
||||||
'fat': mf.food.fat * mf.quantity,
|
tracked_meals = db.query(TrackedMeal).filter(
|
||||||
'fiber': (mf.food.fiber or 0) * mf.quantity,
|
TrackedMeal.tracked_day_id == tracked_day.id
|
||||||
'sodium': (mf.food.sodium or 0) * mf.quantity,
|
).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}")
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
})
|
})
|
||||||
|
|
||||||
meal_details.append({
|
context = {
|
||||||
'plan': plan,
|
"request": request,
|
||||||
'nutrition': meal_nutrition,
|
"title": f"Detailed Plan for {person} on {plan_date_obj.strftime('%B %d, %Y')}" if person else "Detailed View",
|
||||||
'foods': foods
|
"meal_details": meal_details,
|
||||||
})
|
"day_totals": day_totals,
|
||||||
|
"person": person,
|
||||||
context = {
|
"plan_date": plan_date_obj,
|
||||||
"request": request,
|
"templates": templates_list
|
||||||
"title": f"Detailed Plan for {person} on {plan_date_obj.strftime('%B %d, %Y')}" if person else "Detailed View",
|
}
|
||||||
"meal_details": meal_details,
|
|
||||||
"day_totals": day_totals,
|
# If no meals are planned, add a message
|
||||||
"person": person,
|
if not meal_details:
|
||||||
"plan_date": plan_date_obj,
|
context["message"] = "No meals planned for this day."
|
||||||
"templates": templates_list
|
|
||||||
}
|
|
||||||
|
|
||||||
# If no meals are planned, add a message
|
|
||||||
if not meal_details:
|
|
||||||
context["message"] = "No meals planned for this day."
|
|
||||||
|
|
||||||
logging.info(f"DEBUG: Rendering plan details with context: {context}")
|
logging.info(f"DEBUG: Rendering plan details with context: {context}")
|
||||||
return templates.TemplateResponse("detailed.html", context)
|
return templates.TemplateResponse("detailed.html", context)
|
||||||
860
app/api/routes/tracker.py.orig
Normal file
860
app/api/routes/tracker.py.orig
Normal 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)
|
||||||
98
app/api/routes/tracker.py.rej
Normal file
98
app/api/routes/tracker.py.rej
Normal 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
21
detailed.patch
Normal 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
125
fix_tracer.py
Normal 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
160
plans.patch
Normal 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}")
|
||||||
@@ -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,9 +129,17 @@
|
|||||||
<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 meal_detail.plan.meal_time %}
|
{% if is_tracked_view %}
|
||||||
<small class="text-muted">({{ meal_detail.plan.meal_time }})</small>
|
{{ 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 %}
|
||||||
|
{% 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 %}
|
{% 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>
|
||||||
@@ -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
115
tracker.patch
Normal 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)}
|
||||||
Reference in New Issue
Block a user