mirror of
https://github.com/sstent/foodplanner.git
synced 2026-01-25 11:11:36 +00:00
starting templates
This commit is contained in:
Binary file not shown.
39
main.py
39
main.py
@@ -192,6 +192,11 @@ def calculate_day_nutrition(plans, db: Session):
|
|||||||
async def root(request: Request):
|
async def root(request: Request):
|
||||||
return templates.TemplateResponse("index.html", {"request": request})
|
return templates.TemplateResponse("index.html", {"request": request})
|
||||||
|
|
||||||
|
# Imports tab
|
||||||
|
@app.get("/imports", response_class=HTMLResponse)
|
||||||
|
async def imports_page(request: Request):
|
||||||
|
return templates.TemplateResponse("imports.html", {"request": request})
|
||||||
|
|
||||||
# Foods tab
|
# Foods tab
|
||||||
@app.get("/foods", response_class=HTMLResponse)
|
@app.get("/foods", response_class=HTMLResponse)
|
||||||
async def foods_page(request: Request, db: Session = Depends(get_db)):
|
async def foods_page(request: Request, db: Session = Depends(get_db)):
|
||||||
@@ -601,37 +606,13 @@ async def remove_from_plan(plan_id: int, db: Session = Depends(get_db)):
|
|||||||
db.rollback()
|
db.rollback()
|
||||||
return {"status": "error", "message": str(e)}
|
return {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
# Detailed planner tab
|
|
||||||
@app.get("/detailed", response_class=HTMLResponse)
|
@app.get("/detailed", response_class=HTMLResponse)
|
||||||
async def detailed_page(request: Request, person: str = "Person A",
|
async def detailed(request: Request):
|
||||||
plan_day: str = None, db: Session = Depends(get_db)):
|
return templates.TemplateResponse("detailed.html", {"request": request, "title": "Detailed"})
|
||||||
|
|
||||||
if not plan_day:
|
@app.get("/templates", response_class=HTMLResponse)
|
||||||
plan_day = "Day1"
|
async def templates_page(request: Request):
|
||||||
|
return templates.TemplateResponse("plans.html", {"request": request, "title": "Templates"})
|
||||||
# Get all plans for the selected day
|
|
||||||
plans = db.query(Plan).filter(Plan.person == person, Plan.date == plan_day).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)
|
|
||||||
|
|
||||||
# Create list of all days for the selector
|
|
||||||
days = [f"Day{i}" for i in range(1, 15)]
|
|
||||||
|
|
||||||
return templates.TemplateResponse("detailed.html", {
|
|
||||||
"request": request, "person": person, "selected_day": plan_day,
|
|
||||||
"meal_details": meal_details, "day_totals": day_totals, "days": days
|
|
||||||
})
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|||||||
@@ -67,15 +67,21 @@
|
|||||||
<i class="bi bi-egg-fried"></i> Meals
|
<i class="bi bi-egg-fried"></i> Meals
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" onclick="location.href='/imports'">
|
||||||
|
<i class="bi bi-upload"></i> Imports
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" onclick="location.href='/plan'">
|
<button class="nav-link" onclick="location.href='/plan'">
|
||||||
<i class="bi bi-calendar-week"></i> Plan
|
<i class="bi bi-calendar-week"></i> Plan
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item">
|
||||||
<button class="nav-link" onclick="location.href='/detailed'">
|
<a class="nav-link" href="/detailed">Detailed</a>
|
||||||
<i class="bi bi-calendar-day"></i> Detailed
|
</li>
|
||||||
</button>
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/templates">Templates</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,11 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-12">
|
||||||
<h3>Bulk Import</h3>
|
<button type="button" class="btn btn-primary btn-lg mb-4" data-bs-toggle="modal" data-bs-target="#addFoodModal">
|
||||||
<form action="/foods/upload" method="post" enctype="multipart/form-data">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">CSV File</label>
|
|
||||||
<input type="file" class="form-control" name="file" accept=".csv" required>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-secondary mb-4">Upload CSV</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-primary btn-lg" data-bs-toggle="modal" data-bs-target="#addFoodModal">
|
|
||||||
<i class="bi bi-plus-circle"></i> Add New Food
|
<i class="bi bi-plus-circle"></i> Add New Food
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-8" id="upload-results" style="display: none;">
|
|
||||||
<div class="alert alert-success">
|
|
||||||
<strong>Upload Results:</strong>
|
|
||||||
<span id="created-count"></span> created,
|
|
||||||
<span id="updated-count"></span> updated
|
|
||||||
<div id="error-list" class="mt-2 text-danger"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-8">
|
|
||||||
<h3>Foods Database</h3>
|
<h3>Foods Database</h3>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
@@ -59,7 +39,7 @@
|
|||||||
<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 }}, '{{ food.name }}', '{{ food.serving_size }}', '{{ food.serving_unit }}', {{ food.calories }}, {{ food.protein }}, {{ food.carbs }}, {{ food.fat }}, {{ food.fiber }}, {{ food.sugar }}, {{ food.sodium }}, {{ food.calcium }})">
|
onclick="editFood({{ food.id }})">
|
||||||
<i class="bi bi-pencil"></i> Edit
|
<i class="bi bi-pencil"></i> Edit
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@@ -229,21 +209,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Edit food function
|
// Edit food function
|
||||||
function editFood(id, name, serving_size, serving_unit, calories, protein, carbs, fat, fiber, sugar, sodium, calcium) {
|
function editFood(foodId) {
|
||||||
document.getElementById('edit_food_id').value = id;
|
// This function will be implemented after page load using the food data
|
||||||
document.getElementById('edit_name').value = name;
|
// For now, we'll use a placeholder that will be replaced by the actual implementation
|
||||||
document.getElementById('edit_serving_size').value = serving_size;
|
console.log("Edit food called for ID:", foodId);
|
||||||
document.getElementById('edit_serving_unit').value = serving_unit;
|
|
||||||
document.getElementById('edit_calories').value = calories;
|
|
||||||
document.getElementById('edit_protein').value = protein;
|
|
||||||
document.getElementById('edit_carbs').value = carbs;
|
|
||||||
document.getElementById('edit_fat').value = fat;
|
|
||||||
document.getElementById('edit_fiber').value = fiber;
|
|
||||||
document.getElementById('edit_sugar').value = sugar;
|
|
||||||
document.getElementById('edit_sodium').value = sodium;
|
|
||||||
document.getElementById('edit_calcium').value = calcium;
|
|
||||||
|
|
||||||
new bootstrap.Modal(document.getElementById('editFoodModal')).show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit food form (add or edit)
|
// Submit food form (add or edit)
|
||||||
@@ -302,50 +271,46 @@ async function deleteSelectedFoods() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSV upload handling
|
// Initialize editFood function after page load
|
||||||
document.querySelector('form[action="/foods/upload"]').addEventListener('submit', async (e) => {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
e.preventDefault();
|
const foodsData = {
|
||||||
const form = e.target;
|
{% for food in foods %}
|
||||||
const submitBtn = form.querySelector('button[type="submit"]');
|
{{ food.id }}: {
|
||||||
const resultsDiv = document.getElementById('upload-results');
|
name: '{{ food.name | replace("'", "\\'") }}',
|
||||||
|
serving_size: '{{ food.serving_size | replace("'", "\\'") }}',
|
||||||
|
serving_unit: '{{ food.serving_unit | replace("'", "\\'") }}',
|
||||||
|
calories: {{ food.calories }},
|
||||||
|
protein: {{ food.protein }},
|
||||||
|
carbs: {{ food.carbs }},
|
||||||
|
fat: {{ food.fat }},
|
||||||
|
fiber: {{ food.fiber }},
|
||||||
|
sugar: {{ food.sugar }},
|
||||||
|
sodium: {{ food.sodium }},
|
||||||
|
calcium: {{ food.calcium }}
|
||||||
|
},
|
||||||
|
{% endfor %}
|
||||||
|
};
|
||||||
|
|
||||||
submitBtn.disabled = true;
|
// Implement the actual editFood function
|
||||||
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status"></span> Uploading...';
|
window.editFood = function(foodId) {
|
||||||
|
const food = foodsData[foodId];
|
||||||
|
if (!food) return;
|
||||||
|
|
||||||
try {
|
document.getElementById('edit_food_id').value = foodId;
|
||||||
const formData = new FormData(form);
|
document.getElementById('edit_name').value = food.name;
|
||||||
const response = await fetch('/foods/upload', {
|
document.getElementById('edit_serving_size').value = food.serving_size;
|
||||||
method: 'POST',
|
document.getElementById('edit_serving_unit').value = food.serving_unit;
|
||||||
body: formData
|
document.getElementById('edit_calories').value = food.calories;
|
||||||
});
|
document.getElementById('edit_protein').value = food.protein;
|
||||||
|
document.getElementById('edit_carbs').value = food.carbs;
|
||||||
|
document.getElementById('edit_fat').value = food.fat;
|
||||||
|
document.getElementById('edit_fiber').value = food.fiber;
|
||||||
|
document.getElementById('edit_sugar').value = food.sugar;
|
||||||
|
document.getElementById('edit_sodium').value = food.sodium;
|
||||||
|
document.getElementById('edit_calcium').value = food.calcium;
|
||||||
|
|
||||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
new bootstrap.Modal(document.getElementById('editFoodModal')).show();
|
||||||
|
};
|
||||||
const results = await response.json();
|
|
||||||
|
|
||||||
resultsDiv.style.display = 'block';
|
|
||||||
document.getElementById('created-count').textContent = results.created || 0;
|
|
||||||
document.getElementById('updated-count').textContent = results.updated || 0;
|
|
||||||
|
|
||||||
if (results.errors?.length > 0) {
|
|
||||||
document.getElementById('error-list').innerHTML =
|
|
||||||
`<strong>Errors (${results.errors.length}):</strong><br>` + results.errors.join('<br>');
|
|
||||||
} else {
|
|
||||||
document.getElementById('error-list').innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.created || results.updated) {
|
|
||||||
setTimeout(() => window.location.reload(), 2000);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
resultsDiv.style.display = 'block';
|
|
||||||
resultsDiv.querySelector('.alert').className = 'alert alert-danger';
|
|
||||||
document.getElementById('error-list').innerHTML =
|
|
||||||
`<strong>Upload Failed:</strong> ${error.message}`;
|
|
||||||
} finally {
|
|
||||||
submitBtn.disabled = false;
|
|
||||||
submitBtn.textContent = 'Upload CSV';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,22 +1,11 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-12">
|
||||||
<h3>Bulk Import Meals</h3>
|
<button type="button" class="btn btn-primary btn-lg mb-4" data-bs-toggle="modal" data-bs-target="#addMealModal">
|
||||||
<form action="/meals/upload" method="post" enctype="multipart/form-data">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">CSV File</label>
|
|
||||||
<input type="file" class="form-control" name="file" accept=".csv" required>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-secondary mb-4">Upload CSV</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-primary btn-lg" data-bs-toggle="modal" data-bs-target="#addMealModal">
|
|
||||||
<i class="bi bi-plus-circle"></i> Create New Meal
|
<i class="bi bi-plus-circle"></i> Create New Meal
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-8">
|
|
||||||
<h3>Meals</h3>
|
<h3>Meals</h3>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
@@ -336,46 +325,5 @@ async function deleteSelectedMeals() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Meal CSV upload handling
|
|
||||||
document.querySelector('form[action="/meals/upload"]').addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const form = e.target;
|
|
||||||
const submitBtn = form.querySelector('button[type="submit"]');
|
|
||||||
const resultsDiv = document.createElement('div');
|
|
||||||
resultsDiv.className = 'alert alert-info mt-3';
|
|
||||||
form.parentNode.insertBefore(resultsDiv, form.nextSibling);
|
|
||||||
|
|
||||||
submitBtn.disabled = true;
|
|
||||||
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Uploading...';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const formData = new FormData(form);
|
|
||||||
const response = await fetch('/meals/upload', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
const results = await response.json();
|
|
||||||
|
|
||||||
if (results.errors?.length > 0) {
|
|
||||||
resultsDiv.className = 'alert alert-danger';
|
|
||||||
resultsDiv.innerHTML = `<strong>Errors (${results.errors.length}):</strong><br>` +
|
|
||||||
results.errors.join('<br>');
|
|
||||||
} else {
|
|
||||||
resultsDiv.className = 'alert alert-success';
|
|
||||||
resultsDiv.innerHTML = `Successfully created ${results.created} meals, updated ${results.updated}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.created || results.updated) {
|
|
||||||
setTimeout(() => window.location.reload(), 2000);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
resultsDiv.className = 'alert alert-danger';
|
|
||||||
resultsDiv.textContent = `Upload failed: ${error.message}`;
|
|
||||||
} finally {
|
|
||||||
submitBtn.disabled = false;
|
|
||||||
submitBtn.textContent = 'Upload CSV';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user