sync - added week menus

This commit is contained in:
2025-09-22 09:45:28 -07:00
parent b9ac37183a
commit 45779f1739
7 changed files with 1385 additions and 8 deletions

463
main.py
View File

@@ -5,7 +5,7 @@ from fastapi import FastAPI, Depends, HTTPException, Request, Form, Body
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, Text, Date from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, Text, Date, Boolean
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session, relationship from sqlalchemy.orm import sessionmaker, Session, relationship
@@ -101,6 +101,51 @@ class TemplateMeal(Base):
template = relationship("Template", back_populates="template_meals") template = relationship("Template", back_populates="template_meals")
meal = relationship("Meal") meal = relationship("Meal")
class WeeklyMenu(Base):
__tablename__ = "weekly_menus"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True)
# Relationship to weekly menu days
weekly_menu_days = relationship("WeeklyMenuDay", back_populates="weekly_menu")
class WeeklyMenuDay(Base):
__tablename__ = "weekly_menu_days"
id = Column(Integer, primary_key=True, index=True)
weekly_menu_id = Column(Integer, ForeignKey("weekly_menus.id"))
day_of_week = Column(Integer) # 0=Monday, 1=Tuesday, ..., 6=Sunday
template_id = Column(Integer, ForeignKey("templates.id"))
weekly_menu = relationship("WeeklyMenu", back_populates="weekly_menu_days")
template = relationship("Template")
class TrackedDay(Base):
"""Represents a day being tracked (separate from planned days)"""
__tablename__ = "tracked_days"
id = Column(Integer, primary_key=True, index=True)
person = Column(String, index=True) # Person A or Person B
date = Column(Date, index=True) # Date being tracked
is_modified = Column(Boolean, default=False) # Whether this day has been modified from original plan
# Relationship to tracked meals
tracked_meals = relationship("TrackedMeal", back_populates="tracked_day")
class TrackedMeal(Base):
"""Represents a meal tracked for a specific day"""
__tablename__ = "tracked_meals"
id = Column(Integer, primary_key=True, index=True)
tracked_day_id = Column(Integer, ForeignKey("tracked_days.id"))
meal_id = Column(Integer, ForeignKey("meals.id"))
meal_time = Column(String) # Breakfast, Lunch, Dinner, Snack 1, Snack 2, Beverage 1, Beverage 2
quantity = Column(Float, default=1.0) # Quantity multiplier (e.g., 1.5 for 1.5 servings)
tracked_day = relationship("TrackedDay", back_populates="tracked_meals")
meal = relationship("Meal")
# Pydantic models # Pydantic models
class FoodCreate(BaseModel): class FoodCreate(BaseModel):
name: str name: str
@@ -142,6 +187,15 @@ class MealCreate(BaseModel):
meal_time: str meal_time: str
foods: List[dict] # [{"food_id": 1, "quantity": 1.5}] foods: List[dict] # [{"food_id": 1, "quantity": 1.5}]
class TrackedDayCreate(BaseModel):
person: str
date: str # ISO date string
class TrackedMealCreate(BaseModel):
meal_id: int
meal_time: str
quantity: float = 1.0
# Database dependency # Database dependency
def get_db(): def get_db():
db = SessionLocal() db = SessionLocal()
@@ -220,7 +274,8 @@ def calculate_day_nutrition(plans, db: Session):
# Routes # Routes
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
async def root(request: Request): async def root(request: Request):
return templates.TemplateResponse("index.html", {"request": request}) from fastapi.responses import RedirectResponse
return RedirectResponse(url="/tracker", status_code=302)
# Imports tab # Imports tab
@app.get("/imports", response_class=HTMLResponse) @app.get("/imports", response_class=HTMLResponse)
@@ -1284,6 +1339,410 @@ async def delete_template(template_id: int, db: Session = Depends(get_db)):
db.rollback() db.rollback()
return {"status": "error", "message": str(e)} return {"status": "error", "message": str(e)}
# Weekly Menu tab
@app.get("/weeklymenu", response_class=HTMLResponse)
async def weeklymenu_page(request: Request, db: Session = Depends(get_db)):
weekly_menus = db.query(WeeklyMenu).all()
templates_list = db.query(Template).all()
# Convert weekly menus to dictionaries for JSON serialization
weekly_menus_data = []
for weekly_menu in weekly_menus:
weekly_menu_days = db.query(WeeklyMenuDay).filter(WeeklyMenuDay.weekly_menu_id == weekly_menu.id).all()
weekly_menu_dict = {
"id": weekly_menu.id,
"name": weekly_menu.name,
"weekly_menu_days": []
}
for wmd in weekly_menu_days:
weekly_menu_dict["weekly_menu_days"].append({
"day_of_week": wmd.day_of_week,
"template_id": wmd.template_id,
"template": {
"id": wmd.template.id,
"name": wmd.template.name
}
})
weekly_menus_data.append(weekly_menu_dict)
return templates.TemplateResponse("weeklymenu.html", {
"request": request,
"weekly_menus": weekly_menus_data,
"templates": templates_list
})
@app.post("/weeklymenu/create")
async def create_weekly_menu(request: Request, name: str = Form(...),
template_assignments: str = Form(...), db: Session = Depends(get_db)):
"""Create a new weekly menu with template assignments"""
try:
# Create weekly menu
weekly_menu = WeeklyMenu(name=name)
db.add(weekly_menu)
db.flush() # Get weekly menu ID
# Parse template assignments (format: "day_of_week:template_id,day_of_week:template_id,...")
if template_assignments:
assignments = template_assignments.split(',')
for assignment in assignments:
if ':' in assignment:
day_of_week, template_id = assignment.split(':', 1)
if template_id.strip(): # Only add if template_id is not empty
weekly_menu_day = WeeklyMenuDay(
weekly_menu_id=weekly_menu.id,
day_of_week=int(day_of_week.strip()),
template_id=int(template_id.strip())
)
db.add(weekly_menu_day)
db.commit()
return {"status": "success", "weekly_menu_id": weekly_menu.id}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.post("/weeklymenu/{weekly_menu_id}/apply")
async def apply_weekly_menu(weekly_menu_id: int, person: str = Form(...),
week_start_date: str = Form(...), db: Session = Depends(get_db)):
"""Apply a weekly menu to a person's plan for a specific week"""
try:
from datetime import datetime, timedelta
week_start_date_obj = datetime.fromisoformat(week_start_date).date()
weekly_menu_days = db.query(WeeklyMenuDay).filter(WeeklyMenuDay.weekly_menu_id == weekly_menu_id).all()
# Check if any meals already exist for this week
existing_plans_count = 0
for i in range(7):
day_date = week_start_date_obj + timedelta(days=i)
existing_plans_count += db.query(Plan).filter(Plan.person == person, Plan.date == day_date).count()
if existing_plans_count > 0:
return {"status": "confirm_overwrite", "message": f"There are already {existing_plans_count} meals planned for this week. Do you want to overwrite them?"}
# Apply weekly menu to each day
for weekly_menu_day in weekly_menu_days:
day_date = week_start_date_obj + timedelta(days=weekly_menu_day.day_of_week)
# Get template meals for this day
template_meals = db.query(TemplateMeal).filter(TemplateMeal.template_id == weekly_menu_day.template_id).all()
# Add template meals to plan
for tm in template_meals:
plan = Plan(person=person, date=day_date, meal_id=tm.meal_id, meal_time=tm.meal_time)
db.add(plan)
db.commit()
return {"status": "success"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.delete("/weeklymenu/{weekly_menu_id}")
async def delete_weekly_menu(weekly_menu_id: int, db: Session = Depends(get_db)):
"""Delete a weekly menu and its day assignments"""
try:
# Delete weekly menu days first
db.query(WeeklyMenuDay).filter(WeeklyMenuDay.weekly_menu_id == weekly_menu_id).delete()
# Delete weekly menu
weekly_menu = db.query(WeeklyMenu).filter(WeeklyMenu.id == weekly_menu_id).first()
if weekly_menu:
db.delete(weekly_menu)
db.commit()
return {"status": "success"}
else:
return {"status": "error", "message": "Weekly menu not found"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
# Tracker tab
@app.get("/tracker", response_class=HTMLResponse)
async def tracker_page(request: Request, person: str = "Person A", date: str = None, db: Session = Depends(get_db)):
from datetime import datetime, date as date_type
# If no date provided, use today
if not date:
current_date = date_type.today()
else:
current_date = datetime.fromisoformat(date).date()
# Get or create tracked day for this date
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == current_date
).first()
if not tracked_day:
tracked_day = TrackedDay(person=person, date=current_date)
db.add(tracked_day)
db.commit()
db.refresh(tracked_day)
# Get tracked meals for this day
tracked_meals = db.query(TrackedMeal).filter(TrackedMeal.tracked_day_id == tracked_day.id).all()
# If no tracked meals exist, pre-populate with planned meals
if not tracked_meals:
copy_plan_to_tracked(db, person, current_date, tracked_day.id)
# Re-fetch tracked meals after copying
tracked_meals = db.query(TrackedMeal).filter(TrackedMeal.tracked_day_id == tracked_day.id).all()
# Calculate nutrition totals
day_totals = calculate_tracked_day_nutrition(tracked_meals, db)
# Get all meals for selection
meals = db.query(Meal).all()
# Get existing templates for apply template functionality
templates_list = db.query(Template).all()
# Calculate previous and next dates
prev_date = current_date
next_date = current_date
return templates.TemplateResponse("tracker.html", {
"request": request,
"person": person,
"current_date": current_date,
"tracked_meals": tracked_meals,
"day_totals": day_totals,
"meals": meals,
"templates": templates_list,
"is_modified": tracked_day.is_modified if tracked_day else False,
"prev_date": prev_date.isoformat(),
"next_date": next_date.isoformat()
})
def copy_plan_to_tracked(db: Session, person: str, date, tracked_day_id: int):
"""Copy planned meals to tracked meals for a specific date"""
plans = db.query(Plan).filter(Plan.person == person, Plan.date == date).all()
for plan in plans:
# Check if this meal is already tracked
existing_tracked = db.query(TrackedMeal).filter(
TrackedMeal.tracked_day_id == tracked_day_id,
TrackedMeal.meal_id == plan.meal_id,
TrackedMeal.meal_time == plan.meal_time
).first()
if not existing_tracked:
tracked_meal = TrackedMeal(
tracked_day_id=tracked_day_id,
meal_id=plan.meal_id,
meal_time=plan.meal_time,
quantity=1.0
)
db.add(tracked_meal)
# Mark the tracked day as not modified (it's now matching the plan)
tracked_day = db.query(TrackedDay).filter(TrackedDay.id == tracked_day_id).first()
if tracked_day:
tracked_day.is_modified = False
db.commit()
def calculate_tracked_day_nutrition(tracked_meals, db: Session):
"""Calculate total nutrition for tracked meals"""
day_totals = {
'calories': 0, 'protein': 0, 'carbs': 0, 'fat': 0,
'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0
}
for tracked_meal in tracked_meals:
meal_nutrition = calculate_meal_nutrition(tracked_meal.meal, db)
for key in day_totals:
if key in meal_nutrition:
day_totals[key] += meal_nutrition[key] * tracked_meal.quantity
# 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']
else:
day_totals['protein_pct'] = 0
day_totals['carbs_pct'] = 0
day_totals['fat_pct'] = 0
day_totals['net_carbs'] = 0
return day_totals
@app.post("/tracker/add_meal")
async def add_tracked_meal(request: Request, person: str = Form(...),
date: str = Form(...), meal_id: int = Form(...),
meal_time: str = Form(...), quantity: float = Form(1.0),
db: Session = Depends(get_db)):
"""Add a meal to the tracker"""
try:
from datetime import datetime
track_date = datetime.fromisoformat(date).date()
# Get or create tracked day
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == track_date
).first()
if not tracked_day:
tracked_day = TrackedDay(person=person, date=track_date)
db.add(tracked_day)
db.commit()
db.refresh(tracked_day)
# Add the tracked meal
tracked_meal = TrackedMeal(
tracked_day_id=tracked_day.id,
meal_id=meal_id,
meal_time=meal_time,
quantity=quantity
)
db.add(tracked_meal)
# Mark as modified since we're adding a meal
tracked_day.is_modified = True
db.commit()
return {"status": "success"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.delete("/tracker/remove_meal/{tracked_meal_id}")
async def remove_tracked_meal(tracked_meal_id: int, db: Session = Depends(get_db)):
"""Remove a meal from the tracker"""
try:
tracked_meal = db.query(TrackedMeal).filter(TrackedMeal.id == tracked_meal_id).first()
if not tracked_meal:
return {"status": "error", "message": "Tracked meal not found"}
# Mark as modified since we're removing a meal
tracked_day = db.query(TrackedDay).filter(TrackedDay.id == tracked_meal.tracked_day_id).first()
if tracked_day:
tracked_day.is_modified = True
db.delete(tracked_meal)
db.commit()
return {"status": "success"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.post("/tracker/save_template")
async def save_tracked_day_as_template(request: Request, person: str = Form(...),
date: str = Form(...), template_name: str = Form(...),
db: Session = Depends(get_db)):
"""Save the current tracked day as a template"""
try:
from datetime import datetime
track_date = datetime.fromisoformat(date).date()
# Get tracked day
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == track_date
).first()
if not tracked_day:
return {"status": "error", "message": "No tracked day found"}
# Create new template
template = Template(name=template_name)
db.add(template)
db.commit()
db.refresh(template)
# Add all tracked meals to template
tracked_meals = db.query(TrackedMeal).filter(TrackedMeal.tracked_day_id == tracked_day.id).all()
for tracked_meal in tracked_meals:
template_meal = TemplateMeal(
template_id=template.id,
meal_id=tracked_meal.meal_id,
meal_time=tracked_meal.meal_time
)
db.add(template_meal)
db.commit()
return {"status": "success", "template_id": template.id}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.post("/tracker/apply_template")
async def apply_template_to_tracked_day(request: Request, person: str = Form(...),
date: str = Form(...), template_id: int = Form(...),
db: Session = Depends(get_db)):
"""Apply a template to the current tracked day"""
try:
from datetime import datetime
track_date = datetime.fromisoformat(date).date()
# Get or create tracked day
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == track_date
).first()
if not tracked_day:
tracked_day = TrackedDay(person=person, date=track_date)
db.add(tracked_day)
db.commit()
db.refresh(tracked_day)
# Remove existing tracked meals
db.query(TrackedMeal).filter(TrackedMeal.tracked_day_id == tracked_day.id).delete()
# Apply template meals
template_meals = db.query(TemplateMeal).filter(TemplateMeal.template_id == template_id).all()
for template_meal in template_meals:
tracked_meal = TrackedMeal(
tracked_day_id=tracked_day.id,
meal_id=template_meal.meal_id,
meal_time=template_meal.meal_time,
quantity=1.0
)
db.add(tracked_meal)
# Mark as modified since we're applying a template (not the original plan)
tracked_day.is_modified = True
db.commit()
return {"status": "success"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
@app.post("/tracker/reset_to_plan")
async def reset_to_plan(request: Request, person: str = Form(...),
date: str = Form(...), db: Session = Depends(get_db)):
"""Reset the tracked day back to the original plan"""
try:
from datetime import datetime
track_date = datetime.fromisoformat(date).date()
# Get tracked day
tracked_day = db.query(TrackedDay).filter(
TrackedDay.person == person,
TrackedDay.date == track_date
).first()
if not tracked_day:
return {"status": "error", "message": "No tracked day found"}
# Remove existing tracked meals
db.query(TrackedMeal).filter(TrackedMeal.tracked_day_id == tracked_day.id).delete()
# Copy plan meals back
copy_plan_to_tracked(db, person, track_date, tracked_day.id)
return {"status": "success"}
except Exception as e:
db.rollback()
return {"status": "error", "message": str(e)}
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8999) uvicorn.run(app, host="0.0.0.0", port=8999)

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""
Migration script to add is_modified column to tracked_days table.
Run this script to add the modification tracking functionality.
"""
import sqlite3
import os
def migrate_add_is_modified():
"""Add is_modified column to tracked_days table"""
db_path = "./meal_planner.db"
if not os.path.exists(db_path):
print("Database file not found. Please run the main application first to create the database.")
return
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Check if column already exists
cursor.execute("PRAGMA table_info(tracked_days)")
columns = cursor.fetchall()
column_names = [col[1] for col in columns]
if 'is_modified' in column_names:
print("Column 'is_modified' already exists in tracked_days table.")
conn.close()
return
print("Adding is_modified column to tracked_days table...")
# Add the column with default value 0 (False)
cursor.execute("ALTER TABLE tracked_days ADD COLUMN is_modified INTEGER DEFAULT 0")
# Update existing rows to have is_modified = 0 (not modified)
cursor.execute("UPDATE tracked_days SET is_modified = 0 WHERE is_modified IS NULL")
conn.commit()
conn.close()
print("Migration completed successfully!")
print("Added column: is_modified (INTEGER, DEFAULT 0)")
except Exception as e:
print(f"Migration failed: {e}")
if 'conn' in locals():
conn.close()
if __name__ == "__main__":
migrate_add_is_modified()

50
migrate_tracker_schema.py Normal file
View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""
Migration script to add tracker tables for meal tracking functionality.
Run this script to add the necessary tables for the Tracker tab.
"""
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, ForeignKey, Date
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
# Database setup
DATABASE_URL = "sqlite:///./meal_planner.db"
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()
# New models for tracker functionality
class TrackedDay(Base):
"""Represents a day being tracked (separate from planned days)"""
__tablename__ = "tracked_days"
id = Column(Integer, primary_key=True, index=True)
person = Column(String, index=True) # Person A or Person B
date = Column(Date, index=True) # Date being tracked
class TrackedMeal(Base):
"""Represents a meal tracked for a specific day"""
__tablename__ = "tracked_meals"
id = Column(Integer, primary_key=True, index=True)
tracked_day_id = Column(Integer) # Will add FK constraint later
meal_id = Column(Integer) # Will add FK constraint later
meal_time = Column(String) # Breakfast, Lunch, Dinner, Snack 1, Snack 2, Beverage 1, Beverage 2
quantity = Column(Float, default=1.0) # Quantity multiplier (e.g., 1.5 for 1.5 servings)
def migrate_tracker_tables():
"""Create the new tracker tables"""
try:
print("Creating tracker tables...")
Base.metadata.create_all(bind=engine)
print("Migration completed successfully!")
print("New tables created:")
print("- tracked_days: Stores individual days being tracked")
print("- tracked_meals: Stores meals for each tracked day")
except Exception as e:
print(f"Migration failed: {e}")
if __name__ == "__main__":
migrate_tracker_tables()

