working models

This commit is contained in:
2025-09-19 12:05:32 -07:00
parent 688757b0e5
commit 4d75d19f8d
7 changed files with 1221 additions and 300 deletions

Binary file not shown.

310
main.py
View File

@@ -15,6 +15,7 @@ from datetime import date, datetime
import os
import csv
from fastapi import File, UploadFile
# Database setup - Use SQLite for easier setup
DATABASE_URL = "sqlite:///./meal_planner.db"
# For production, use PostgreSQL: DATABASE_URL = "postgresql://username:password@localhost/meal_planner"
@@ -49,7 +50,7 @@ class Meal(Base):
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
meal_type = Column(String) # breakfast, lunch, dinner, snack
meal_type = Column(String) # breakfast, lunch, dinner, snack, custom
# Relationship to meal foods
meal_foods = relationship("MealFood", back_populates="meal")
@@ -70,7 +71,7 @@ class Plan(Base):
id = Column(Integer, primary_key=True, index=True)
person = Column(String, index=True) # Person A or Person B
date = Column(Date, index=True)
date = Column(String, index=True) # Changed from Date to String to store "Day1", "Day2", etc.
meal_id = Column(Integer, ForeignKey("meals.id"))
meal = relationship("Meal")
@@ -138,10 +139,10 @@ def calculate_meal_nutrition(meal, db: Session):
totals['protein'] += food.protein * quantity
totals['carbs'] += food.carbs * quantity
totals['fat'] += food.fat * quantity
totals['fiber'] += food.fiber * quantity
totals['sugar'] += food.sugar * quantity
totals['sodium'] += food.sodium * quantity
totals['calcium'] += food.calcium * quantity
totals['fiber'] += (food.fiber or 0) * quantity
totals['sugar'] += (food.sugar or 0) * quantity
totals['sodium'] += (food.sodium or 0) * quantity
totals['calcium'] += (food.calcium or 0) * quantity
# Calculate percentages
total_cals = totals['calories']
@@ -150,6 +151,11 @@ def calculate_meal_nutrition(meal, db: Session):
totals['carbs_pct'] = round((totals['carbs'] * 4 / total_cals) * 100, 1)
totals['fat_pct'] = round((totals['fat'] * 9 / total_cals) * 100, 1)
totals['net_carbs'] = totals['carbs'] - totals['fiber']
else:
totals['protein_pct'] = 0
totals['carbs_pct'] = 0
totals['fat_pct'] = 0
totals['net_carbs'] = 0
return totals
@@ -173,6 +179,11 @@ def calculate_day_nutrition(plans, db: Session):
day_totals['carbs_pct'] = round((day_totals['carbs'] * 4 / total_cals) * 100, 1)
day_totals['fat_pct'] = round((day_totals['fat'] * 9 / total_cals) * 100, 1)
day_totals['net_carbs'] = day_totals['carbs'] - day_totals['fiber']
else:
day_totals['protein_pct'] = 0
day_totals['carbs_pct'] = 0
day_totals['fat_pct'] = 0
day_totals['net_carbs'] = 0
return day_totals
@@ -238,6 +249,79 @@ async def bulk_upload_foods(file: UploadFile = File(...), db: Session = Depends(
db.rollback()
return {"status": "error", "message": str(e)}
@app.post("/foods/add")
async def add_food(request: Request, db: Session = Depends(get_db),
name: str = Form(...), serving_size: str = Form(...),
serving_unit: str = Form(...), calories: float = Form(...),
protein: float = Form(...), carbs: float = Form(...),
fat: float = Form(...), fiber: float = Form(0),
sugar: float = Form(0), sodium: float = Form(0),
calcium: float = Form(0)):
try:
food = Food(
name=name, serving_size=serving_size, serving_unit=serving_unit,
calories=calories, protein=protein, carbs=carbs, fat=fat,
fiber=fiber, sugar=sugar, sodium=sodium, calcium=calcium
)
db.add(food)
db.commit()
return {"status": "success", "message": "Food added successfully"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.post("/foods/edit")
async def edit_food(request: Request, db: Session = Depends(get_db),
food_id: int = Form(...), name: str = Form(...),
serving_size: str = Form(...), serving_unit: str = Form(...),
calories: float = Form(...), protein: float = Form(...),
carbs: float = Form(...), fat: float = Form(...),
fiber: float = Form(0), sugar: float = Form(0),
sodium: float = Form(0), calcium: float = Form(0)):
try:
food = db.query(Food).filter(Food.id == food_id).first()
if not food:
return {"status": "error", "message": "Food not found"}
food.name = name
food.serving_size = serving_size
food.serving_unit = serving_unit
food.calories = calories
food.protein = protein
food.carbs = carbs
food.fat = fat
food.fiber = fiber
food.sugar = sugar
food.sodium = sodium
food.calcium = calcium
db.commit()
return {"status": "success", "message": "Food updated successfully"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.post("/foods/delete")
async def delete_foods(food_ids: dict = Body(...), db: Session = Depends(get_db)):
try:
# Delete foods
db.query(Food).filter(Food.id.in_(food_ids["food_ids"])).delete(synchronize_session=False)
db.commit()
return {"status": "success"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
# Meals tab
@app.get("/meals", response_class=HTMLResponse)
async def meals_page(request: Request, db: Session = Depends(get_db)):
meals = db.query(Meal).all()
foods = db.query(Food).all()
return templates.TemplateResponse("meals.html",
{"request": request, "meals": meals, "foods": foods})
@app.post("/meals/upload")
async def bulk_upload_meals(file: UploadFile = File(...), db: Session = Depends(get_db)):
"""Handle bulk meal upload from CSV"""
@@ -329,50 +413,83 @@ async def bulk_upload_meals(file: UploadFile = File(...), db: Session = Depends(
db.rollback()
return {"status": "error", "message": str(e)}
@app.post("/foods/add")
async def add_food(request: Request, db: Session = Depends(get_db),
name: str = Form(...), serving_size: str = Form(...),
serving_unit: str = Form(...), calories: float = Form(...),
protein: float = Form(...), carbs: float = Form(...),
fat: float = Form(...), fiber: float = Form(0),
sugar: float = Form(0), sodium: float = Form(0),
calcium: float = Form(0)):
food = Food(
name=name, serving_size=serving_size, serving_unit=serving_unit,
calories=calories, protein=protein, carbs=carbs, fat=fat,
fiber=fiber, sugar=sugar, sodium=sodium, calcium=calcium
)
db.add(food)
db.commit()
return {"status": "success", "message": "Food added successfully"}
# Meals tab
@app.get("/meals", response_class=HTMLResponse)
async def meals_page(request: Request, db: Session = Depends(get_db)):
meals = db.query(Meal).all()
foods = db.query(Food).all()
return templates.TemplateResponse("meals.html",
{"request": request, "meals": meals, "foods": foods})
@app.post("/meals/add")
async def add_meal(request: Request, db: Session = Depends(get_db),
name: str = Form(...), meal_type: str = Form(...)):
meal = Meal(name=name, meal_type=meal_type)
db.add(meal)
db.commit()
db.refresh(meal)
return {"status": "success", "meal_id": meal.id}
try:
meal = Meal(name=name, meal_type=meal_type)
db.add(meal)
db.commit()
db.refresh(meal)
return {"status": "success", "meal_id": meal.id}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.post("/meals/edit")
async def edit_meal(request: Request, db: Session = Depends(get_db),
meal_id: int = Form(...), name: str = Form(...),
meal_type: str = Form(...)):
try:
meal = db.query(Meal).filter(Meal.id == meal_id).first()
if not meal:
return {"status": "error", "message": "Meal not found"}
meal.name = name
meal.meal_type = meal_type
db.commit()
return {"status": "success", "message": "Meal updated successfully"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.get("/meals/{meal_id}/foods")
async def get_meal_foods(meal_id: int, db: Session = Depends(get_db)):
"""Get all foods in a meal"""
try:
meal_foods = db.query(MealFood).filter(MealFood.meal_id == meal_id).all()
result = []
for mf in meal_foods:
result.append({
"id": mf.id,
"food_id": mf.food_id,
"food_name": mf.food.name,
"quantity": mf.quantity
})
return result
except Exception as e:
return {"status": "error", "message": str(e)}
@app.post("/meals/{meal_id}/add_food")
async def add_food_to_meal(meal_id: int, food_id: int = Form(...),
quantity: float = Form(...), db: Session = Depends(get_db)):
meal_food = MealFood(meal_id=meal_id, food_id=food_id, quantity=quantity)
db.add(meal_food)
db.commit()
return {"status": "success"}
try:
meal_food = MealFood(meal_id=meal_id, food_id=food_id, quantity=quantity)
db.add(meal_food)
db.commit()
return {"status": "success"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.delete("/meals/remove_food/{meal_food_id}")
async def remove_food_from_meal(meal_food_id: int, db: Session = Depends(get_db)):
"""Remove a food from a meal"""
try:
meal_food = db.query(MealFood).filter(MealFood.id == meal_food_id).first()
if not meal_food:
return {"status": "error", "message": "Meal food not found"}
db.delete(meal_food)
db.commit()
return {"status": "success"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.post("/meals/delete")
async def delete_meals(meal_ids: dict = Body(...), db: Session = Depends(get_db)):
@@ -390,52 +507,110 @@ async def delete_meals(meal_ids: dict = Body(...), db: Session = Depends(get_db)
# Plan tab
@app.get("/plan", response_class=HTMLResponse)
async def plan_page(request: Request, person: str = "Person A", db: Session = Depends(get_db)):
from datetime import date, timedelta
# Create 14 days (Day1 through Day14)
days = [f"Day{i}" for i in range(1, 15)]
# Get 2 weeks starting from today
start_date = date.today()
dates = [start_date + timedelta(days=i) for i in range(14)]
# Get plans for the person
# Get plans for the person (using day names as dates)
plans = {}
for d in dates:
day_plans = db.query(Plan).filter(Plan.person == person, Plan.date == d).all()
plans[d] = day_plans
for day in days:
try:
day_plans = db.query(Plan).filter(Plan.person == person, Plan.date == day).all()
plans[day] = day_plans
except Exception as e:
print(f"Error loading plans for {day}: {e}")
plans[day] = []
# Calculate daily totals
daily_totals = {}
for d in dates:
daily_totals[d] = calculate_day_nutrition(plans[d], db)
for day in days:
daily_totals[day] = calculate_day_nutrition(plans[day], db)
meals = db.query(Meal).all()
return templates.TemplateResponse("plan.html", {
"request": request, "person": person, "dates": dates,
"request": request, "person": person, "days": days,
"plans": plans, "daily_totals": daily_totals, "meals": meals
})
@app.post("/plan/add")
async def add_to_plan(request: Request, person: str = Form(...),
plan_date: str = Form(...), meal_id: int = Form(...),
plan_day: str = Form(...), meal_id: int = Form(...),
db: Session = Depends(get_db)):
plan = Plan(person=person, date=datetime.strptime(plan_date, "%Y-%m-%d").date(), meal_id=meal_id)
db.add(plan)
db.commit()
return {"status": "success"}
try:
plan = Plan(person=person, date=plan_day, meal_id=meal_id)
db.add(plan)
db.commit()
return {"status": "success"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.get("/plan/{person}/{day}")
async def get_day_plan(person: str, day: str, db: Session = Depends(get_db)):
"""Get all meals for a specific day"""
try:
plans = db.query(Plan).filter(Plan.person == person, Plan.date == day).all()
result = []
for plan in plans:
result.append({
"id": plan.id,
"meal_id": plan.meal_id,
"meal_name": plan.meal.name,
"meal_type": plan.meal.meal_type
})
return result
except Exception as e:
return {"status": "error", "message": str(e)}
@app.post("/plan/update_day")
async def update_day_plan(request: Request, person: str = Form(...),
day: str = Form(...), meal_ids: str = Form(...),
db: Session = Depends(get_db)):
"""Replace all meals for a specific day"""
try:
# Parse meal_ids (comma-separated string)
meal_id_list = [int(x.strip()) for x in meal_ids.split(',') if x.strip()]
# Delete existing plans for this day
db.query(Plan).filter(Plan.person == person, Plan.date == day).delete()
# Add new plans
for meal_id in meal_id_list:
plan = Plan(person=person, date=day, meal_id=meal_id)
db.add(plan)
db.commit()
return {"status": "success"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.delete("/plan/{plan_id}")
async def remove_from_plan(plan_id: int, db: Session = Depends(get_db)):
"""Remove a specific meal from a plan"""
try:
plan = db.query(Plan).filter(Plan.id == plan_id).first()
if not plan:
return {"status": "error", "message": "Plan not found"}
db.delete(plan)
db.commit()
return {"status": "success"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
# Detailed planner tab
@app.get("/detailed", response_class=HTMLResponse)
async def detailed_page(request: Request, person: str = "Person A",
plan_date: str = None, db: Session = Depends(get_db)):
plan_day: str = None, db: Session = Depends(get_db)):
if not plan_date:
plan_date = date.today().strftime("%Y-%m-%d")
selected_date = datetime.strptime(plan_date, "%Y-%m-%d").date()
if not plan_day:
plan_day = "Day1"
# Get all plans for the selected day
plans = db.query(Plan).filter(Plan.person == person, Plan.date == selected_date).all()
plans = db.query(Plan).filter(Plan.person == person, Plan.date == plan_day).all()
# Group by meal type and calculate nutrition
meal_details = []
@@ -450,9 +625,12 @@ async def detailed_page(request: Request, person: str = "Person A",
# Calculate day totals
day_totals = calculate_day_nutrition(plans, db)
# Create list of all days for the selector
days = [f"Day{i}" for i in range(1, 15)]
return templates.TemplateResponse("detailed.html", {
"request": request, "person": person, "selected_date": selected_date,
"meal_details": meal_details, "day_totals": day_totals
"request": request, "person": person, "selected_day": plan_day,
"meal_details": meal_details, "day_totals": day_totals, "days": days
})
if __name__ == "__main__":

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meal Planner</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" rel="stylesheet">
<style>
.nav-tabs .nav-link.active {
background-color: #0d6efd;
@@ -28,6 +29,18 @@
top: 10px;
right: 20px;
}
.modal-lg {
max-width: 800px;
}
.btn i {
margin-right: 4px;
}
.table th, .table td {
vertical-align: middle;
}
.badge {
font-size: 0.8em;
}
</style>
</head>
<body>
@@ -39,20 +52,30 @@
</select>
</div>
<h1 class="mt-3 mb-4">Meal Planner</h1>
<h1 class="mt-3 mb-4">
<i class="bi bi-calendar-check"></i> Meal Planner
</h1>
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link" onclick="location.href='/foods'">Foods</button>
<button class="nav-link" onclick="location.href='/foods'">
<i class="bi bi-apple"></i> Foods
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" onclick="location.href='/meals'">Meals</button>
<button class="nav-link" onclick="location.href='/meals'">
<i class="bi bi-egg-fried"></i> Meals
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" onclick="location.href='/plan'">Plan A</button>
<button class="nav-link" onclick="location.href='/plan'">
<i class="bi bi-calendar-week"></i> Plan
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" onclick="location.href='/detailed'">Detailed Planner</button>
<button class="nav-link" onclick="location.href='/detailed'">
<i class="bi bi-calendar-day"></i> Detailed
</button>
</li>
</ul>
@@ -69,6 +92,21 @@
currentUrl.searchParams.set('person', person);
window.location.href = currentUrl.toString();
}
// Set active tab based on current URL
document.addEventListener('DOMContentLoaded', function() {
const currentPath = window.location.pathname;
const tabs = document.querySelectorAll('.nav-link');
tabs.forEach(tab => {
const href = tab.getAttribute('onclick');
if (href && href.includes(currentPath)) {
tab.classList.add('active');
} else {
tab.classList.remove('active');
}
});
});
</script>
</body>
</html>
</html>

View File

@@ -1,64 +1,204 @@
<!-- templates/detailed.html -->
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="row mb-3">
<div class="col-md-6">
<h3>Detailed View for {{ person }}</h3>
<form method="get">
<form method="get" class="d-flex">
<input type="hidden" name="person" value="{{ person }}">
<div class="input-group mb-3">
<input type="date" class="form-control" name="plan_date" value="{{ selected_date }}">
<button class="btn btn-primary" type="submit">View Date</button>
</div>
<select class="form-control me-2" name="plan_day">
{% for day in days %}
<option value="{{ day }}" {% if day == selected_day %}selected{% endif %}>{{ day }}</option>
{% endfor %}
</select>
<button class="btn btn-primary" type="submit">
<i class="bi bi-search"></i> View
</button>
</form>
</div>
<div class="col-md-6 text-end">
<h4 class="mb-0">{{ selected_day }}</h4>
<small class="text-muted">Detailed nutritional breakdown</small>
</div>
</div>
<h4>{{ selected_date.strftime('%A, %B %d, %Y') }}</h4>
<style>
.meal-card {
margin-bottom: 1.5rem;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
overflow: hidden;
}
.meal-header {
background: linear-gradient(135deg, #6c757d 0%, #495057 100%);
color: white;
padding: 0.75rem 1rem;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.meal-table {
margin-bottom: 0;
font-size: 0.9em;
}
.meal-table th {
background-color: #f8f9fa;
border-top: none;
font-size: 0.8em;
font-weight: 600;
padding: 0.5rem 0.75rem;
vertical-align: middle;
}
.meal-table td {
padding: 0.5rem 0.75rem;
vertical-align: middle;
border-top: 1px solid #f1f3f4;
}
.meal-table tbody tr:hover {
background-color: #f8f9fa;
}
.food-name {
font-weight: 500;
color: #495057;
}
.serving-info {
font-size: 0.8em;
color: #6c757d;
}
.nutrient-value {
font-weight: 500;
text-align: center;
}
.meal-totals {
background-color: #e3f2fd;
border-top: 2px solid #1976d2;
}
.meal-totals td {
font-weight: 600;
background-color: #e3f2fd;
}
.day-totals {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
padding: 1rem;
border-radius: 0.5rem;
margin-top: 1.5rem;
}
.day-totals-table {
color: white;
margin-bottom: 0;
}
.day-totals-table th,
.day-totals-table td {
border-color: rgba(255, 255, 255, 0.3);
text-align: center;
font-weight: 600;
}
.day-totals-table th {
background-color: rgba(255, 255, 255, 0.1);
}
.macro-pct {
font-size: 0.8em;
opacity: 0.9;
}
</style>
{% for meal_detail in meal_details %}
<div class="card mb-3">
<div class="card-header">
<strong>{{ meal_detail.plan.meal.name }}</strong> - {{ meal_detail.plan.meal.meal_type.title() }}
<div class="meal-card">
<div class="meal-header">
<span>
<i class="bi bi-egg-fried"></i> {{ meal_detail.plan.meal.name }} - {{ meal_detail.plan.meal.meal_type.title() }}
</span>
<span class="badge bg-light text-dark">{{ "%.0f"|format(meal_detail.nutrition.calories) }} cal</span>
</div>
<div class="card-body">
<h6>Foods:</h6>
<ul>
<table class="table meal-table">
<thead>
<tr>
<th style="width: 35%;">Food Item</th>
<th style="width: 15%;">Serving</th>
<th style="width: 8%;">Cal</th>
<th style="width: 8%;">Protein</th>
<th style="width: 8%;">Carbs</th>
<th style="width: 8%;">Fat</th>
<th style="width: 8%;">Fiber</th>
<th style="width: 10%;">Sodium</th>
</tr>
</thead>
<tbody>
{% for meal_food in meal_detail.foods %}
<li>{{ meal_food.quantity }} × {{ meal_food.food.name }}
({{ meal_food.food.serving_size }} {{ meal_food.food.serving_unit }})
</li>
<tr>
<td>
<div class="food-name">{{ meal_food.food.name }}</div>
</td>
<td>
<div class="serving-info">
{{ meal_food.quantity }} × {{ meal_food.food.serving_size }}{{ meal_food.food.serving_unit }}
</div>
</td>
<td class="nutrient-value">{{ "%.0f"|format(meal_food.food.calories * meal_food.quantity) }}</td>
<td class="nutrient-value">{{ "%.1f"|format(meal_food.food.protein * meal_food.quantity) }}g</td>
<td class="nutrient-value">{{ "%.1f"|format(meal_food.food.carbs * meal_food.quantity) }}g</td>
<td class="nutrient-value">{{ "%.1f"|format(meal_food.food.fat * meal_food.quantity) }}g</td>
<td class="nutrient-value">{{ "%.1f"|format((meal_food.food.fiber or 0) * meal_food.quantity) }}g</td>
<td class="nutrient-value">{{ "%.0f"|format((meal_food.food.sodium or 0) * meal_food.quantity) }}mg</td>
</tr>
{% endfor %}
</ul>
<div class="nutrition-grid">
<div class="nutrition-item">
<strong>{{ "%.0f"|format(meal_detail.nutrition.calories) }}</strong><br>
<small>Calories</small>
<!-- Meal Totals Row -->
<tr class="meal-totals">
<td><strong><i class="bi bi-calculator"></i> Meal Totals</strong></td>
<td class="text-center"><small>-</small></td>
<td class="nutrient-value">{{ "%.0f"|format(meal_detail.nutrition.calories) }}</td>
<td class="nutrient-value">
{{ "%.1f"|format(meal_detail.nutrition.protein) }}g
<div class="macro-pct">({{ meal_detail.nutrition.protein_pct or 0 }}%)</div>
</td>
<td class="nutrient-value">
{{ "%.1f"|format(meal_detail.nutrition.carbs) }}g
<div class="macro-pct">({{ meal_detail.nutrition.carbs_pct or 0 }}%)</div>
</td>
<td class="nutrient-value">
{{ "%.1f"|format(meal_detail.nutrition.fat) }}g
<div class="macro-pct">({{ meal_detail.nutrition.fat_pct or 0 }}%)</div>
</td>
<td class="nutrient-value">{{ "%.1f"|format(meal_detail.nutrition.fiber) }}g</td>
<td class="nutrient-value">{{ "%.0f"|format(meal_detail.nutrition.sodium) }}mg</td>
</tr>
</tbody>
</table>
<!-- Additional Nutrients Row -->
<div class="px-3 py-2" style="background-color: #f8f9fa; border-top: 1px solid #dee2e6; font-size: 0.85em;">
<div class="row text-center">
<div class="col-3">
<strong>Net Carbs:</strong> {{ "%.1f"|format(meal_detail.nutrition.net_carbs or 0) }}g
</div>
<div class="nutrition-item">
<strong>{{ "%.1f"|format(meal_detail.nutrition.protein) }}g</strong><br>
<small>Protein ({{ meal_detail.nutrition.protein_pct or 0 }}%)</small>
<div class="col-3">
<strong>Sugar:</strong> {{ "%.1f"|format(meal_detail.nutrition.sugar) }}g
</div>
<div class="nutrition-item">
<strong>{{ "%.1f"|format(meal_detail.nutrition.carbs) }}g</strong><br>
<small>Carbs ({{ meal_detail.nutrition.carbs_pct or 0 }}%)</small>
<div class="col-3">
<strong>Calcium:</strong> {{ "%.0f"|format(meal_detail.nutrition.calcium) }}mg
</div>
<div class="nutrition-item">
<strong>{{ "%.1f"|format(meal_detail.nutrition.fat) }}g</strong><br>
<small>Fat ({{ meal_detail.nutrition.fat_pct or 0 }}%)</small>
</div>
<div class="nutrition-item">
<strong>{{ "%.1f"|format(meal_detail.nutrition.net_carbs or 0) }}g</strong><br>
<small>Net Carbs</small>
</div>
<div class="nutrition-item">
<strong>{{ "%.0f"|format(meal_detail.nutrition.calcium) }}mg</strong><br>
<small>Calcium</small>
</div>
<div class="nutrition-item">
<strong>{{ "%.0f"|format(meal_detail.nutrition.sodium) }}mg</strong><br>
<small>Sodium</small>
<div class="col-3">
<strong>Ratio:</strong>
<span class="text-muted">
{{ meal_detail.nutrition.protein_pct or 0 }}:{{ meal_detail.nutrition.carbs_pct or 0 }}:{{ meal_detail.nutrition.fat_pct or 0 }}
</span>
</div>
</div>
</div>
@@ -66,47 +206,60 @@
{% endfor %}
<!-- Day Totals -->
<div class="card bg-light">
<div class="card-header">
<strong>Daily Totals</strong>
</div>
<div class="card-body">
<div class="nutrition-grid">
<div class="nutrition-item">
<strong>{{ "%.0f"|format(day_totals.calories) }}</strong><br>
<small>Total Calories</small>
</div>
<div class="nutrition-item">
<strong>{{ "%.1f"|format(day_totals.protein) }}g</strong><br>
<small>Protein ({{ day_totals.protein_pct or 0 }}%)</small>
</div>
<div class="nutrition-item">
<strong>{{ "%.1f"|format(day_totals.carbs) }}g</strong><br>
<small>Carbs ({{ day_totals.carbs_pct or 0 }}%)</small>
</div>
<div class="nutrition-item">
<strong>{{ "%.1f"|format(day_totals.fat) }}g</strong><br>
<small>Fat ({{ day_totals.fat_pct or 0 }}%)</small>
</div>
<div class="nutrition-item">
<strong>{{ "%.1f"|format(day_totals.net_carbs or 0) }}g</strong><br>
<small>Net Carbs</small>
</div>
<div class="nutrition-item">
<strong>{{ "%.0f"|format(day_totals.calcium) }}mg</strong><br>
<small>Total Calcium</small>
</div>
<div class="nutrition-item">
<strong>{{ "%.0f"|format(day_totals.sodium) }}mg</strong><br>
<small>Total Sodium</small>
</div>
</div>
<div class="day-totals">
<h5 class="mb-3 text-center">
<i class="bi bi-calendar-check"></i> Daily Totals - {{ "%.0f"|format(day_totals.calories) }} Total Calories
</h5>
<table class="table day-totals-table">
<thead>
<tr>
<th>Calories</th>
<th>Protein</th>
<th>Carbs</th>
<th>Fat</th>
<th>Fiber</th>
<th>Net Carbs</th>
<th>Sodium</th>
<th>Calcium</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div style="font-size: 1.2em;">{{ "%.0f"|format(day_totals.calories) }}</div>
</td>
<td>
<div style="font-size: 1.1em;">{{ "%.1f"|format(day_totals.protein) }}g</div>
<div class="macro-pct">({{ day_totals.protein_pct or 0 }}%)</div>
</td>
<td>
<div style="font-size: 1.1em;">{{ "%.1f"|format(day_totals.carbs) }}g</div>
<div class="macro-pct">({{ day_totals.carbs_pct or 0 }}%)</div>
</td>
<td>
<div style="font-size: 1.1em;">{{ "%.1f"|format(day_totals.fat) }}g</div>
<div class="macro-pct">({{ day_totals.fat_pct or 0 }}%)</div>
</td>
<td>{{ "%.1f"|format(day_totals.fiber) }}g</td>
<td>{{ "%.1f"|format(day_totals.net_carbs or 0) }}g</td>
<td>{{ "%.0f"|format(day_totals.sodium) }}mg</td>
<td>{{ "%.0f"|format(day_totals.calcium) }}mg</td>
</tr>
</tbody>
</table>
<div class="text-center mt-2">
<small>
<strong>Daily Macro Ratio:</strong>
{{ day_totals.protein_pct or 0 }}% Protein : {{ day_totals.carbs_pct or 0 }}% Carbs : {{ day_totals.fat_pct or 0 }}% Fat
</small>
</div>
</div>
{% if not meal_details %}
<div class="alert alert-info">
No meals planned for this date. Go to the Plan A tab to add meals.
<i class="bi bi-info-circle"></i> No meals planned for this date. Go to the Plan tab to add meals.
</div>
{% endif %}

View File

@@ -11,64 +11,9 @@
<button type="submit" class="btn btn-secondary mb-4">Upload CSV</button>
</form>
<h3>Add New Food</h3>
<form action="/foods/add" method="post">
<div class="mb-3">
<label class="form-label">Name</label>
<input type="text" class="form-control" name="name" required>
</div>
<div class="row">
<div class="col-6">
<label class="form-label">Serving Size</label>
<input type="text" class="form-control" name="serving_size" required>
</div>
<div class="col-6">
<label class="form-label">Unit</label>
<input type="text" class="form-control" name="serving_unit" required>
</div>
</div>
<div class="row mt-3">
<div class="col-6">
<label class="form-label">Calories</label>
<input type="number" step="0.1" class="form-control" name="calories" required>
</div>
<div class="col-6">
<label class="form-label">Protein (g)</label>
<input type="number" step="0.1" class="form-control" name="protein" required>
</div>
</div>
<div class="row mt-3">
<div class="col-6">
<label class="form-label">Carbs (g)</label>
<input type="number" step="0.1" class="form-control" name="carbs" required>
</div>
<div class="col-6">
<label class="form-label">Fat (g)</label>
<input type="number" step="0.1" class="form-control" name="fat" required>
</div>
</div>
<div class="row mt-3">
<div class="col-6">
<label class="form-label">Fiber (g)</label>
<input type="number" step="0.1" class="form-control" name="fiber" value="0">
</div>
<div class="col-6">
<label class="form-label">Sugar (g)</label>
<input type="number" step="0.1" class="form-control" name="sugar" value="0">
</div>
</div>
<div class="row mt-3">
<div class="col-6">
<label class="form-label">Sodium (mg)</label>
<input type="number" step="0.1" class="form-control" name="sodium" value="0">
</div>
<div class="col-6">
<label class="form-label">Calcium (mg)</label>
<input type="number" step="0.1" class="form-control" name="calcium" value="0">
</div>
</div>
<button type="submit" class="btn btn-primary mt-3">Add Food</button>
</form>
<button type="button" class="btn btn-primary btn-lg" data-bs-toggle="modal" data-bs-target="#addFoodModal">
<i class="bi bi-plus-circle"></i> Add New Food
</button>
</div>
<div class="col-md-8" id="upload-results" style="display: none;">
@@ -86,6 +31,7 @@
<table class="table table-striped">
<thead>
<tr>
<th>Select</th>
<th>Name</th>
<th>Serving</th>
<th>Cal</th>
@@ -95,6 +41,7 @@
<th>Fiber</th>
<th>Sodium</th>
<th>Calcium</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@@ -110,15 +57,252 @@
<td>{{ "%.2f"|format(food.fiber) }}g</td>
<td>{{ "%.2f"|format(food.sodium) }}mg</td>
<td>{{ "%.2f"|format(food.calcium) }}mg</td>
<td>
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="editFood({{ food.id }}, '{{ food.name }}', '{{ food.serving_size }}', '{{ food.serving_unit }}', {{ food.calories }}, {{ food.protein }}, {{ food.carbs }}, {{ food.fat }}, {{ food.fiber }}, {{ food.sugar }}, {{ food.sodium }}, {{ food.calcium }})">
<i class="bi bi-pencil"></i> Edit
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<button class="btn btn-danger mt-3" onclick="deleteSelectedFoods()">
<i class="bi bi-trash"></i> Delete Selected Foods
</button>
</div>
</div>
<!-- Add Food Modal -->
<div class="modal fade" id="addFoodModal" tabindex="-1" aria-labelledby="addFoodModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addFoodModalLabel">Add New Food</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="addFoodForm" action="/foods/add" method="post">
<div class="row">
<div class="col-12 mb-3">
<label class="form-label">Name</label>
<input type="text" class="form-control" name="name" required>
</div>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label">Serving Size</label>
<input type="text" class="form-control" name="serving_size" required>
</div>
<div class="col-6 mb-3">
<label class="form-label">Unit</label>
<input type="text" class="form-control" name="serving_unit" required>
</div>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label">Calories</label>
<input type="number" step="0.1" class="form-control" name="calories" required>
</div>
<div class="col-6 mb-3">
<label class="form-label">Protein (g)</label>
<input type="number" step="0.1" class="form-control" name="protein" required>
</div>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label">Carbs (g)</label>
<input type="number" step="0.1" class="form-control" name="carbs" required>
</div>
<div class="col-6 mb-3">
<label class="form-label">Fat (g)</label>
<input type="number" step="0.1" class="form-control" name="fat" required>
</div>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label">Fiber (g)</label>
<input type="number" step="0.1" class="form-control" name="fiber" value="0">
</div>
<div class="col-6 mb-3">
<label class="form-label">Sugar (g)</label>
<input type="number" step="0.1" class="form-control" name="sugar" value="0">
</div>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label">Sodium (mg)</label>
<input type="number" step="0.1" class="form-control" name="sodium" value="0">
</div>
<div class="col-6 mb-3">
<label class="form-label">Calcium (mg)</label>
<input type="number" step="0.1" class="form-control" name="calcium" value="0">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="submitFoodForm('add')">Add Food</button>
</div>
</div>
</div>
</div>
<!-- Edit Food Modal -->
<div class="modal fade" id="editFoodModal" tabindex="-1" aria-labelledby="editFoodModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editFoodModalLabel">Edit Food</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="editFoodForm" action="/foods/edit" method="post">
<input type="hidden" name="food_id" id="edit_food_id">
<div class="row">
<div class="col-12 mb-3">
<label class="form-label">Name</label>
<input type="text" class="form-control" name="name" id="edit_name" required>
</div>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label">Serving Size</label>
<input type="text" class="form-control" name="serving_size" id="edit_serving_size" required>
</div>
<div class="col-6 mb-3">
<label class="form-label">Unit</label>
<input type="text" class="form-control" name="serving_unit" id="edit_serving_unit" required>
</div>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label">Calories</label>
<input type="number" step="0.1" class="form-control" name="calories" id="edit_calories" required>
</div>
<div class="col-6 mb-3">
<label class="form-label">Protein (g)</label>
<input type="number" step="0.1" class="form-control" name="protein" id="edit_protein" required>
</div>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label">Carbs (g)</label>
<input type="number" step="0.1" class="form-control" name="carbs" id="edit_carbs" required>
</div>
<div class="col-6 mb-3">
<label class="form-label">Fat (g)</label>
<input type="number" step="0.1" class="form-control" name="fat" id="edit_fat" required>
</div>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label">Fiber (g)</label>
<input type="number" step="0.1" class="form-control" name="fiber" id="edit_fiber">
</div>
<div class="col-6 mb-3">
<label class="form-label">Sugar (g)</label>
<input type="number" step="0.1" class="form-control" name="sugar" id="edit_sugar">
</div>
</div>
<div class="row">
<div class="col-6 mb-3">
<label class="form-label">Sodium (mg)</label>
<input type="number" step="0.1" class="form-control" name="sodium" id="edit_sodium">
</div>
<div class="col-6 mb-3">
<label class="form-label">Calcium (mg)</label>
<input type="number" step="0.1" class="form-control" name="calcium" id="edit_calcium">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="submitFoodForm('edit')">Update Food</button>
</div>
</div>
</div>
</div>
<script>
// Edit food function
function editFood(id, name, serving_size, serving_unit, calories, protein, carbs, fat, fiber, sugar, sodium, calcium) {
document.getElementById('edit_food_id').value = id;
document.getElementById('edit_name').value = name;
document.getElementById('edit_serving_size').value = serving_size;
document.getElementById('edit_serving_unit').value = serving_unit;
document.getElementById('edit_calories').value = calories;
document.getElementById('edit_protein').value = protein;
document.getElementById('edit_carbs').value = carbs;
document.getElementById('edit_fat').value = fat;
document.getElementById('edit_fiber').value = fiber;
document.getElementById('edit_sugar').value = sugar;
document.getElementById('edit_sodium').value = sodium;
document.getElementById('edit_calcium').value = calcium;
new bootstrap.Modal(document.getElementById('editFoodModal')).show();
}
// Submit food form (add or edit)
async function submitFoodForm(action) {
const form = document.getElementById(action + 'FoodForm');
const formData = new FormData(form);
try {
const response = await fetch(form.action, {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
// Close modal and reload page
bootstrap.Modal.getInstance(document.getElementById(action + 'FoodModal')).hide();
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error submitting form: ' + error.message);
}
}
// Delete selected foods
async function deleteSelectedFoods() {
const selected = Array.from(document.querySelectorAll('input[name="selected_foods"]:checked'))
.map(checkbox => checkbox.value);
if (selected.length === 0) {
alert('Please select foods to delete');
return;
}
if (confirm(`Delete ${selected.length} selected foods?`)) {
try {
const response = await fetch('/foods/delete', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({food_ids: selected})
});
const result = await response.json();
if (response.ok) {
window.location.reload();
} else {
alert(`Delete failed: ${result.message || response.status}`);
}
} catch (error) {
alert('Delete failed: ' + error.message);
}
}
}
// CSV upload handling
document.querySelector('form[action="/foods/upload"]').addEventListener('submit', async (e) => {
e.preventDefault();
const form = e.target;
@@ -139,7 +323,6 @@ document.querySelector('form[action="/foods/upload"]').addEventListener('submit'
const results = await response.json();
// Always show results div even if no changes
resultsDiv.style.display = 'block';
document.getElementById('created-count').textContent = results.created || 0;
document.getElementById('updated-count').textContent = results.updated || 0;

View File

@@ -1,4 +1,3 @@
<!-- templates/meals.html -->
{% extends "base.html" %}
{% block content %}
<div class="row">
@@ -12,52 +11,9 @@
<button type="submit" class="btn btn-secondary mb-4">Upload CSV</button>
</form>
<h3>Create New Meal</h3>
<form action="/meals/add" method="post">
<div class="mb-3">
<label class="form-label">Meal Name</label>
<input type="text" class="form-control" name="name" required>
</div>
<div class="mb-3">
<label class="form-label">Meal Type</label>
<select class="form-control" name="meal_type" required>
<option value="breakfast">Breakfast</option>
<option value="lunch">Lunch</option>
<option value="dinner">Dinner</option>
<option value="snack">Snack</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Create Meal</button>
</form>
<div class="mt-4">
<h4>Add Food to Meal</h4>
<form id="addFoodForm">
<div class="mb-3">
<label class="form-label">Select Meal</label>
<select class="form-control" id="mealSelect">
<option value="">Choose meal...</option>
{% for meal in meals %}
<option value="{{ meal.id }}">{{ meal.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label">Select Food</label>
<select class="form-control" id="foodSelect">
<option value="">Choose food...</option>
{% for food in foods %}
<option value="{{ food.id }}">{{ food.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label">Quantity</label>
<input type="number" step="0.1" class="form-control" id="quantity" value="1">
</div>
<button type="button" onclick="addFoodToMeal()" class="btn btn-success">Add Food</button>
</form>
</div>
<button type="button" class="btn btn-primary btn-lg" data-bs-toggle="modal" data-bs-target="#addMealModal">
<i class="bi bi-plus-circle"></i> Create New Meal
</button>
</div>
<div class="col-md-8">
@@ -70,6 +26,7 @@
<th>Name</th>
<th>Type</th>
<th>Food Items</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@@ -89,26 +46,267 @@
<em>No foods added</em>
{% endif %}
</td>
<td>
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="editMeal({{ meal.id }}, '{{ meal.name }}', '{{ meal.meal_type }}')">
<i class="bi bi-pencil"></i> Edit
</button>
<button type="button" class="btn btn-sm btn-outline-success"
onclick="manageMealFoods({{ meal.id }}, '{{ meal.name }}')">
<i class="bi bi-list"></i> Foods
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<button class="btn btn-danger mt-3" onclick="deleteSelectedMeals()" style="margin-top: 20px !important;">
<button class="btn btn-danger mt-3" onclick="deleteSelectedMeals()">
<i class="bi bi-trash"></i> Delete Selected Meals
</button>
100| </div>
</div>
</div>
<!-- Add Meal Modal -->
<div class="modal fade" id="addMealModal" tabindex="-1" aria-labelledby="addMealModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addMealModalLabel">Create New Meal</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="addMealForm" action="/meals/add" method="post">
<div class="mb-3">
<label class="form-label">Meal Name</label>
<input type="text" class="form-control" name="name" required>
</div>
<div class="mb-3">
<label class="form-label">Meal Type</label>
<select class="form-control" name="meal_type" required>
<option value="breakfast">Breakfast</option>
<option value="lunch">Lunch</option>
<option value="dinner">Dinner</option>
<option value="snack">Snack</option>
<option value="custom">Custom</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="submitMealForm('add')">Create Meal</button>
</div>
</div>
</div>
</div>
<!-- Edit Meal Modal -->
<div class="modal fade" id="editMealModal" tabindex="-1" aria-labelledby="editMealModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editMealModalLabel">Edit Meal</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="editMealForm" action="/meals/edit" method="post">
<input type="hidden" name="meal_id" id="edit_meal_id">
<div class="mb-3">
<label class="form-label">Meal Name</label>
<input type="text" class="form-control" name="name" id="edit_meal_name" required>
</div>
<div class="mb-3">
<label class="form-label">Meal Type</label>
<select class="form-control" name="meal_type" id="edit_meal_type" required>
<option value="breakfast">Breakfast</option>
<option value="lunch">Lunch</option>
<option value="dinner">Dinner</option>
<option value="snack">Snack</option>
<option value="custom">Custom</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="submitMealForm('edit')">Update Meal</button>
</div>
</div>
</div>
</div>
<!-- Manage Meal Foods Modal -->
<div class="modal fade" id="manageMealFoodsModal" tabindex="-1" aria-labelledby="manageMealFoodsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="manageMealFoodsModalLabel">Manage Foods for Meal</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<h6>Add Food to Meal</h6>
<form id="addFoodToMealForm">
<input type="hidden" id="meal_id_for_food" name="meal_id">
<div class="mb-3">
<label class="form-label">Select Food</label>
<select class="form-control" id="foodSelect" name="food_id" required>
<option value="">Choose food...</option>
{% for food in foods %}
<option value="{{ food.id }}">{{ food.name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label">Quantity</label>
<input type="number" step="0.01" class="form-control" name="quantity" value="1" required>
</div>
<button type="button" class="btn btn-success" onclick="addFoodToMeal()">Add Food</button>
</form>
</div>
<div class="col-md-6">
<h6>Current Foods</h6>
<div id="currentMealFoods">
<!-- This will be populated dynamically -->
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
function toggleAllMeals(source) {
console.log('Toggling all meals');
const checkboxes = document.querySelectorAll('input[name="selected_meals"]');
checkboxes.forEach(checkbox => checkbox.checked = source.checked);
}
// Edit meal function
function editMeal(id, name, meal_type) {
document.getElementById('edit_meal_id').value = id;
document.getElementById('edit_meal_name').value = name;
document.getElementById('edit_meal_type').value = meal_type;
new bootstrap.Modal(document.getElementById('editMealModal')).show();
}
// Manage meal foods function
async function manageMealFoods(mealId, mealName) {
document.getElementById('meal_id_for_food').value = mealId;
document.getElementById('manageMealFoodsModalLabel').textContent = `Manage Foods for: ${mealName}`;
// Load current meal foods
await loadCurrentMealFoods(mealId);
new bootstrap.Modal(document.getElementById('manageMealFoodsModal')).show();
}
// Load current meal foods
async function loadCurrentMealFoods(mealId) {
try {
const response = await fetch(`/meals/${mealId}/foods`);
const foods = await response.json();
const container = document.getElementById('currentMealFoods');
if (foods.length === 0) {
container.innerHTML = '<em>No foods added yet</em>';
} else {
container.innerHTML = foods.map(mf => `
<div class="d-flex justify-content-between align-items-center mb-2 p-2 bg-light rounded">
<span>${mf.quantity} × ${mf.food_name}</span>
<button class="btn btn-sm btn-outline-danger" onclick="removeFoodFromMeal(${mf.id})">
<i class="bi bi-trash"></i>
</button>
</div>
`).join('');
}
} catch (error) {
console.error('Error loading meal foods:', error);
}
}
// Add food to meal
async function addFoodToMeal() {
const form = document.getElementById('addFoodToMealForm');
const formData = new FormData(form);
const mealId = formData.get('meal_id');
try {
const response = await fetch(`/meals/${mealId}/add_food`, {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
// Reset form and reload current foods
form.reset();
document.getElementById('meal_id_for_food').value = mealId;
await loadCurrentMealFoods(mealId);
} else {
alert('Error adding food: ' + result.message);
}
} catch (error) {
alert('Error adding food: ' + error.message);
}
}
// Remove food from meal
async function removeFoodFromMeal(mealFoodId) {
if (confirm('Remove this food from the meal?')) {
try {
const response = await fetch(`/meals/remove_food/${mealFoodId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.status === 'success') {
const mealId = document.getElementById('meal_id_for_food').value;
await loadCurrentMealFoods(mealId);
} else {
alert('Error removing food: ' + result.message);
}
} catch (error) {
alert('Error removing food: ' + error.message);
}
}
}
// Submit meal form (add or edit)
async function submitMealForm(action) {
const form = document.getElementById(action + 'MealForm');
const formData = new FormData(form);
try {
const response = await fetch(form.action, {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
// Close modal and reload page
bootstrap.Modal.getInstance(document.getElementById(action + 'MealModal')).hide();
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error submitting form: ' + error.message);
}
}
// Delete selected meals
async function deleteSelectedMeals() {
console.log('Delete selected meals called');
const selected = Array.from(document.querySelectorAll('input[name="selected_meals"]:checked'))
.map(checkbox => checkbox.value);
@@ -119,7 +317,6 @@ async function deleteSelectedMeals() {
if (confirm(`Delete ${selected.length} selected meals?`)) {
try {
console.log('Deleting meals:', selected);
const response = await fetch('/meals/delete', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
@@ -127,7 +324,6 @@ async function deleteSelectedMeals() {
});
const result = await response.json();
console.log('Delete response:', result);
if (response.ok) {
window.location.reload();
@@ -135,7 +331,6 @@ async function deleteSelectedMeals() {
alert(`Delete failed: ${result.message || response.status}`);
}
} catch (error) {
console.error('Delete failed:', error);
alert('Delete failed: ' + error.message);
}
}
@@ -183,4 +378,4 @@ document.querySelector('form[action="/meals/upload"]').addEventListener('submit'
}
});
</script>
{% endblock %}
{% endblock %}

View File

@@ -6,36 +6,40 @@
<table class="table table-bordered">
<thead>
<tr>
<th>Date</th>
<th>Meals</th>
<th>Calories</th>
<th>Protein</th>
<th>Carbs</th>
<th>Fat</th>
<th>Net Carbs</th>
<th>Calcium</th>
<th>Sodium</th>
<th>Actions</th>
<th style="width: 10%;">Day</th>
<th style="width: 25%;">Meals</th>
<th style="width: 10%;">Calories</th>
<th style="width: 12%;">Protein</th>
<th style="width: 12%;">Carbs</th>
<th style="width: 12%;">Fat</th>
<th style="width: 9%;">Net Carbs</th>
<th style="width: 10%;">Actions</th>
</tr>
</thead>
<tbody>
{% for date in dates %}
{% for day in days %}
<tr>
<td>{{ date.strftime('%m/%d') }}</td>
<td><strong>{{ day }}</strong></td>
<td>
{% for plan in plans[date] %}
{% for plan in plans[day] %}
<span class="badge bg-secondary me-1">{{ plan.meal.name }}</span>
{% endfor %}
{% if not plans[day] %}
<em class="text-muted">No meals</em>
{% endif %}
</td>
<td>{{ "%.0f"|format(daily_totals[date].calories or 0) }}</td>
<td>{{ "%.1f"|format(daily_totals[date].protein or 0) }}g ({{ daily_totals[date].protein_pct or 0 }}%)</td>
<td>{{ "%.1f"|format(daily_totals[date].carbs or 0) }}g ({{ daily_totals[date].carbs_pct or 0 }}%)</td>
<td>{{ "%.1f"|format(daily_totals[date].fat or 0) }}g ({{ daily_totals[date].fat_pct or 0 }}%)</td>
<td>{{ "%.1f"|format(daily_totals[date].net_carbs or 0) }}g</td>
<td>{{ "%.0f"|format(daily_totals[date].calcium or 0) }}mg</td>
<td>{{ "%.0f"|format(daily_totals[date].sodium or 0) }}mg</td>
<td>{{ "%.0f"|format(daily_totals[day].calories or 0) }}</td>
<td>{{ "%.1f"|format(daily_totals[day].protein or 0) }}g<br><small class="text-muted">({{ daily_totals[day].protein_pct or 0 }}%)</small></td>
<td>{{ "%.1f"|format(daily_totals[day].carbs or 0) }}g<br><small class="text-muted">({{ daily_totals[day].carbs_pct or 0 }}%)</small></td>
<td>{{ "%.1f"|format(daily_totals[day].fat or 0) }}g<br><small class="text-muted">({{ daily_totals[day].fat_pct or 0 }}%)</small></td>
<td>{{ "%.1f"|format(daily_totals[day].net_carbs or 0) }}g</td>
<td>
<button class="btn btn-sm btn-primary" onclick="addMealToDay('{{ date }}')">Add Meal</button>
<button class="btn btn-sm btn-primary" onclick="editDay('{{ day }}', '{{ person }}')">
<i class="bi bi-pencil"></i> Edit
</button>
<button class="btn btn-sm btn-outline-success" onclick="quickAddMeal('{{ day }}')">
<i class="bi bi-plus"></i> Add
</button>
</td>
</tr>
{% endfor %}
@@ -43,17 +47,17 @@
</table>
</div>
<!-- Add Meal Modal -->
<div class="modal fade" id="addMealModal" tabindex="-1">
<!-- Quick Add Meal Modal -->
<div class="modal fade" id="quickAddMealModal" tabindex="-1" aria-labelledby="quickAddMealModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add Meal to Plan</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
<h5 class="modal-title" id="quickAddMealModalLabel">Add Meal to <span id="quickAddDay"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="addMealForm">
<input type="hidden" id="planDate" name="plan_date">
<form id="quickAddMealForm">
<input type="hidden" id="quickAddPlanDay" name="plan_day">
<input type="hidden" name="person" value="{{ person }}">
<div class="mb-3">
<label class="form-label">Select Meal</label>
@@ -68,20 +72,73 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="submitMealToPlan()">Add Meal</button>
<button type="button" class="btn btn-primary" onclick="submitQuickAddMeal()">Add Meal</button>
</div>
</div>
</div>
</div>
<!-- Edit Day Modal -->
<div class="modal fade" id="editDayModal" tabindex="-1" aria-labelledby="editDayModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editDayModalLabel">Edit Meals for <span id="editDayName"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<h6><i class="bi bi-list"></i> Current Meals</h6>
<div id="currentDayMeals" class="mb-3">
<!-- This will be populated dynamically -->
</div>
<h6><i class="bi bi-plus-circle"></i> Add Meal</h6>
<form id="addMealToDayForm">
<input type="hidden" id="editDayPerson" name="person">
<input type="hidden" id="editDayValue" name="plan_day">
<div class="mb-3">
<select class="form-control" id="mealSelectForDay" name="meal_id" required>
<option value="">Choose meal...</option>
{% for meal in meals %}
<option value="{{ meal.id }}">{{ meal.name }} ({{ meal.meal_type }})</option>
{% endfor %}
</select>
</div>
<button type="button" class="btn btn-success btn-sm" onclick="addMealToCurrentDay()">
<i class="bi bi-plus"></i> Add Selected Meal
</button>
</form>
</div>
<div class="col-md-6">
<h6><i class="bi bi-calculator"></i> Day Preview</h6>
<div id="dayNutritionPreview" class="p-3 bg-light rounded">
<!-- This will show live nutrition totals -->
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
function addMealToDay(date) {
document.getElementById('planDate').value = date;
new bootstrap.Modal(document.getElementById('addMealModal')).show();
let currentEditingDay = null;
let currentEditingPerson = null;
// Quick add meal function
function quickAddMeal(day) {
document.getElementById('quickAddDay').textContent = day;
document.getElementById('quickAddPlanDay').value = day;
new bootstrap.Modal(document.getElementById('quickAddMealModal')).show();
}
function submitMealToPlan() {
const form = document.getElementById('addMealForm');
function submitQuickAddMeal() {
const form = document.getElementById('quickAddMealForm');
const formData = new FormData(form);
fetch('/plan/add', {
@@ -91,9 +148,126 @@ function submitMealToPlan() {
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('quickAddMealModal')).hide();
location.reload();
} else {
alert('Error: ' + data.message);
}
})
.catch(error => {
alert('Error: ' + error.message);
});
}
// Edit day function
async function editDay(day, person) {
currentEditingDay = day;
currentEditingPerson = person;
document.getElementById('editDayName').textContent = day;
document.getElementById('editDayPerson').value = person;
document.getElementById('editDayValue').value = day;
// Load current meals for this day
await loadCurrentDayMeals(person, day);
new bootstrap.Modal(document.getElementById('editDayModal')).show();
}
// Load current meals for the day
async function loadCurrentDayMeals(person, day) {
try {
const response = await fetch(`/plan/${person}/${day}`);
const meals = await response.json();
const container = document.getElementById('currentDayMeals');
if (meals.length === 0) {
container.innerHTML = '<em class="text-muted">No meals planned</em>';
} else {
container.innerHTML = meals.map(meal => `
<div class="d-flex justify-content-between align-items-center mb-2 p-2 bg-light rounded">
<span><strong>${meal.meal_name}</strong> <small class="text-muted">(${meal.meal_type})</small></span>
<button class="btn btn-sm btn-outline-danger" onclick="removeMealFromDay(${meal.id})">
<i class="bi bi-trash"></i>
</button>
</div>
`).join('');
}
// Update nutrition preview
updateDayNutritionPreview(meals);
} catch (error) {
console.error('Error loading day meals:', error);
}
}
// Add meal to current day
async function addMealToCurrentDay() {
const form = document.getElementById('addMealToDayForm');
const formData = new FormData(form);
try {
const response = await fetch('/plan/add', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
// Reset meal selector
document.getElementById('mealSelectForDay').value = '';
// Reload current meals
await loadCurrentDayMeals(currentEditingPerson, currentEditingDay);
} else {
alert('Error adding meal: ' + result.message);
}
} catch (error) {
alert('Error adding meal: ' + error.message);
}
}
// Remove meal from day
async function removeMealFromDay(planId) {
if (confirm('Remove this meal from the day?')) {
try {
const response = await fetch(`/plan/${planId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.status === 'success') {
await loadCurrentDayMeals(currentEditingPerson, currentEditingDay);
} else {
alert('Error removing meal: ' + result.message);
}
} catch (error) {
alert('Error removing meal: ' + error.message);
}
}
}
// Update nutrition preview (simplified - you could enhance this with actual calculations)
function updateDayNutritionPreview(meals) {
const preview = document.getElementById('dayNutritionPreview');
preview.innerHTML = `
<div class="text-center">
<div class="mb-2">
<strong>${meals.length}</strong> meals planned
</div>
<small class="text-muted">
Nutrition totals will be calculated when page reloads
</small>
</div>
`;
}
// Handle modal cleanup
document.getElementById('editDayModal').addEventListener('hidden.bs.modal', function () {
// Reload the page to refresh all totals
location.reload();
});
</script>
{% endblock %}
{% endblock %}