mirror of
https://github.com/sstent/foodplanner.git
synced 2026-02-15 23:35:29 +00:00
openfood test
This commit is contained in:
Binary file not shown.
132
main.py
132
main.py
@@ -14,7 +14,9 @@ from typing import List, Optional
|
|||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
import os
|
import os
|
||||||
import csv
|
import csv
|
||||||
|
import requests
|
||||||
from fastapi import File, UploadFile
|
from fastapi import File, UploadFile
|
||||||
|
import openfoodfacts
|
||||||
|
|
||||||
# Database setup - Use SQLite for easier setup
|
# Database setup - Use SQLite for easier setup
|
||||||
DATABASE_URL = "sqlite:///./meal_planner.db"
|
DATABASE_URL = "sqlite:///./meal_planner.db"
|
||||||
@@ -44,6 +46,7 @@ class Food(Base):
|
|||||||
sugar = Column(Float, default=0)
|
sugar = Column(Float, default=0)
|
||||||
sodium = Column(Float, default=0)
|
sodium = Column(Float, default=0)
|
||||||
calcium = Column(Float, default=0)
|
calcium = Column(Float, default=0)
|
||||||
|
source = Column(String, default="manual") # manual, csv, openfoodfacts
|
||||||
|
|
||||||
class Meal(Base):
|
class Meal(Base):
|
||||||
__tablename__ = "meals"
|
__tablename__ = "meals"
|
||||||
@@ -73,6 +76,7 @@ class Plan(Base):
|
|||||||
person = Column(String, index=True) # Person A or Person B
|
person = Column(String, index=True) # Person A or Person B
|
||||||
date = Column(Date, index=True) # Store actual calendar dates
|
date = Column(Date, index=True) # Store actual calendar dates
|
||||||
meal_id = Column(Integer, ForeignKey("meals.id"))
|
meal_id = Column(Integer, ForeignKey("meals.id"))
|
||||||
|
meal_time = Column(String, default="Breakfast") # Breakfast, Lunch, Dinner, Snack 1, Snack 2, Beverage 1, Beverage 2
|
||||||
|
|
||||||
meal = relationship("Meal")
|
meal = relationship("Meal")
|
||||||
|
|
||||||
@@ -109,6 +113,7 @@ class FoodCreate(BaseModel):
|
|||||||
sugar: float = 0
|
sugar: float = 0
|
||||||
sodium: float = 0
|
sodium: float = 0
|
||||||
calcium: float = 0
|
calcium: float = 0
|
||||||
|
source: str = "manual"
|
||||||
|
|
||||||
class FoodResponse(BaseModel):
|
class FoodResponse(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
@@ -123,6 +128,7 @@ class FoodResponse(BaseModel):
|
|||||||
sugar: float
|
sugar: float
|
||||||
sodium: float
|
sodium: float
|
||||||
calcium: float
|
calcium: float
|
||||||
|
source: str
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
@@ -257,9 +263,13 @@ async def bulk_upload_foods(file: UploadFile = File(...), db: Session = Depends(
|
|||||||
# Update existing food
|
# Update existing food
|
||||||
for key, value in food_data.items():
|
for key, value in food_data.items():
|
||||||
setattr(existing, key, value)
|
setattr(existing, key, value)
|
||||||
|
# Ensure source is set for existing foods
|
||||||
|
if not existing.source:
|
||||||
|
existing.source = "csv"
|
||||||
stats['updated'] += 1
|
stats['updated'] += 1
|
||||||
else:
|
else:
|
||||||
# Create new food
|
# Create new food
|
||||||
|
food_data['source'] = "csv"
|
||||||
food = Food(**food_data)
|
food = Food(**food_data)
|
||||||
db.add(food)
|
db.add(food)
|
||||||
stats['created'] += 1
|
stats['created'] += 1
|
||||||
@@ -281,13 +291,14 @@ async def add_food(request: Request, db: Session = Depends(get_db),
|
|||||||
protein: float = Form(...), carbs: float = Form(...),
|
protein: float = Form(...), carbs: float = Form(...),
|
||||||
fat: float = Form(...), fiber: float = Form(0),
|
fat: float = Form(...), fiber: float = Form(0),
|
||||||
sugar: float = Form(0), sodium: float = Form(0),
|
sugar: float = Form(0), sodium: float = Form(0),
|
||||||
calcium: float = Form(0)):
|
calcium: float = Form(0), source: str = Form("manual")):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
food = Food(
|
food = Food(
|
||||||
name=name, serving_size=serving_size, serving_unit=serving_unit,
|
name=name, serving_size=serving_size, serving_unit=serving_unit,
|
||||||
calories=calories, protein=protein, carbs=carbs, fat=fat,
|
calories=calories, protein=protein, carbs=carbs, fat=fat,
|
||||||
fiber=fiber, sugar=sugar, sodium=sodium, calcium=calcium
|
fiber=fiber, sugar=sugar, sodium=sodium, calcium=calcium,
|
||||||
|
source=source
|
||||||
)
|
)
|
||||||
db.add(food)
|
db.add(food)
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -303,7 +314,8 @@ async def edit_food(request: Request, db: Session = Depends(get_db),
|
|||||||
calories: float = Form(...), protein: float = Form(...),
|
calories: float = Form(...), protein: float = Form(...),
|
||||||
carbs: float = Form(...), fat: float = Form(...),
|
carbs: float = Form(...), fat: float = Form(...),
|
||||||
fiber: float = Form(0), sugar: float = Form(0),
|
fiber: float = Form(0), sugar: float = Form(0),
|
||||||
sodium: float = Form(0), calcium: float = Form(0)):
|
sodium: float = Form(0), calcium: float = Form(0),
|
||||||
|
source: str = Form("manual")):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
food = db.query(Food).filter(Food.id == food_id).first()
|
food = db.query(Food).filter(Food.id == food_id).first()
|
||||||
@@ -321,6 +333,7 @@ async def edit_food(request: Request, db: Session = Depends(get_db),
|
|||||||
food.sugar = sugar
|
food.sugar = sugar
|
||||||
food.sodium = sodium
|
food.sodium = sodium
|
||||||
food.calcium = calcium
|
food.calcium = calcium
|
||||||
|
food.source = source
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
return {"status": "success", "message": "Food updated successfully"}
|
return {"status": "success", "message": "Food updated successfully"}
|
||||||
@@ -339,6 +352,110 @@ async def delete_foods(food_ids: dict = Body(...), db: Session = Depends(get_db)
|
|||||||
db.rollback()
|
db.rollback()
|
||||||
return {"status": "error", "message": str(e)}
|
return {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
|
@app.get("/foods/search_openfoodfacts")
|
||||||
|
async def search_openfoodfacts(query: str, limit: int = 10):
|
||||||
|
"""Search OpenFoodFacts database for foods using the official SDK"""
|
||||||
|
try:
|
||||||
|
# Initialize OpenFoodFacts API with User-Agent
|
||||||
|
api = openfoodfacts.API(user_agent="FoodPlanner/1.0")
|
||||||
|
|
||||||
|
# Perform text search
|
||||||
|
search_result = api.product.text_search(query, page_size=limit)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
if search_result and 'products' in search_result:
|
||||||
|
for product in search_result['products']:
|
||||||
|
# Extract nutritional information
|
||||||
|
nutriments = product.get('nutriments', {})
|
||||||
|
|
||||||
|
# Get serving size
|
||||||
|
serving_size = product.get('serving_size', '100g')
|
||||||
|
if not serving_size or serving_size == '':
|
||||||
|
serving_size = '100g'
|
||||||
|
|
||||||
|
# Extract serving quantity and unit
|
||||||
|
serving_quantity = 100 # default to 100g
|
||||||
|
serving_unit = 'g'
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try to parse serving size (e.g., "30g", "1 cup")
|
||||||
|
import re
|
||||||
|
match = re.match(r'(\d+(?:\.\d+)?)\s*([a-zA-Z]+)', serving_size)
|
||||||
|
if match:
|
||||||
|
serving_quantity = float(match.group(1))
|
||||||
|
serving_unit = match.group(2)
|
||||||
|
else:
|
||||||
|
# If no match, assume 100g
|
||||||
|
serving_quantity = 100
|
||||||
|
serving_unit = 'g'
|
||||||
|
except:
|
||||||
|
serving_quantity = 100
|
||||||
|
serving_unit = 'g'
|
||||||
|
|
||||||
|
# Calculate per serving values (SDK returns per 100g values)
|
||||||
|
def get_nutrient_value(key, default=0):
|
||||||
|
value = nutriments.get(key, default)
|
||||||
|
if value:
|
||||||
|
try:
|
||||||
|
# Convert to float if it's a string or other type
|
||||||
|
numeric_value = float(value)
|
||||||
|
if serving_quantity != 100:
|
||||||
|
# Convert to per serving
|
||||||
|
numeric_value = (numeric_value * serving_quantity) / 100
|
||||||
|
return round(numeric_value, 2)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return default
|
||||||
|
return default
|
||||||
|
|
||||||
|
food_data = {
|
||||||
|
'name': product.get('product_name', product.get('product_name_en', 'Unknown Product')),
|
||||||
|
'serving_size': str(serving_quantity),
|
||||||
|
'serving_unit': serving_unit,
|
||||||
|
'calories': get_nutrient_value('energy-kcal_100g', 0),
|
||||||
|
'protein': get_nutrient_value('proteins_100g', 0),
|
||||||
|
'carbs': get_nutrient_value('carbohydrates_100g', 0),
|
||||||
|
'fat': get_nutrient_value('fat_100g', 0),
|
||||||
|
'fiber': get_nutrient_value('fiber_100g', 0),
|
||||||
|
'sugar': get_nutrient_value('sugars_100g', 0),
|
||||||
|
'sodium': get_nutrient_value('sodium_100g', 0),
|
||||||
|
'calcium': get_nutrient_value('calcium_100g', 0),
|
||||||
|
'source': 'openfoodfacts',
|
||||||
|
'openfoodfacts_id': product.get('code', ''),
|
||||||
|
'brand': product.get('brands', ''),
|
||||||
|
'image_url': product.get('image_url', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
results.append(food_data)
|
||||||
|
|
||||||
|
return {"status": "success", "results": results}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "message": f"OpenFoodFacts search failed: {str(e)}"}
|
||||||
|
|
||||||
|
@app.post("/foods/add_openfoodfacts")
|
||||||
|
async def add_openfoodfacts_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), openfoodfacts_id: str = Form("")):
|
||||||
|
|
||||||
|
try:
|
||||||
|
food = Food(
|
||||||
|
name=name, serving_size=serving_size, serving_unit=serving_unit,
|
||||||
|
calories=calories, protein=protein, carbs=carbs, fat=fat,
|
||||||
|
fiber=fiber, sugar=sugar, sodium=sodium, calcium=calcium,
|
||||||
|
source="openfoodfacts"
|
||||||
|
)
|
||||||
|
db.add(food)
|
||||||
|
db.commit()
|
||||||
|
return {"status": "success", "message": "Food added from OpenFoodFacts successfully"}
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
return {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
# Meals tab
|
# Meals tab
|
||||||
@app.get("/meals", response_class=HTMLResponse)
|
@app.get("/meals", response_class=HTMLResponse)
|
||||||
async def meals_page(request: Request, db: Session = Depends(get_db)):
|
async def meals_page(request: Request, db: Session = Depends(get_db)):
|
||||||
@@ -590,12 +707,12 @@ async def plan_page(request: Request, person: str = "Person A", week_start_date:
|
|||||||
@app.post("/plan/add")
|
@app.post("/plan/add")
|
||||||
async def add_to_plan(request: Request, person: str = Form(...),
|
async def add_to_plan(request: Request, person: str = Form(...),
|
||||||
plan_date: str = Form(...), meal_id: int = Form(...),
|
plan_date: str = Form(...), meal_id: int = Form(...),
|
||||||
db: Session = Depends(get_db)):
|
meal_time: str = Form("Breakfast"), db: Session = Depends(get_db)):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
plan_date_obj = datetime.fromisoformat(plan_date).date()
|
plan_date_obj = datetime.fromisoformat(plan_date).date()
|
||||||
plan = Plan(person=person, date=plan_date_obj, meal_id=meal_id)
|
plan = Plan(person=person, date=plan_date_obj, meal_id=meal_id, meal_time=meal_time)
|
||||||
db.add(plan)
|
db.add(plan)
|
||||||
db.commit()
|
db.commit()
|
||||||
return {"status": "success"}
|
return {"status": "success"}
|
||||||
@@ -616,7 +733,8 @@ async def get_day_plan(person: str, date: str, db: Session = Depends(get_db)):
|
|||||||
"id": plan.id,
|
"id": plan.id,
|
||||||
"meal_id": plan.meal_id,
|
"meal_id": plan.meal_id,
|
||||||
"meal_name": plan.meal.name,
|
"meal_name": plan.meal.name,
|
||||||
"meal_type": plan.meal.meal_type
|
"meal_type": plan.meal.meal_type,
|
||||||
|
"meal_time": plan.meal_time
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -639,7 +757,7 @@ async def update_day_plan(request: Request, person: str = Form(...),
|
|||||||
|
|
||||||
# Add new plans
|
# Add new plans
|
||||||
for meal_id in meal_id_list:
|
for meal_id in meal_id_list:
|
||||||
plan = Plan(person=person, date=plan_date, meal_id=meal_id)
|
plan = Plan(person=person, date=plan_date, meal_id=meal_id, meal_time="Breakfast")
|
||||||
db.add(plan)
|
db.add(plan)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|||||||
63
migrate_db_schema.py
Normal file
63
migrate_db_schema.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Database migration script to add new fields for food sources and meal times.
|
||||||
|
Run this script to update the database schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
from sqlalchemy import create_engine, text
|
||||||
|
from main import Base, engine
|
||||||
|
|
||||||
|
def migrate_database():
|
||||||
|
"""Add new columns to existing tables"""
|
||||||
|
|
||||||
|
# Connect to database
|
||||||
|
conn = sqlite3.connect('./meal_planner.db')
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check if source column exists in foods table
|
||||||
|
cursor.execute("PRAGMA table_info(foods)")
|
||||||
|
columns = cursor.fetchall()
|
||||||
|
column_names = [col[1] for col in columns]
|
||||||
|
|
||||||
|
if 'source' not in column_names:
|
||||||
|
print("Adding 'source' column to foods table...")
|
||||||
|
cursor.execute("ALTER TABLE foods ADD COLUMN source TEXT DEFAULT 'manual'")
|
||||||
|
print("✓ Added source column to foods table")
|
||||||
|
|
||||||
|
# Check if meal_time column exists in plans table
|
||||||
|
cursor.execute("PRAGMA table_info(plans)")
|
||||||
|
columns = cursor.fetchall()
|
||||||
|
column_names = [col[1] for col in columns]
|
||||||
|
|
||||||
|
if 'meal_time' not in column_names:
|
||||||
|
print("Adding 'meal_time' column to plans table...")
|
||||||
|
cursor.execute("ALTER TABLE plans ADD COLUMN meal_time TEXT DEFAULT 'Breakfast'")
|
||||||
|
print("✓ Added meal_time column to plans table")
|
||||||
|
|
||||||
|
# Update existing records to have proper source values
|
||||||
|
print("Updating existing food records with source information...")
|
||||||
|
|
||||||
|
# Set source to 'csv' for foods that might have been imported
|
||||||
|
# Note: This is a heuristic - you may need to adjust based on your data
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE foods
|
||||||
|
SET source = 'csv'
|
||||||
|
WHERE name LIKE '%(%'
|
||||||
|
AND source = 'manual'
|
||||||
|
""")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
print("✓ Migration completed successfully!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Migration failed: {e}")
|
||||||
|
conn.rollback()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Starting database migration...")
|
||||||
|
migrate_database()
|
||||||
|
print("Migration script completed.")
|
||||||
@@ -4,3 +4,4 @@ sqlalchemy>=2.0.24
|
|||||||
#psycopg2-binary==2.9.9
|
#psycopg2-binary==2.9.9
|
||||||
python-multipart==0.0.6
|
python-multipart==0.0.6
|
||||||
jinja2==3.1.2
|
jinja2==3.1.2
|
||||||
|
openfoodfacts>=3.0.0
|
||||||
@@ -145,6 +145,9 @@
|
|||||||
<div class="meal-header">
|
<div class="meal-header">
|
||||||
<span>
|
<span>
|
||||||
<i class="bi bi-egg-fried"></i> {{ meal_detail.plan.meal.name }} - {{ meal_detail.plan.meal.meal_type.title() }}
|
<i class="bi bi-egg-fried"></i> {{ meal_detail.plan.meal.name }} - {{ meal_detail.plan.meal.meal_type.title() }}
|
||||||
|
{% if meal_detail.plan.meal_time %}
|
||||||
|
<small class="text-muted">({{ meal_detail.plan.meal_time }})</small>
|
||||||
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
<span class="badge bg-light text-dark">{{ "%.0f"|format(meal_detail.nutrition.calories) }} cal</span>
|
<span class="badge bg-light text-dark">{{ "%.0f"|format(meal_detail.nutrition.calories) }} cal</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-4">
|
||||||
<h3>Food Import</h3>
|
<h3>Food Import</h3>
|
||||||
<form action="/foods/upload" method="post" enctype="multipart/form-data">
|
<form action="/foods/upload" method="post" enctype="multipart/form-data">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -12,7 +12,21 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-4">
|
||||||
|
<h3>OpenFoodFacts Search</h3>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Search for Food</label>
|
||||||
|
<input type="text" class="form-control" id="offSearch" placeholder="e.g., apple, banana, pizza">
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary mb-4" onclick="searchOpenFoodFacts()">Search</button>
|
||||||
|
|
||||||
|
<div id="offResults" class="mt-3" style="display: none;">
|
||||||
|
<h6>Search Results:</h6>
|
||||||
|
<div id="offResultsList" class="list-group"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
<h3>Meal Import</h3>
|
<h3>Meal Import</h3>
|
||||||
<form action="/meals/upload" method="post" enctype="multipart/form-data">
|
<form action="/meals/upload" method="post" enctype="multipart/form-data">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -79,5 +93,107 @@ document.querySelectorAll('form').forEach(form => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// OpenFoodFacts search functionality
|
||||||
|
async function searchOpenFoodFacts() {
|
||||||
|
const query = document.getElementById('offSearch').value.trim();
|
||||||
|
if (!query) {
|
||||||
|
alert('Please enter a search term');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultsDiv = document.getElementById('offResults');
|
||||||
|
const resultsList = document.getElementById('offResultsList');
|
||||||
|
|
||||||
|
// Show loading
|
||||||
|
resultsDiv.style.display = 'block';
|
||||||
|
resultsList.innerHTML = '<div class="text-center"><div class="spinner-border" role="status"></div> Searching...</div>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/foods/search_openfoodfacts?query=${encodeURIComponent(query)}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
displayOpenFoodFactsResults(data.results);
|
||||||
|
} else {
|
||||||
|
resultsList.innerHTML = `<div class="alert alert-danger">Error: ${data.message}</div>`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
resultsList.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayOpenFoodFactsResults(results) {
|
||||||
|
const resultsList = document.getElementById('offResultsList');
|
||||||
|
|
||||||
|
if (results.length === 0) {
|
||||||
|
resultsList.innerHTML = '<div class="alert alert-info">No results found</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
results.forEach((food, index) => {
|
||||||
|
html += `
|
||||||
|
<div class="list-group-item">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h6 class="mb-1">${food.name}</h6>
|
||||||
|
<p class="mb-1 text-muted small">
|
||||||
|
${food.serving_size}${food.serving_unit} |
|
||||||
|
${food.calories} cal |
|
||||||
|
P: ${food.protein}g, C: ${food.carbs}g, F: ${food.fat}g
|
||||||
|
</p>
|
||||||
|
${food.brand ? `<small class="text-muted">Brand: ${food.brand}</small>` : ''}
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-sm btn-success" onclick="addOpenFoodFactsFood(${index})">
|
||||||
|
<i class="bi bi-plus-circle"></i> Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
resultsList.innerHTML = html;
|
||||||
|
|
||||||
|
// Store results for later use
|
||||||
|
window.offSearchResults = results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addOpenFoodFactsFood(index) {
|
||||||
|
const food = window.offSearchResults[index];
|
||||||
|
if (!food) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
Object.keys(food).forEach(key => {
|
||||||
|
if (key !== 'image_url' && key !== 'openfoodfacts_id' && key !== 'brand') {
|
||||||
|
formData.append(key, food[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch('/foods/add_openfoodfacts', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.status === 'success') {
|
||||||
|
alert('Food added successfully!');
|
||||||
|
// Optionally reload the page or update UI
|
||||||
|
} else {
|
||||||
|
alert('Error adding food: ' + result.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error adding food: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow Enter key to trigger search
|
||||||
|
document.getElementById('offSearch').addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
searchOpenFoodFacts();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -43,7 +43,9 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% for plan in plans[day.date.isoformat()] %}
|
{% for plan in plans[day.date.isoformat()] %}
|
||||||
<span class="badge bg-secondary me-1">{{ plan.meal.name }}</span>
|
<span class="badge bg-secondary me-1" title="{{ plan.meal_time }}">
|
||||||
|
<small>{{ plan.meal_time }}:</small> {{ plan.meal.name }}
|
||||||
|
</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if not plans[day.date.isoformat()] %}
|
{% if not plans[day.date.isoformat()] %}
|
||||||
<em class="text-muted">No meals</em>
|
<em class="text-muted">No meals</em>
|
||||||
@@ -89,6 +91,18 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Meal Time</label>
|
||||||
|
<select class="form-control" name="meal_time" required>
|
||||||
|
<option value="Breakfast">Breakfast</option>
|
||||||
|
<option value="Lunch">Lunch</option>
|
||||||
|
<option value="Dinner">Dinner</option>
|
||||||
|
<option value="Snack 1">Snack 1</option>
|
||||||
|
<option value="Snack 2">Snack 2</option>
|
||||||
|
<option value="Beverage 1">Beverage 1</option>
|
||||||
|
<option value="Beverage 2">Beverage 2</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@@ -127,6 +141,18 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Meal Time</label>
|
||||||
|
<select class="form-control" id="mealTimeSelectForDay" name="meal_time" required>
|
||||||
|
<option value="Breakfast">Breakfast</option>
|
||||||
|
<option value="Lunch">Lunch</option>
|
||||||
|
<option value="Dinner">Dinner</option>
|
||||||
|
<option value="Snack 1">Snack 1</option>
|
||||||
|
<option value="Snack 2">Snack 2</option>
|
||||||
|
<option value="Beverage 1">Beverage 1</option>
|
||||||
|
<option value="Beverage 2">Beverage 2</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<button type="button" class="btn btn-success btn-sm" onclick="addMealToCurrentDay()">
|
<button type="button" class="btn btn-success btn-sm" onclick="addMealToCurrentDay()">
|
||||||
<i class="bi bi-plus"></i> Add Selected Meal
|
<i class="bi bi-plus"></i> Add Selected Meal
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user