mirror of
https://github.com/sstent/foodplanner.git
synced 2026-02-01 06:31:36 +00:00
initial
This commit is contained in:
BIN
__pycache__/main.cpython-313.pyc
Normal file
BIN
__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
303
main.py
Normal file
303
main.py
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
# Meal Planner FastAPI Application
|
||||||
|
# Run with: uvicorn main:app --reload
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Depends, HTTPException, Request, Form
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, Text, Date
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.orm import sessionmaker, Session, relationship
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List, Optional
|
||||||
|
from datetime import date, datetime
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {})
|
||||||
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
# Initialize FastAPI app
|
||||||
|
app = FastAPI(title="Meal Planner")
|
||||||
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
# Database Models
|
||||||
|
class Food(Base):
|
||||||
|
__tablename__ = "foods"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
name = Column(String, unique=True, index=True)
|
||||||
|
serving_size = Column(String)
|
||||||
|
serving_unit = Column(String)
|
||||||
|
calories = Column(Float)
|
||||||
|
protein = Column(Float)
|
||||||
|
carbs = Column(Float)
|
||||||
|
fat = Column(Float)
|
||||||
|
fiber = Column(Float, default=0)
|
||||||
|
sugar = Column(Float, default=0)
|
||||||
|
sodium = Column(Float, default=0)
|
||||||
|
calcium = Column(Float, default=0)
|
||||||
|
|
||||||
|
class Meal(Base):
|
||||||
|
__tablename__ = "meals"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
name = Column(String, index=True)
|
||||||
|
meal_type = Column(String) # breakfast, lunch, dinner, snack
|
||||||
|
|
||||||
|
# Relationship to meal foods
|
||||||
|
meal_foods = relationship("MealFood", back_populates="meal")
|
||||||
|
|
||||||
|
class MealFood(Base):
|
||||||
|
__tablename__ = "meal_foods"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
meal_id = Column(Integer, ForeignKey("meals.id"))
|
||||||
|
food_id = Column(Integer, ForeignKey("foods.id"))
|
||||||
|
quantity = Column(Float)
|
||||||
|
|
||||||
|
meal = relationship("Meal", back_populates="meal_foods")
|
||||||
|
food = relationship("Food")
|
||||||
|
|
||||||
|
class Plan(Base):
|
||||||
|
__tablename__ = "plans"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
person = Column(String, index=True) # Person A or Person B
|
||||||
|
date = Column(Date, index=True)
|
||||||
|
meal_id = Column(Integer, ForeignKey("meals.id"))
|
||||||
|
|
||||||
|
meal = relationship("Meal")
|
||||||
|
|
||||||
|
# Pydantic models
|
||||||
|
class FoodCreate(BaseModel):
|
||||||
|
name: str
|
||||||
|
serving_size: str
|
||||||
|
serving_unit: str
|
||||||
|
calories: float
|
||||||
|
protein: float
|
||||||
|
carbs: float
|
||||||
|
fat: float
|
||||||
|
fiber: float = 0
|
||||||
|
sugar: float = 0
|
||||||
|
sodium: float = 0
|
||||||
|
calcium: float = 0
|
||||||
|
|
||||||
|
class FoodResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
serving_size: str
|
||||||
|
serving_unit: str
|
||||||
|
calories: float
|
||||||
|
protein: float
|
||||||
|
carbs: float
|
||||||
|
fat: float
|
||||||
|
fiber: float
|
||||||
|
sugar: float
|
||||||
|
sodium: float
|
||||||
|
calcium: float
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
class MealCreate(BaseModel):
|
||||||
|
name: str
|
||||||
|
meal_type: str
|
||||||
|
foods: List[dict] # [{"food_id": 1, "quantity": 1.5}]
|
||||||
|
|
||||||
|
# Database dependency
|
||||||
|
def get_db():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
# Create tables
|
||||||
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
# Utility functions
|
||||||
|
def calculate_meal_nutrition(meal, db: Session):
|
||||||
|
"""Calculate total nutrition for a meal"""
|
||||||
|
totals = {
|
||||||
|
'calories': 0, 'protein': 0, 'carbs': 0, 'fat': 0,
|
||||||
|
'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for meal_food in meal.meal_foods:
|
||||||
|
food = meal_food.food
|
||||||
|
quantity = meal_food.quantity
|
||||||
|
|
||||||
|
totals['calories'] += food.calories * quantity
|
||||||
|
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
|
||||||
|
|
||||||
|
# Calculate percentages
|
||||||
|
total_cals = totals['calories']
|
||||||
|
if total_cals > 0:
|
||||||
|
totals['protein_pct'] = round((totals['protein'] * 4 / total_cals) * 100, 1)
|
||||||
|
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']
|
||||||
|
|
||||||
|
return totals
|
||||||
|
|
||||||
|
def calculate_day_nutrition(plans, db: Session):
|
||||||
|
"""Calculate total nutrition for a day's worth of meals"""
|
||||||
|
day_totals = {
|
||||||
|
'calories': 0, 'protein': 0, 'carbs': 0, 'fat': 0,
|
||||||
|
'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for plan in plans:
|
||||||
|
meal_nutrition = calculate_meal_nutrition(plan.meal, db)
|
||||||
|
for key in day_totals:
|
||||||
|
if key in meal_nutrition:
|
||||||
|
day_totals[key] += meal_nutrition[key]
|
||||||
|
|
||||||
|
# Calculate percentages
|
||||||
|
total_cals = day_totals['calories']
|
||||||
|
if total_cals > 0:
|
||||||
|
day_totals['protein_pct'] = round((day_totals['protein'] * 4 / total_cals) * 100, 1)
|
||||||
|
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']
|
||||||
|
|
||||||
|
return day_totals
|
||||||
|
|
||||||
|
# Routes
|
||||||
|
@app.get("/", response_class=HTMLResponse)
|
||||||
|
async def root(request: Request):
|
||||||
|
return templates.TemplateResponse("index.html", {"request": request})
|
||||||
|
|
||||||
|
# Foods tab
|
||||||
|
@app.get("/foods", response_class=HTMLResponse)
|
||||||
|
async def foods_page(request: Request, db: Session = Depends(get_db)):
|
||||||
|
foods = db.query(Food).all()
|
||||||
|
return templates.TemplateResponse("foods.html", {"request": request, "foods": foods})
|
||||||
|
|
||||||
|
@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}
|
||||||
|
|
||||||
|
@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"}
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
plans = {}
|
||||||
|
for d in dates:
|
||||||
|
day_plans = db.query(Plan).filter(Plan.person == person, Plan.date == d).all()
|
||||||
|
plans[d] = day_plans
|
||||||
|
|
||||||
|
# Calculate daily totals
|
||||||
|
daily_totals = {}
|
||||||
|
for d in dates:
|
||||||
|
daily_totals[d] = calculate_day_nutrition(plans[d], db)
|
||||||
|
|
||||||
|
meals = db.query(Meal).all()
|
||||||
|
|
||||||
|
return templates.TemplateResponse("plan.html", {
|
||||||
|
"request": request, "person": person, "dates": dates,
|
||||||
|
"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(...),
|
||||||
|
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"}
|
||||||
|
|
||||||
|
# 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)):
|
||||||
|
|
||||||
|
if not plan_date:
|
||||||
|
plan_date = date.today().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
selected_date = datetime.strptime(plan_date, "%Y-%m-%d").date()
|
||||||
|
|
||||||
|
# Get all plans for the selected day
|
||||||
|
plans = db.query(Plan).filter(Plan.person == person, Plan.date == selected_date).all()
|
||||||
|
|
||||||
|
# Group by meal type and calculate nutrition
|
||||||
|
meal_details = []
|
||||||
|
for plan in plans:
|
||||||
|
meal_nutrition = calculate_meal_nutrition(plan.meal, db)
|
||||||
|
meal_details.append({
|
||||||
|
'plan': plan,
|
||||||
|
'nutrition': meal_nutrition,
|
||||||
|
'foods': plan.meal.meal_foods
|
||||||
|
})
|
||||||
|
|
||||||
|
# Calculate day totals
|
||||||
|
day_totals = calculate_day_nutrition(plans, db)
|
||||||
|
|
||||||
|
return templates.TemplateResponse("detailed.html", {
|
||||||
|
"request": request, "person": person, "selected_date": selected_date,
|
||||||
|
"meal_details": meal_details, "day_totals": day_totals
|
||||||
|
})
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8999)
|
||||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
fastapi==0.104.1
|
||||||
|
uvicorn[standard]==0.24.0
|
||||||
|
sqlalchemy==2.0.23
|
||||||
|
#psycopg2-binary==2.9.9
|
||||||
|
python-multipart==0.0.6
|
||||||
|
jinja2==3.1.2
|
||||||
74
templates/base.html
Normal file
74
templates/base.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<!-- templates/base.html -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<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">
|
||||||
|
<style>
|
||||||
|
.nav-tabs .nav-link.active {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.nutrition-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.nutrition-item {
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.person-toggle {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="person-toggle">
|
||||||
|
<select id="personSelect" class="form-select" onchange="switchPerson()">
|
||||||
|
<option value="Person A" {% if person == "Person A" %}selected{% endif %}>Person A</option>
|
||||||
|
<option value="Person B" {% if person == "Person B" %}selected{% endif %}>Person B</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="mt-3 mb-4">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>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" onclick="location.href='/meals'">Meals</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" onclick="location.href='/plan'">Plan A</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" onclick="location.href='/detailed'">Detailed Planner</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content mt-3">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function switchPerson() {
|
||||||
|
const person = document.getElementById('personSelect').value;
|
||||||
|
const currentUrl = new URL(window.location);
|
||||||
|
currentUrl.searchParams.set('person', person);
|
||||||
|
window.location.href = currentUrl.toString();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
514
templates/combo.html
Normal file
514
templates/combo.html
Normal file
@@ -0,0 +1,514 @@
|
|||||||
|
<!-- templates/base.html -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<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">
|
||||||
|
<style>
|
||||||
|
.nav-tabs .nav-link.active {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.nutrition-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.nutrition-item {
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.person-toggle {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="person-toggle">
|
||||||
|
<select id="personSelect" class="form-select" onchange="switchPerson()">
|
||||||
|
<option value="Person A" {% if person == "Person A" %}selected{% endif %}>Person A</option>
|
||||||
|
<option value="Person B" {% if person == "Person B" %}selected{% endif %}>Person B</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="mt-3 mb-4">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>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" onclick="location.href='/meals'">Meals</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" onclick="location.href='/plan'">Plan A</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" onclick="location.href='/detailed'">Detailed Planner</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content mt-3">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function switchPerson() {
|
||||||
|
const person = document.getElementById('personSelect').value;
|
||||||
|
const currentUrl = new URL(window.location);
|
||||||
|
currentUrl.searchParams.set('person', person);
|
||||||
|
window.location.href = currentUrl.toString();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<!-- templates/index.html -->
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<h2>Welcome to Meal Planner</h2>
|
||||||
|
<p>Use the tabs above to navigate:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Foods</strong> - Manage your food database with nutritional information</li>
|
||||||
|
<li><strong>Meals</strong> - Create meals by combining foods</li>
|
||||||
|
<li><strong>Plan A</strong> - Create 2-week meal plans with daily totals</li>
|
||||||
|
<li><strong>Detailed Planner</strong> - View detailed breakdown for a specific day</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<!-- templates/foods.html -->
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h3>Foods Database</h3>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Serving</th>
|
||||||
|
<th>Cal</th>
|
||||||
|
<th>Protein</th>
|
||||||
|
<th>Carbs</th>
|
||||||
|
<th>Fat</th>
|
||||||
|
<th>Fiber</th>
|
||||||
|
<th>Sodium</th>
|
||||||
|
<th>Calcium</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for food in foods %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ food.name }}</td>
|
||||||
|
<td>{{ food.serving_size }} {{ food.serving_unit }}</td>
|
||||||
|
<td>{{ "%.1f"|format(food.calories) }}</td>
|
||||||
|
<td>{{ "%.1f"|format(food.protein) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.carbs) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.fat) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.fiber) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.sodium) }}mg</td>
|
||||||
|
<td>{{ "%.1f"|format(food.calcium) }}mg</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<!-- templates/meals.html -->
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h3>Meals</h3>
|
||||||
|
{% for meal in meals %}
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<strong>{{ meal.name }}</strong> - {{ meal.meal_type.title() }}
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% if meal.meal_foods %}
|
||||||
|
<ul>
|
||||||
|
{% for meal_food in meal.meal_foods %}
|
||||||
|
<li>{{ meal_food.quantity }} × {{ meal_food.food.name }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<em>No foods added yet</em>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function addFoodToMeal() {
|
||||||
|
const mealId = document.getElementById('mealSelect').value;
|
||||||
|
const foodId = document.getElementById('foodSelect').value;
|
||||||
|
const quantity = document.getElementById('quantity').value;
|
||||||
|
|
||||||
|
if (!mealId || !foodId || !quantity) {
|
||||||
|
alert('Please fill all fields');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('food_id', foodId);
|
||||||
|
formData.append('quantity', quantity);
|
||||||
|
|
||||||
|
fetch(`/meals/${mealId}/add_food`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<!-- templates/plan.html -->
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h3>2-Week Plan for {{ person }}</h3>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for date in dates %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ date.strftime('%m/%d') }}</td>
|
||||||
|
<td>
|
||||||
|
{% for plan in plans[date] %}
|
||||||
|
<span class="badge bg-secondary me-1">{{ plan.meal.name }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</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>
|
||||||
|
<button class="btn btn-sm btn-primary" onclick="addMealToDay('{{ date }}')">Add Meal</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Meal Modal -->
|
||||||
|
<div class="modal fade" id="addMealModal" tabindex="-1">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="addMealForm">
|
||||||
|
<input type="hidden" id="planDate" name="plan_date">
|
||||||
|
<input type="hidden" name="person" value="{{ person }}">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Select Meal</label>
|
||||||
|
<select class="form-control" 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>
|
||||||
|
</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="submitMealToPlan()">Add Meal</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function addMealToDay(date) {
|
||||||
|
document.getElementById('planDate').value = date;
|
||||||
|
new bootstrap.Modal(document.getElementById('addMealModal')).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitMealToPlan() {
|
||||||
|
const form = document.getElementById('addMealForm');
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
fetch('/plan/add', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<!-- templates/detailed.html -->
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>Detailed View for {{ person }}</h3>
|
||||||
|
<form method="get">
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>{{ selected_date.strftime('%A, %B %d, %Y') }}</h4>
|
||||||
|
|
||||||
|
{% 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>
|
||||||
|
<div class="card-body">
|
||||||
|
<h6>Foods:</h6>
|
||||||
|
<ul>
|
||||||
|
{% 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>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="nutrition-grid">
|
||||||
|
<div class="nutrition-item">
|
||||||
|
<strong>{{ "%.0f"|format(meal_detail.nutrition.calories) }}</strong><br>
|
||||||
|
<small>Calories</small>
|
||||||
|
</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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% 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>
|
||||||
|
</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.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
113
templates/detailed.html
Normal file
113
templates/detailed.html
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<!-- templates/detailed.html -->
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>Detailed View for {{ person }}</h3>
|
||||||
|
<form method="get">
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>{{ selected_date.strftime('%A, %B %d, %Y') }}</h4>
|
||||||
|
|
||||||
|
{% 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>
|
||||||
|
<div class="card-body">
|
||||||
|
<h6>Foods:</h6>
|
||||||
|
<ul>
|
||||||
|
{% 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>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="nutrition-grid">
|
||||||
|
<div class="nutrition-item">
|
||||||
|
<strong>{{ "%.0f"|format(meal_detail.nutrition.calories) }}</strong><br>
|
||||||
|
<small>Calories</small>
|
||||||
|
</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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% 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>
|
||||||
|
</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.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
101
templates/foods.html
Normal file
101
templates/foods.html
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h3>Foods Database</h3>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Serving</th>
|
||||||
|
<th>Cal</th>
|
||||||
|
<th>Protein</th>
|
||||||
|
<th>Carbs</th>
|
||||||
|
<th>Fat</th>
|
||||||
|
<th>Fiber</th>
|
||||||
|
<th>Sodium</th>
|
||||||
|
<th>Calcium</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for food in foods %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ food.name }}</td>
|
||||||
|
<td>{{ food.serving_size }} {{ food.serving_unit }}</td>
|
||||||
|
<td>{{ "%.1f"|format(food.calories) }}</td>
|
||||||
|
<td>{{ "%.1f"|format(food.protein) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.carbs) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.fat) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.fiber) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.sodium) }}mg</td>
|
||||||
|
<td>{{ "%.1f"|format(food.calcium) }}mg</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
16
templates/index.html
Normal file
16
templates/index.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!-- templates/index.html -->
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<h2>Welcome to Meal Planner</h2>
|
||||||
|
<p>Use the tabs above to navigate:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Foods</strong> - Manage your food database with nutritional information</li>
|
||||||
|
<li><strong>Meals</strong> - Create meals by combining foods</li>
|
||||||
|
<li><strong>Plan A</strong> - Create 2-week meal plans with daily totals</li>
|
||||||
|
<li><strong>Detailed Planner</strong> - View detailed breakdown for a specific day</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
101
templates/meals.html
Normal file
101
templates/meals.html
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h3>Foods Database</h3>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Serving</th>
|
||||||
|
<th>Cal</th>
|
||||||
|
<th>Protein</th>
|
||||||
|
<th>Carbs</th>
|
||||||
|
<th>Fat</th>
|
||||||
|
<th>Fiber</th>
|
||||||
|
<th>Sodium</th>
|
||||||
|
<th>Calcium</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for food in foods %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ food.name }}</td>
|
||||||
|
<td>{{ food.serving_size }} {{ food.serving_unit }}</td>
|
||||||
|
<td>{{ "%.1f"|format(food.calories) }}</td>
|
||||||
|
<td>{{ "%.1f"|format(food.protein) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.carbs) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.fat) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.fiber) }}g</td>
|
||||||
|
<td>{{ "%.1f"|format(food.sodium) }}mg</td>
|
||||||
|
<td>{{ "%.1f"|format(food.calcium) }}mg</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
99
templates/plan.html
Normal file
99
templates/plan.html
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h3>2-Week Plan for {{ person }}</h3>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for date in dates %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ date.strftime('%m/%d') }}</td>
|
||||||
|
<td>
|
||||||
|
{% for plan in plans[date] %}
|
||||||
|
<span class="badge bg-secondary me-1">{{ plan.meal.name }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</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>
|
||||||
|
<button class="btn btn-sm btn-primary" onclick="addMealToDay('{{ date }}')">Add Meal</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Meal Modal -->
|
||||||
|
<div class="modal fade" id="addMealModal" tabindex="-1">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="addMealForm">
|
||||||
|
<input type="hidden" id="planDate" name="plan_date">
|
||||||
|
<input type="hidden" name="person" value="{{ person }}">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Select Meal</label>
|
||||||
|
<select class="form-control" 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>
|
||||||
|
</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="submitMealToPlan()">Add Meal</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function addMealToDay(date) {
|
||||||
|
document.getElementById('planDate').value = date;
|
||||||
|
new bootstrap.Modal(document.getElementById('addMealModal')).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitMealToPlan() {
|
||||||
|
const form = document.getElementById('addMealForm');
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
fetch('/plan/add', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user