days implemented

This commit is contained in:
2025-09-19 15:36:38 -07:00
parent 7adf7e4d14
commit 7d3e4b3339
5 changed files with 356 additions and 22 deletions

Binary file not shown.

124
main.py
View File

@@ -538,15 +538,15 @@ async def plan_page(request: Request, person: str = "Person A", week_start_date:
if not week_start_date:
today = datetime.now().date()
# Find Monday of current week
week_start_date = (today - timedelta(days=today.weekday())).isoformat()
week_start_date_obj = (today - timedelta(days=today.weekday()))
else:
week_start_date = datetime.fromisoformat(week_start_date).date()
week_start_date_obj = datetime.fromisoformat(week_start_date).date()
# Generate 7 days starting from Monday
days = []
day_names = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
for i in range(7):
day_date = week_start_date + timedelta(days=i)
day_date = week_start_date_obj + timedelta(days=i)
days.append({
'date': day_date,
'name': day_names[i],
@@ -572,15 +572,19 @@ async def plan_page(request: Request, person: str = "Person A", week_start_date:
meals = db.query(Meal).all()
# Calculate previous and next week dates
prev_week = (week_start_date - timedelta(days=7)).isoformat()
next_week = (week_start_date + timedelta(days=7)).isoformat()
prev_week = (week_start_date_obj - timedelta(days=7)).isoformat()
next_week = (week_start_date_obj + timedelta(days=7)).isoformat()
# Debug logging
print(f"DEBUG: days structure: {days}")
print(f"DEBUG: first day: {days[0] if days else 'No days'}")
return templates.TemplateResponse("plan.html", {
"request": request, "person": person, "days": days,
"plans": plans, "daily_totals": daily_totals, "meals": meals,
"week_start_date": week_start_date.isoformat(),
"week_start_date": week_start_date_obj.isoformat(),
"prev_week": prev_week, "next_week": next_week,
"week_range": f"{days[0]['display']} - {days[-1]['display']}, {week_start_date.year}"
"week_range": f"{days[0]['display']} - {days[-1]['display']}, {week_start_date_obj.year}"
})
@app.post("/plan/add")
@@ -660,8 +664,110 @@ async def remove_from_plan(plan_id: int, db: Session = Depends(get_db)):
return {"status": "error", "message": str(e)}
@app.get("/detailed", response_class=HTMLResponse)
async def detailed(request: Request):
return templates.TemplateResponse("detailed.html", {"request": request, "title": "Detailed"})
async def detailed(request: Request, person: str = "Person A", plan_date: str = None, template_id: int = None, db: Session = Depends(get_db)):
from datetime import datetime
if template_id:
# Show template details
template = db.query(Template).filter(Template.id == template_id).first()
if not template:
return templates.TemplateResponse("detailed.html", {
"request": request, "title": "Template Not Found",
"error": "Template not found"
})
template_meals = db.query(TemplateMeal).filter(TemplateMeal.template_id == template_id).all()
# Calculate template nutrition
template_nutrition = {'calories': 0, 'protein': 0, 'carbs': 0, 'fat': 0, 'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0}
meal_details = []
for tm in template_meals:
meal_nutrition = calculate_meal_nutrition(tm.meal, db)
meal_details.append({
'plan': {'meal': tm.meal},
'nutrition': meal_nutrition,
'foods': [] # Template view doesn't show individual foods
})
for key in template_nutrition:
if key in meal_nutrition:
template_nutrition[key] += meal_nutrition[key]
# Calculate percentages
total_cals = template_nutrition['calories']
if total_cals > 0:
template_nutrition['protein_pct'] = round((template_nutrition['protein'] * 4 / total_cals) * 100, 1)
template_nutrition['carbs_pct'] = round((template_nutrition['carbs'] * 4 / total_cals) * 100, 1)
template_nutrition['fat_pct'] = round((template_nutrition['fat'] * 9 / total_cals) * 100, 1)
template_nutrition['net_carbs'] = template_nutrition['carbs'] - template_nutrition['fiber']
return templates.TemplateResponse("detailed.html", {
"request": request, "title": f"Template: {template.name}",
"person": person, "meal_details": meal_details, "day_totals": template_nutrition,
"selected_day": template.name, "view_mode": "template"
})
elif plan_date:
# Show planned day details
plan_date_obj = datetime.fromisoformat(plan_date).date()
plans = db.query(Plan).filter(Plan.person == person, Plan.date == plan_date_obj).all()
meal_details = []
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)
# Get meal foods for detailed breakdown
meal_foods = []
for meal_food in plan.meal.meal_foods:
meal_foods.append({
'food': meal_food.food,
'quantity': meal_food.quantity
})
meal_details.append({
'plan': plan,
'nutrition': meal_nutrition,
'foods': meal_foods
})
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']
selected_day = plan_date_obj.strftime('%A, %B %d, %Y')
return templates.TemplateResponse("detailed.html", {
"request": request, "title": f"Detailed View - {selected_day}",
"person": person, "meal_details": meal_details, "day_totals": day_totals,
"selected_day": selected_day, "view_mode": "day"
})
else:
# Default view - show current week
templates_list = db.query(Template).all()
context = {
"request": request, "title": "Detailed View",
"person": person, "view_mode": "select", "templates": templates_list,
"meal_details": [], # Empty list for default view
"day_totals": { # Default empty nutrition totals
'calories': 0, 'protein': 0, 'carbs': 0, 'fat': 0,
'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0,
'protein_pct': 0, 'carbs_pct': 0, 'fat_pct': 0, 'net_carbs': 0
},
"selected_day": "Select a date or template above"
}
return templates.TemplateResponse("detailed.html", context)
@app.get("/templates", response_class=HTMLResponse)
async def templates_page(request: Request, db: Session = Depends(get_db)):

128
migrate_to_dates.py Normal file
View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""
Migration script to convert Plan.date from String to Date type
and convert existing "DayX" values to actual calendar dates.
"""
import sqlite3
from datetime import datetime, timedelta
def migrate_plans_to_dates():
"""Convert existing DayX plans to actual dates"""
# Connect to database
conn = sqlite3.connect('meal_planner.db')
cursor = conn.cursor()
try:
# Check if migration is needed
cursor.execute("PRAGMA table_info(plans)")
columns = cursor.fetchall()
date_column_type = None
for col in columns:
if col[1] == 'date': # column name
date_column_type = col[2] # column type
break
if date_column_type and 'DATE' in date_column_type.upper():
print("Migration already completed - date column is already DATE type")
return
print("Starting migration from String to Date...")
# Create backup
print("Creating backup of plans table...")
cursor.execute("""
CREATE TABLE plans_backup AS
SELECT * FROM plans
""")
# Add new date column
print("Adding new date column...")
cursor.execute("""
ALTER TABLE plans ADD COLUMN date_new DATE
""")
# Convert DayX to actual dates (starting from today as Day1)
print("Converting DayX values to dates...")
today = datetime.now().date()
# Get all unique day values
cursor.execute("SELECT DISTINCT date FROM plans WHERE date LIKE 'Day%'")
day_values = cursor.fetchall()
for (day_str,) in day_values:
if day_str.startswith('Day'):
try:
day_num = int(day_str[3:]) # Extract number from "Day1", "Day2", etc.
# Convert to date (Day1 = today, Day2 = tomorrow, etc.)
actual_date = today + timedelta(days=day_num - 1)
cursor.execute("""
UPDATE plans
SET date_new = ?
WHERE date = ?
""", (actual_date.isoformat(), day_str))
print(f"Converted {day_str} to {actual_date.isoformat()}")
except (ValueError, IndexError) as e:
print(f"Error converting {day_str}: {e}")
# Handle any non-DayX dates (if they exist)
cursor.execute("""
UPDATE plans
SET date_new = date
WHERE date NOT LIKE 'Day%' AND date_new IS NULL
""")
# Recreate table with new structure (SQLite doesn't support DROP COLUMN with indexes)
print("Recreating table with new structure...")
cursor.execute("""
CREATE TABLE plans_new (
id INTEGER PRIMARY KEY,
person VARCHAR NOT NULL,
date DATE NOT NULL,
meal_id INTEGER NOT NULL REFERENCES meals(id)
)
""")
# Copy data to new table
cursor.execute("""
INSERT INTO plans_new (id, person, date, meal_id)
SELECT id, person, date_new, meal_id FROM plans
""")
# Drop old table and rename new one
cursor.execute("DROP TABLE plans")
cursor.execute("ALTER TABLE plans_new RENAME TO plans")
# Create index on new date column
cursor.execute("CREATE INDEX ix_plans_date ON plans(date)")
conn.commit()
print("Migration completed successfully!")
# Show summary
cursor.execute("SELECT COUNT(*) FROM plans")
total_plans = cursor.fetchone()[0]
print(f"Total plans migrated: {total_plans}")
except Exception as e:
print(f"Migration failed: {e}")
conn.rollback()
# Restore backup if something went wrong
try:
cursor.execute("DROP TABLE IF EXISTS plans")
cursor.execute("ALTER TABLE plans_backup RENAME TO plans")
print("Restored from backup")
except Exception as backup_error:
print(f"Failed to restore backup: {backup_error}")
finally:
conn.close()
if __name__ == "__main__":
migrate_plans_to_dates()

View File

@@ -3,17 +3,39 @@
<div class="row mb-3">
<div class="col-md-6">
<h3>Detailed View for {{ person }}</h3>
<form method="get" class="d-flex">
<input type="hidden" name="person" value="{{ person }}">
<select class="form-control me-2" name="plan_day">
{% for day in days %}
<option value="{{ day }}" {% if day == selected_day %}selected{% endif %}>{{ day }}</option>
{% endfor %}
</select>
<button class="btn btn-primary" type="submit">
<i class="bi bi-search"></i> View
</button>
</form>
<div class="mb-3">
<div class="btn-group" role="group">
<input type="radio" class="btn-check" name="viewMode" id="dayView" autocomplete="off" {% if view_mode == 'day' or not view_mode %}checked{% endif %}>
<label class="btn btn-outline-primary" for="dayView">View Planned Day</label>
<input type="radio" class="btn-check" name="viewMode" id="templateView" autocomplete="off" {% if view_mode == 'template' %}checked{% endif %}>
<label class="btn btn-outline-primary" for="templateView">View Template</label>
</div>
</div>
<div id="daySelector" {% if view_mode == 'template' %}style="display: none;"{% endif %}>
<form method="get" class="d-flex">
<input type="hidden" name="person" value="{{ person }}">
<input type="date" class="form-control me-2" name="plan_date" value="{% if view_mode == 'day' %}{{ plan_date }}{% else %}{{ today|default('') }}{% endif %}" required>
<button class="btn btn-primary" type="submit">
<i class="bi bi-search"></i> View Day
</button>
</form>
</div>
<div id="templateSelector" {% if view_mode != 'template' %}style="display: none;"{% endif %}>
<form method="get" class="d-flex">
<input type="hidden" name="person" value="{{ person }}">
<select class="form-control me-2" name="template_id" required>
<option value="">Select Template...</option>
{% for template in templates %}
<option value="{{ template.id }}" {% if template_id == template.id %}selected{% endif %}>{{ template.name }}</option>
{% endfor %}
</select>
<button class="btn btn-primary" type="submit">
<i class="bi bi-search"></i> View Template
</button>
</form>
</div>
</div>
<div class="col-md-6 text-end">
<h4 class="mb-0">{{ selected_day }}</h4>
@@ -259,8 +281,43 @@
{% if not meal_details %}
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> No meals planned for this date. Go to the Plan tab to add meals.
<i class="bi bi-info-circle"></i>
{% if view_mode == 'template' %}
No template selected. Please select a template to view.
{% else %}
No meals planned for this date. Go to the Plan tab to add meals.
{% endif %}
</div>
{% endif %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Handle view mode switching
const dayViewRadio = document.getElementById('dayView');
const templateViewRadio = document.getElementById('templateView');
const daySelector = document.getElementById('daySelector');
const templateSelector = document.getElementById('templateSelector');
function switchViewMode() {
if (dayViewRadio.checked) {
daySelector.style.display = 'block';
templateSelector.style.display = 'none';
} else {
daySelector.style.display = 'none';
templateSelector.style.display = 'block';
}
}
dayViewRadio.addEventListener('change', switchViewMode);
templateViewRadio.addEventListener('change', switchViewMode);
// Set default date to today if no date is selected
const dateInput = document.querySelector('input[name="plan_date"]');
if (dateInput && !dateInput.value) {
const today = new Date().toISOString().split('T')[0];
dateInput.value = today;
}
});
</script>
{% endblock %}

View File

@@ -289,6 +289,10 @@ function createTemplate() {
function useTemplateModal(templateId) {
currentTemplateId = templateId;
// Set default date to today
const today = new Date().toISOString().split('T')[0];
document.getElementById('startDate').value = today;
document.getElementById('overwriteWarning').style.display = 'none';
new bootstrap.Modal(document.getElementById('useTemplateModal')).show();
}
@@ -298,7 +302,46 @@ function useTemplate() {
const data = {
person: formData.get('person'),
start_day: formData.get('start_day')
start_date: formData.get('start_date')
};
fetch(`/templates/${currentTemplateId}/use`, {
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('overwriteWarning').style.display = 'block';
// Change button text to confirm overwrite
const submitBtn = document.querySelector('#useTemplateForm button[type="submit"]');
submitBtn.textContent = 'Overwrite Existing Meals';
submitBtn.onclick = () => confirmOverwrite(data);
} else if (data.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('useTemplateModal')).hide();
alert('Template applied to your plan successfully!');
} else {
alert('Error using template: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error using template');
});
}
function confirmOverwrite(overwriteData) {
const form = document.getElementById('useTemplateForm');
const formData = new FormData(form);
const data = {
person: formData.get('person'),
start_date: formData.get('start_date'),
confirm_overwrite: 'true'
};
fetch(`/templates/${currentTemplateId}/use`, {