View File

@@ -83,6 +83,16 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/templates">Templates</a> <a class="nav-link" href="/templates">Templates</a>
</li> </li>
<li class="nav-item" role="presentation">
<button class="nav-link" onclick="location.href='/weeklymenu'">
<i class="bi bi-calendar-week"></i> Weekly Menu
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" onclick="location.href='/tracker'">
<i class="bi bi-calendar-check"></i> Tracker
</button>
</li>
</ul> </ul>
<div class="tab-content mt-3"> <div class="tab-content mt-3">
@@ -103,11 +113,17 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const currentPath = window.location.pathname; const currentPath = window.location.pathname;
const tabs = document.querySelectorAll('.nav-link'); const tabs = document.querySelectorAll('.nav-link');
tabs.forEach(tab => { tabs.forEach(tab => {
const href = tab.getAttribute('onclick'); const href = tab.getAttribute('onclick');
if (href && href.includes(currentPath)) { if (href && href.includes(currentPath)) {
tab.classList.add('active'); tab.classList.add('active');
} else if (currentPath === '/tracker' && href && href.includes('/tracker')) {
// Special case: ensure Tracker tab is active when on /tracker
tab.classList.add('active');
} else if (currentPath === '/weeklymenu' && href && href.includes('/weeklymenu')) {
// Special case: ensure Weekly Menu tab is active when on /weeklymenu
tab.classList.add('active');
} else { } else {
tab.classList.remove('active'); tab.classList.remove('active');
} }

View File

@@ -13,6 +13,7 @@
<tr> <tr>
<th>Select</th> <th>Select</th>
<th>Name</th> <th>Name</th>
<th>Brand</th>
<th>Serving</th> <th>Serving</th>
<th>Cal</th> <th>Cal</th>
<th>Protein</th> <th>Protein</th>
@@ -29,6 +30,7 @@
<tr> <tr>
<td><input type="checkbox" name="selected_foods" value="{{ food.id }}"></td> <td><input type="checkbox" name="selected_foods" value="{{ food.id }}"></td>
<td>{{ food.name }}</td> <td>{{ food.name }}</td>
<td>{{ food.brand or '' }}</td>
<td>{{ food.serving_size }} {{ food.serving_unit }}</td> <td>{{ food.serving_size }} {{ food.serving_unit }}</td>
<td>{{ "%.2f"|format(food.calories) }}</td> <td>{{ "%.2f"|format(food.calories) }}</td>
<td>{{ "%.2f"|format(food.protein) }}g</td> <td>{{ "%.2f"|format(food.protein) }}g</td>
@@ -38,7 +40,7 @@
<td>{{ "%.2f"|format(food.sodium) }}mg</td> <td>{{ "%.2f"|format(food.sodium) }}mg</td>
<td>{{ "%.2f"|format(food.calcium) }}mg</td> <td>{{ "%.2f"|format(food.calcium) }}mg</td>
<td> <td>
<button type="button" class="btn btn-sm btn-outline-primary" <button type="button" class="btn btn-sm btn-outline-primary"
onclick="editFood({{ food.id }})"> onclick="editFood({{ food.id }})">
<i class="bi bi-pencil"></i> Edit <i class="bi bi-pencil"></i> Edit
</button> </button>
@@ -65,10 +67,14 @@
<div class="modal-body"> <div class="modal-body">
<form id="addFoodForm" action="/foods/add" method="post"> <form id="addFoodForm" action="/foods/add" method="post">
<div class="row"> <div class="row">
<div class="col-12 mb-3"> <div class="col-8 mb-3">
<label class="form-label">Name</label> <label class="form-label">Name</label>
<input type="text" class="form-control" name="name" required> <input type="text" class="form-control" name="name" required>
</div> </div>
<div class="col-4 mb-3">
<label class="form-label">Brand</label>
<input type="text" class="form-control" name="brand" placeholder="Optional">
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 mb-3"> <div class="col-6 mb-3">
@@ -142,10 +148,14 @@
<form id="editFoodForm" action="/foods/edit" method="post"> <form id="editFoodForm" action="/foods/edit" method="post">
<input type="hidden" name="food_id" id="edit_food_id"> <input type="hidden" name="food_id" id="edit_food_id">
<div class="row"> <div class="row">
<div class="col-12 mb-3"> <div class="col-8 mb-3">
<label class="form-label">Name</label> <label class="form-label">Name</label>
<input type="text" class="form-control" name="name" id="edit_name" required> <input type="text" class="form-control" name="name" id="edit_name" required>
</div> </div>
<div class="col-4 mb-3">
<label class="form-label">Brand</label>
<input type="text" class="form-control" name="brand" id="edit_brand" placeholder="Optional">
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-6 mb-3"> <div class="col-6 mb-3">
@@ -277,6 +287,7 @@ document.addEventListener('DOMContentLoaded', function() {
{% for food in foods %} {% for food in foods %}
{{ food.id }}: { {{ food.id }}: {
name: '{{ food.name | replace("'", "\\'") }}', name: '{{ food.name | replace("'", "\\'") }}',
brand: '{{ food.brand | replace("'", "\\'") }}',
serving_size: '{{ food.serving_size | replace("'", "\\'") }}', serving_size: '{{ food.serving_size | replace("'", "\\'") }}',
serving_unit: '{{ food.serving_unit | replace("'", "\\'") }}', serving_unit: '{{ food.serving_unit | replace("'", "\\'") }}',
calories: {{ food.calories }}, calories: {{ food.calories }},
@@ -295,9 +306,10 @@ document.addEventListener('DOMContentLoaded', function() {
window.editFood = function(foodId) { window.editFood = function(foodId) {
const food = foodsData[foodId]; const food = foodsData[foodId];
if (!food) return; if (!food) return;
document.getElementById('edit_food_id').value = foodId; document.getElementById('edit_food_id').value = foodId;
document.getElementById('edit_name').value = food.name; document.getElementById('edit_name').value = food.name;
document.getElementById('edit_brand').value = food.brand || '';
document.getElementById('edit_serving_size').value = food.serving_size; document.getElementById('edit_serving_size').value = food.serving_size;
document.getElementById('edit_serving_unit').value = food.serving_unit; document.getElementById('edit_serving_unit').value = food.serving_unit;
document.getElementById('edit_calories').value = food.calories; document.getElementById('edit_calories').value = food.calories;
@@ -308,7 +320,7 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('edit_sugar').value = food.sugar; document.getElementById('edit_sugar').value = food.sugar;
document.getElementById('edit_sodium').value = food.sodium; document.getElementById('edit_sodium').value = food.sodium;
document.getElementById('edit_calcium').value = food.calcium; document.getElementById('edit_calcium').value = food.calcium;
new bootstrap.Modal(document.getElementById('editFoodModal')).show(); new bootstrap.Modal(document.getElementById('editFoodModal')).show();
}; };
}); });

388
templates/tracker.html Normal file
View File

@@ -0,0 +1,388 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-md-8">
<!-- Date Navigation -->
<div class="d-flex justify-content-between align-items-center mb-4">
<button class="btn btn-outline-secondary" onclick="navigateDate('{{ prev_date }}')">
<i class="bi bi-chevron-left"></i> Yesterday
</button>
<div class="text-center">
<h3>{{ current_date.strftime('%A, %B %d, %Y') }}</h3>
{% if is_modified %}
<span class="badge bg-warning text-dark">Custom</span>
{% else %}
<span class="badge bg-success">As Planned</span>
{% endif %}
</div>
<button class="btn btn-outline-secondary" onclick="navigateDate('{{ next_date }}')">
Tomorrow <i class="bi bi-chevron-right"></i>
</button>
</div>
<!-- Reset to Plan Button (only show if modified) -->
{% if is_modified %}
<div class="d-flex justify-content-center mb-4">
<button class="btn btn-outline-primary" onclick="resetToPlan()">
<i class="bi bi-arrow-counterclockwise"></i> Reset to Plan
</button>
</div>
{% endif %}
<!-- Template Actions -->
<div class="d-flex gap-2 mb-4">
<button class="btn btn-success" onclick="saveAsTemplate()">
<i class="bi bi-save"></i> Save as Template
</button>
<button class="btn btn-primary" onclick="applyTemplate()">
<i class="bi bi-upload"></i> Apply Template
</button>
</div>
<!-- Meal Times -->
{% set meal_times = ["Breakfast", "Lunch", "Dinner", "Snack 1", "Snack 2", "Beverage 1", "Beverage 2"] %}
{% for meal_time in meal_times %}
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">{{ meal_time }}</h5>
</div>
<div class="card-body">
<div id="meals-{{ meal_time|lower|replace(' ', '-') }}">
{% set meals_for_time = [] %}
{% for tracked_meal in tracked_meals %}
{% if tracked_meal.meal_time == meal_time %}
{% set _ = meals_for_time.append(tracked_meal) %}
{% endif %}
{% endfor %}
{% if meals_for_time %}
{% for tracked_meal in meals_for_time %}
<div class="d-flex justify-content-between align-items-center mb-2 p-2 bg-light rounded">
<div>
<strong>{{ tracked_meal.meal.name }}</strong>
{% if tracked_meal.quantity != 1.0 %}
<span class="text-muted">({{ "%.1f"|format(tracked_meal.quantity) }}x)</span>
{% endif %}
</div>
<button class="btn btn-sm btn-outline-danger" onclick="removeMeal({{ tracked_meal.id }})">
<i class="bi bi-trash"></i>
</button>
</div>
{% endfor %}
{% else %}
<p class="text-muted mb-0">No meals tracked</p>
{% endif %}
</div>
<!-- Add Meal Button -->
<button class="btn btn-sm btn-outline-success mt-2" onclick="addMealToTime('{{ meal_time }}')">
<i class="bi bi-plus"></i> Add Meal
</button>
</div>
</div>
{% endfor %}
</div>
<!-- Right Column - Nutrition Totals -->
<div class="col-md-4">
<div class="card sticky-top" style="top: 20px;">
<div class="card-header">
<h5 class="mb-0">Daily Totals</h5>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong class="h4 text-primary">{{ "%.0f"|format(day_totals.calories) }}</strong>
<div class="small text-muted">Calories</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong class="h4 text-success">{{ "%.1f"|format(day_totals.protein) }}g</strong>
<div class="small text-muted">Protein ({{ day_totals.protein_pct }}%)</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong class="h4 text-warning">{{ "%.1f"|format(day_totals.carbs) }}g</strong>
<div class="small text-muted">Carbs ({{ day_totals.carbs_pct }}%)</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong class="h4 text-danger">{{ "%.1f"|format(day_totals.fat) }}g</strong>
<div class="small text-muted">Fat ({{ day_totals.fat_pct }}%)</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong>{{ "%.1f"|format(day_totals.fiber) }}g</strong>
<div class="small text-muted">Fiber</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong>{{ "%.1f"|format(day_totals.net_carbs) }}g</strong>
<div class="small text-muted">Net Carbs</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong>{{ "%.0f"|format(day_totals.sugar) }}g</strong>
<div class="small text-muted">Sugar</div>
</div>
</div>
<div class="col-6 mb-3">
<div class="border rounded p-2">
<strong>{{ "%.0f"|format(day_totals.sodium) }}mg</strong>
<div class="small text-muted">Sodium</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 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">Add Meal to <span id="mealTimeDisplay"></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="addMealTime" name="meal_time">
<input type="hidden" name="person" value="{{ person }}">
<input type="hidden" name="date" value="{{ current_date.isoformat() }}">
<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 }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label">Quantity (multiplier)</label>
<input type="number" step="0.1" class="form-control" name="quantity" value="1.0" min="0.1">
</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="submitAddMeal()">Add Meal</button>
</div>
</div>
</div>
</div>
<!-- Save Template Modal -->
<div class="modal fade" id="saveTemplateModal" tabindex="-1" aria-labelledby="saveTemplateModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="saveTemplateModalLabel">Save Day as Template</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="saveTemplateForm">
<input type="hidden" name="person" value="{{ person }}">
<input type="hidden" name="date" value="{{ current_date.isoformat() }}">
<div class="mb-3">
<label class="form-label">Template Name</label>
<input type="text" class="form-control" name="template_name" required placeholder="e.g., High Protein Day">
</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-success" onclick="submitSaveTemplate()">Save Template</button>
</div>
</div>
</div>
</div>
<!-- Apply Template Modal -->
<div class="modal fade" id="applyTemplateModal" tabindex="-1" aria-labelledby="applyTemplateModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="applyTemplateModalLabel">Apply Template</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="applyTemplateForm">
<input type="hidden" name="person" value="{{ person }}">
<input type="hidden" name="date" value="{{ current_date.isoformat() }}">
<div class="mb-3">
<label class="form-label">Select Template</label>
<select class="form-control" name="template_id" required>
<option value="">Choose template...</option>
{% for template in templates %}
<option value="{{ template.id }}">{{ template.name }}</option>
{% endfor %}
</select>
</div>
<div class="alert alert-warning">
<strong>Warning:</strong> This will replace all meals currently tracked for this day.
</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="submitApplyTemplate()">Apply Template</button>
</div>
</div>
</div>
</div>
<script>
// Date navigation
function navigateDate(date) {
const url = new URL(window.location);
url.searchParams.set('date', date);
window.location.href = url.toString();
}
// Add meal to specific time
function addMealToTime(mealTime) {
document.getElementById('mealTimeDisplay').textContent = mealTime;
document.getElementById('addMealTime').value = mealTime;
new bootstrap.Modal(document.getElementById('addMealModal')).show();
}
// Submit add meal form
async function submitAddMeal() {
const form = document.getElementById('addMealForm');
const formData = new FormData(form);
try {
const response = await fetch('/tracker/add_meal', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('addMealModal')).hide();
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
// Remove meal
async function removeMeal(trackedMealId) {
if (confirm('Remove this meal from the tracker?')) {
try {
const response = await fetch(`/tracker/remove_meal/${trackedMealId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.status === 'success') {
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
}
// Save as template
function saveAsTemplate() {
new bootstrap.Modal(document.getElementById('saveTemplateModal')).show();
}
// Submit save template
async function submitSaveTemplate() {
const form = document.getElementById('saveTemplateForm');
const formData = new FormData(form);
try {
const response = await fetch('/tracker/save_template', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('saveTemplateModal')).hide();
alert('Template saved successfully!');
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
// Apply template
function applyTemplate() {
new bootstrap.Modal(document.getElementById('applyTemplateModal')).show();
}
// Submit apply template
async function submitApplyTemplate() {
const form = document.getElementById('applyTemplateForm');
const formData = new FormData(form);
try {
const response = await fetch('/tracker/apply_template', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('applyTemplateModal')).hide();
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
// Reset to plan
async function resetToPlan() {
if (confirm('Reset this day back to the original plan? All custom changes will be lost.')) {
const formData = new FormData();
formData.append('person', '{{ person }}');
formData.append('date', '{{ current_date.isoformat() }}');
try {
const response = await fetch('/tracker/reset_to_plan', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.status === 'success') {
location.reload();
} else {
alert('Error: ' + result.message);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
}
</script>
{% endblock %}

400
templates/weeklymenu.html Normal file
View File

@@ -0,0 +1,400 @@
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-calendar-week"></i> Weekly Menus</h2>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createWeeklyMenuModal">
<i class="bi bi-plus-circle"></i> Create Weekly Menu
</button>
</div>
<div class="card">
<div class="card-header">
<h5 class="mb-0">Your Weekly Menus</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped" id="weeklyMenusTable">
<thead>
<tr>
<th>Weekly Menu Name</th>
<th>Templates Assigned</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<!-- Weekly menus will be loaded here -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Create Weekly Menu Modal -->
<div class="modal fade" id="createWeeklyMenuModal" tabindex="-1" aria-labelledby="createWeeklyMenuModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createWeeklyMenuModalLabel">Create New Weekly Menu</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="createWeeklyMenuForm">
<div class="modal-body">
<div class="mb-3">
<label for="weeklyMenuName" class="form-label">Weekly Menu Name</label>
<input type="text" class="form-control" id="weeklyMenuName" name="name" required>
</div>
<h6 class="mb-3">Assign Templates to Days</h6>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="monday" class="form-label">Monday</label>
<select class="form-control template-select" id="monday" name="monday">
<option value="">Select template...</option>
{% for template in templates %}
<option value="{{ template.id }}">{{ template.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="tuesday" class="form-label">Tuesday</label>
<select class="form-control template-select" id="tuesday" name="tuesday">
<option value="">Select template...</option>
{% for template in templates %}
<option value="{{ template.id }}">{{ template.name }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="wednesday" class="form-label">Wednesday</label>
<select class="form-control template-select" id="wednesday" name="wednesday">
<option value="">Select template...</option>
{% for template in templates %}
<option value="{{ template.id }}">{{ template.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="thursday" class="form-label">Thursday</label>
<select class="form-control template-select" id="thursday" name="thursday">
<option value="">Select template...</option>
{% for template in templates %}
<option value="{{ template.id }}">{{ template.name }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="friday" class="form-label">Friday</label>
<select class="form-control template-select" id="friday" name="friday">
<option value="">Select template...</option>
{% for template in templates %}
<option value="{{ template.id }}">{{ template.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="saturday" class="form-label">Saturday</label>
<select class="form-control template-select" id="saturday" name="saturday">
<option value="">Select template...</option>
{% for template in templates %}
<option value="{{ template.id }}">{{ template.name }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="sunday" class="form-label">Sunday</label>
<select class="form-control template-select" id="sunday" name="sunday">
<option value="">Select template...</option>
{% for template in templates %}
<option value="{{ template.id }}">{{ template.name }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Create Weekly Menu</button>
</div>
</form>
</div>
</div>
</div>
<!-- Apply Weekly Menu Modal -->
<div class="modal fade" id="applyWeeklyMenuModal" tabindex="-1" aria-labelledby="applyWeeklyMenuModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="applyWeeklyMenuModalLabel">Apply Weekly Menu</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="applyWeeklyMenuForm">
<div class="modal-body">
<p>Apply this weekly menu to your plan starting from the selected week:</p>
<div class="mb-3">
<label for="weekStartDate" class="form-label">Select Week Start Date (Monday)</label>
<input type="date" class="form-control" id="weekStartDate" name="week_start_date" required>
</div>
<div class="mb-3">
<label for="applyPerson" class="form-label">Person</label>
<select class="form-control" id="applyPerson" name="person" required>
<option value="Person A">Person A</option>
<option value="Person B">Person B</option>
</select>
</div>
<div id="applyOverwriteWarning" class="alert alert-warning" style="display: none;">
<i class="bi bi-exclamation-triangle"></i>
<strong>Warning:</strong> There are already meals planned for this week.
Applying the weekly menu will overwrite them.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Apply Weekly Menu</button>
</div>
</form>
</div>
</div>
</div>
<script>
let currentWeeklyMenuId = null;
document.addEventListener('DOMContentLoaded', function() {
loadWeeklyMenus();
// Handle weekly menu creation
document.getElementById('createWeeklyMenuForm').addEventListener('submit', function(e) {
e.preventDefault();
createWeeklyMenu();
});
// Handle weekly menu application
document.getElementById('applyWeeklyMenuForm').addEventListener('submit', function(e) {
e.preventDefault();
applyWeeklyMenu();
});
});
function loadWeeklyMenus() {
fetch('/weeklymenu')
.then(response => response.text())
.then(html => {
// Extract weekly menus data from the HTML response
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const weeklyMenus = JSON.parse(doc.querySelector('script[data-weekly-menus]')?.textContent || '[]');
const tbody = document.querySelector('#weeklyMenusTable tbody');
tbody.innerHTML = '';
if (weeklyMenus.length === 0) {
tbody.innerHTML = '<tr><td colspan="3" class="text-center text-muted">No weekly menus created yet. Click "Create Weekly Menu" to get started.</td></tr>';
return;
}
const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
weeklyMenus.forEach(weeklyMenu => {
const row = document.createElement('tr');
row.innerHTML = `
<td><strong>${weeklyMenu.name}</strong></td>
<td>
${weeklyMenu.weekly_menu_days && weeklyMenu.weekly_menu_days.length > 0 ?
weeklyMenu.weekly_menu_days.map(wmd =>
`<span class="badge bg-secondary me-1">${dayNames[wmd.day_of_week]}: ${wmd.template.name}</span>`
).join('') : 'No templates assigned'}
</td>
<td>
<button class="btn btn-sm btn-outline-primary me-2" onclick="applyWeeklyMenuModal(${weeklyMenu.id})">
<i class="bi bi-play-circle"></i> Apply
</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteWeeklyMenu(${weeklyMenu.id})">
<i class="bi bi-trash"></i> Delete
</button>
</td>
`;
tbody.appendChild(row);
});
})
.catch(error => {
console.error('Error loading weekly menus:', error);
});
}
function createWeeklyMenu() {
const form = document.getElementById('createWeeklyMenuForm');
const formData = new FormData(form);
// Build template assignments string
const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
const assignments = [];
days.forEach((day, index) => {
const templateId = formData.get(day);
if (templateId) {
assignments.push(`${index}:${templateId}`);
}
});
const data = {
name: formData.get('name'),
template_assignments: assignments.join(',')
};
fetch('/weeklymenu/create', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(data)
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('createWeeklyMenuModal')).hide();
form.reset();
loadWeeklyMenus();
} else {
alert('Error creating weekly menu: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error creating weekly menu');
});
}
function applyWeeklyMenuModal(weeklyMenuId) {
currentWeeklyMenuId = weeklyMenuId;
// Set default date to current week's Monday
const today = new Date();
const monday = new Date(today);
monday.setDate(today.getDate() - today.getDay() + 1); // Get Monday of current week
document.getElementById('weekStartDate').value = monday.toISOString().split('T')[0];
document.getElementById('applyOverwriteWarning').style.display = 'none';
new bootstrap.Modal(document.getElementById('applyWeeklyMenuModal')).show();
}
function applyWeeklyMenu() {
const form = document.getElementById('applyWeeklyMenuForm');
const formData = new FormData(form);
const data = {
person: formData.get('person'),
week_start_date: formData.get('week_start_date')
};
fetch(`/weeklymenu/${currentWeeklyMenuId}/apply`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(data)
})
.then(response => response.json())
.then(data => {
if (data.status === 'confirm_overwrite') {
// Show overwrite warning
document.getElementById('applyOverwriteWarning').style.display = 'block';
// Change button text to confirm overwrite
const submitBtn = document.querySelector('#applyWeeklyMenuForm button[type="submit"]');
submitBtn.textContent = 'Overwrite Existing Meals';
submitBtn.onclick = () => confirmApplyOverwrite(data);
} else if (data.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('applyWeeklyMenuModal')).hide();
alert('Weekly menu applied to your plan successfully!');
} else {
alert('Error applying weekly menu: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error applying weekly menu');
});
}
function confirmApplyOverwrite(overwriteData) {
const form = document.getElementById('applyWeeklyMenuForm');
const formData = new FormData(form);
const data = {
person: formData.get('person'),
week_start_date: formData.get('week_start_date'),
confirm_overwrite: 'true'
};
fetch(`/weeklymenu/${currentWeeklyMenuId}/apply`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(data)
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('applyWeeklyMenuModal')).hide();
alert('Weekly menu applied to your plan successfully!');
} else {
alert('Error applying weekly menu: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error applying weekly menu');
});
}
function deleteWeeklyMenu(weeklyMenuId) {
if (!confirm('Are you sure you want to delete this weekly menu?')) {
return;
}
fetch(`/weeklymenu/${weeklyMenuId}`, {
method: 'DELETE'
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
loadWeeklyMenus();
} else {
alert('Error deleting weekly menu: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error deleting weekly menu');
});
}
</script>
<!-- Hidden script to pass weekly menus data -->
<script type="application/json" data-weekly-menus>
{{ weekly_menus|tojson }}
</script>
{% endblock %}