diff --git a/main.py b/main.py
index 6f8fc72..e16c443 100644
--- a/main.py
+++ b/main.py
@@ -1617,6 +1617,28 @@ async def get_weekly_menus_api(db: Session = Depends(get_db)):
))
return results
+
+@app.get("/weeklymenu/{weekly_menu_id}", response_model=WeeklyMenuDetail)
+async def get_weekly_menu_detail(weekly_menu_id: int, db: Session = Depends(get_db)):
+ """API endpoint to get a specific weekly menu with template details."""
+ weekly_menu = db.query(WeeklyMenu).options(joinedload(WeeklyMenu.weekly_menu_days).joinedload(WeeklyMenuDay.template)).filter(WeeklyMenu.id == weekly_menu_id).first()
+
+ if not weekly_menu:
+ raise HTTPException(status_code=404, detail="Weekly menu not found")
+
+ day_details = [
+ WeeklyMenuDayDetail(
+ day_of_week=wmd.day_of_week,
+ template_id=wmd.template_id,
+ template_name=wmd.template.name if wmd.template else "Unknown"
+ ) for wmd in weekly_menu.weekly_menu_days
+ ]
+ return WeeklyMenuDetail(
+ id=weekly_menu.id,
+ name=weekly_menu.name,
+ weekly_menu_days=day_details
+ )
+
@app.post("/weeklymenu/create")
async def create_weekly_menu(request: Request, db: Session = Depends(get_db)):
"""Create a new weekly menu with template assignments."""
@@ -1725,6 +1747,83 @@ async def apply_weekly_menu(weekly_menu_id: int, request: Request, db: Session =
logging.error(f"Error applying weekly menu: {e}")
return {"status": "error", "message": str(e)}
+
+@app.put("/weeklymenu/{weekly_menu_id}")
+async def update_weekly_menu(weekly_menu_id: int, request: Request, db: Session = Depends(get_db)):
+ """Update an existing weekly menu with new template assignments."""
+ try:
+ form_data = await request.form()
+ name = form_data.get("name")
+ template_assignments_str = form_data.get("template_assignments")
+
+ weekly_menu = db.query(WeeklyMenu).filter(WeeklyMenu.id == weekly_menu_id).first()
+ if not weekly_menu:
+ return {"status": "error", "message": "Weekly menu not found"}
+
+ if not name:
+ return {"status": "error", "message": "Weekly menu name is required"}
+
+ # Check for duplicate name if changed
+ if name != weekly_menu.name:
+ existing_weekly_menu = db.query(WeeklyMenu).filter(WeeklyMenu.name == name).first()
+ if existing_weekly_menu:
+ return {"status": "error", "message": f"Weekly menu with name '{name}' already exists"}
+
+ weekly_menu.name = name
+
+ # Clear existing weekly menu days
+ db.query(WeeklyMenuDay).filter(WeeklyMenuDay.weekly_menu_id == weekly_menu_id).delete()
+ db.flush()
+
+ # Process new template assignments
+ if template_assignments_str:
+ assignments = template_assignments_str.split(',')
+ for assignment in assignments:
+ day_of_week_str, template_id_str = assignment.split(':', 1)
+ day_of_week = int(day_of_week_str)
+ template_id = int(template_id_str)
+
+ # Check if template exists
+ template = db.query(Template).filter(Template.id == template_id).first()
+ if not template:
+ raise HTTPException(status_code=400, detail=f"Template with ID {template_id} not found.")
+
+ weekly_menu_day = WeeklyMenuDay(
+ weekly_menu_id=weekly_menu.id,
+ day_of_week=day_of_week,
+ template_id=template_id
+ )
+ db.add(weekly_menu_day)
+
+ db.commit()
+ return {"status": "success", "message": "Weekly menu updated successfully"}
+
+ except Exception as e:
+ db.rollback()
+ logging.error(f"Error updating weekly menu: {e}")
+ 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:
+ weekly_menu = db.query(WeeklyMenu).filter(WeeklyMenu.id == weekly_menu_id).first()
+ if not weekly_menu:
+ return {"status": "error", "message": "Weekly menu not found"}
+
+ # Delete associated weekly menu days
+ db.query(WeeklyMenuDay).filter(WeeklyMenuDay.weekly_menu_id == weekly_menu_id).delete()
+
+ db.delete(weekly_menu)
+ db.commit()
+
+ return {"status": "success"}
+ except Exception as e:
+ db.rollback()
+ logging.error(f"Error deleting weekly menu: {e}")
+ return {"status": "error", "message": str(e)}
+
# Plan tab
@app.get("/plan", response_class=HTMLResponse)
async def plan_page(request: Request, person: str = "Sarah", week_start_date: str = None, db: Session = Depends(get_db)):
@@ -1899,7 +1998,7 @@ async def detailed(request: Request, person: str = "Sarah", plan_date: str = Non
logging.info(f"DEBUG: Detailed page requested with person={person}, plan_date={plan_date}, template_id={template_id}")
# Get all templates for the dropdown
- templates = db.query(Template).order_by(Template.name).all()
+ templates_list = db.query(Template).order_by(Template.name).all()
if template_id:
# Show template details
@@ -1907,11 +2006,11 @@ async def detailed(request: Request, person: str = "Sarah", plan_date: str = Non
template = db.query(Template).filter(Template.id == template_id).first()
if not template:
logging.error(f"DEBUG: Template with id {template_id} not found")
- return templates.TemplateResponse("detailed.html", {
+ return templates.TemplateResponse(request, "detailed.html", {
"request": request, "title": "Template Not Found",
"error": "Template not found",
"day_totals": {},
- "templates": templates,
+ "templates": templates_list,
"person": person
})
@@ -1948,11 +2047,11 @@ async def detailed(request: Request, person: str = "Sarah", plan_date: str = Non
"meal_details": meal_details,
"day_totals": template_nutrition,
"person": person,
- "templates": templates,
+ "templates": templates_list,
"selected_template_id": template_id
}
logging.info(f"DEBUG: Rendering template details with context: {context}")
- return templates.TemplateResponse("detailed.html", context)
+ return templates.TemplateResponse(request, "detailed.html", context)
# If no plan_date is provided, default to today's date
if not plan_date:
@@ -1966,7 +2065,7 @@ async def detailed(request: Request, person: str = "Sarah", plan_date: str = Non
"request": request, "title": "Invalid Date",
"error": "Invalid date format. Please use YYYY-MM-DD.",
"day_totals": {},
- "templates": templates,
+ "templates": templates_list,
"person": person
})
@@ -2008,7 +2107,7 @@ async def detailed(request: Request, person: str = "Sarah", plan_date: str = Non
"day_totals": day_totals,
"person": person,
"plan_date": plan_date_obj,
- "templates": templates
+ "templates": templates_list
}
# If no meals are planned, add a message
diff --git a/templates/detailed.html b/templates/detailed.html
index 01f1df4..d962d4f 100644
--- a/templates/detailed.html
+++ b/templates/detailed.html
@@ -29,7 +29,7 @@
View Template
diff --git a/templates/weeklymenu.html b/templates/weeklymenu.html
index 7b125a9..74b0715 100644
--- a/templates/weeklymenu.html
+++ b/templates/weeklymenu.html
@@ -297,7 +297,9 @@
let currentWeeklyMenuId = null;
document.addEventListener('DOMContentLoaded', function() {
- loadWeeklyMenus();
+ // Use the data that's already available in the template via the data attribute
+ const weeklyMenusData = JSON.parse(document.querySelector('script[data-weekly-menus]').textContent);
+ loadWeeklyMenus(weeklyMenusData);
// Handle weekly menu creation
document.getElementById('createWeeklyMenuForm').addEventListener('submit', function(e) {
@@ -318,53 +320,41 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
-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 = '| No weekly menus created yet. Click "Create Weekly Menu" to get started. |
';
- return;
- }
-
- const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
-
- weeklyMenus.forEach(weeklyMenu => {
- const row = document.createElement('tr');
- row.innerHTML = `
- ${weeklyMenu.name} |
-
- ${weeklyMenu.weekly_menu_days && weeklyMenu.weekly_menu_days.length > 0 ?
- weeklyMenu.weekly_menu_days.map(wmd =>
- `${dayNames[wmd.day_of_week]}: ${wmd.template.name}`
- ).join('') : 'No templates assigned'}
- |
-
-
-
-
- |
- `;
- tbody.appendChild(row);
- });
- })
- .catch(error => {
- console.error('Error loading weekly menus:', error);
- });
+function loadWeeklyMenus(weeklyMenus) {
+ const tbody = document.querySelector('#weeklyMenusTable tbody');
+ tbody.innerHTML = '';
+
+ if (!weeklyMenus || weeklyMenus.length === 0) {
+ tbody.innerHTML = '| No weekly menus created yet. Click "Create Weekly Menu" to get started. |
';
+ return;
+ }
+
+ const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
+
+ weeklyMenus.forEach(weeklyMenu => {
+ const row = document.createElement('tr');
+ row.innerHTML = `
+ \${weeklyMenu.name} |
+
+ \${weeklyMenu.weekly_menu_days && weeklyMenu.weekly_menu_days.length > 0 ?
+ weeklyMenu.weekly_menu_days.map(wmd =>
+ \`\${dayNames[wmd.day_of_week]}: \${wmd.template_name}\`
+ ).join('') : 'No templates assigned'}
+ |
+
+
+
+
+ |
+ `;
+ tbody.appendChild(row);
+ });
}
function createWeeklyMenu() {
@@ -518,32 +508,26 @@ function editWeeklyMenuModal(weeklyMenuId) {
fetch(`/weeklymenu/${weeklyMenuId}`)
.then(response => response.json())
.then(data => {
- if (data.status === 'success') {
- // Populate the form
- document.getElementById('editWeeklyMenuId').value = data.id;
- document.getElementById('editWeeklyMenuName').value = data.name;
-
- // Reset all selects
- const selects = ['editMonday', 'editTuesday', 'editWednesday', 'editThursday', 'editFriday', 'editSaturday', 'editSunday'];
- selects.forEach(selectId => {
- document.getElementById(selectId).value = '';
- });
-
- // Set template assignments
- const assignments = data.template_assignments;
- if (assignments[0]) document.getElementById('editMonday').value = assignments[0];
- if (assignments[1]) document.getElementById('editTuesday').value = assignments[1];
- if (assignments[2]) document.getElementById('editWednesday').value = assignments[2];
- if (assignments[3]) document.getElementById('editThursday').value = assignments[3];
- if (assignments[4]) document.getElementById('editFriday').value = assignments[4];
- if (assignments[5]) document.getElementById('editSaturday').value = assignments[5];
- if (assignments[6]) document.getElementById('editSunday').value = assignments[6];
-
- // Show modal
- new bootstrap.Modal(document.getElementById('editWeeklyMenuModal')).show();
- } else {
- alert('Error loading weekly menu: ' + data.message);
- }
+ // Populate the form
+ document.getElementById('editWeeklyMenuId').value = data.id;
+ document.getElementById('editWeeklyMenuName').value = data.name;
+
+ // Reset all selects
+ const selects = ['editMonday', 'editTuesday', 'editWednesday', 'editThursday', 'editFriday', 'editSaturday', 'editSunday'];
+ selects.forEach(selectId => {
+ document.getElementById(selectId).value = '';
+ });
+
+ // Set template assignments based on weekly menu days
+ data.weekly_menu_days.forEach(wmd => {
+ const dayIndex = wmd.day_of_week;
+ const templateId = wmd.template_id;
+ const selectId = `edit${['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'][dayIndex]}`;
+ document.getElementById(selectId).value = templateId;
+ });
+
+ // Show modal
+ new bootstrap.Modal(document.getElementById('editWeeklyMenuModal')).show();
})
.catch(error => {
console.error('Error:', error);
@@ -554,20 +538,40 @@ function editWeeklyMenuModal(weeklyMenuId) {
function editWeeklyMenu() {
const form = document.getElementById('editWeeklyMenuForm');
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 selectId = `edit${day.charAt(0).toUpperCase() + day.slice(1)}`;
+ const templateId = document.getElementById(selectId).value;
+ if (templateId) {
+ assignments.push(`${index}:${templateId}`);
+ }
+ });
- fetch('/weeklymenu/edit', {
- method: 'POST',
+ const weeklyMenuId = document.getElementById('editWeeklyMenuId').value;
+ const data = {
+ name: formData.get('name'),
+ template_assignments: assignments.join(',')
+ };
+
+ fetch(`/weeklymenu/${weeklyMenuId}`, {
+ method: 'PUT',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
- body: new URLSearchParams(formData)
+ body: new URLSearchParams(data)
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
bootstrap.Modal.getInstance(document.getElementById('editWeeklyMenuModal')).hide();
form.reset();
- loadWeeklyMenus();
+ // Reload the weekly menus using the same data that's already available in the template
+ const weeklyMenusData = JSON.parse(document.querySelector('script[data-weekly-menus]').textContent);
+ loadWeeklyMenus(weeklyMenusData);
} else {
alert('Error updating weekly menu: ' + data.message);
}
diff --git a/tests/test_detailed.py b/tests/test_detailed.py
index 5f6b088..723d167 100644
--- a/tests/test_detailed.py
+++ b/tests/test_detailed.py
@@ -42,7 +42,7 @@ def client_fixture(session):
def test_detailed_page_no_params(client):
response = client.get("/detailed")
assert response.status_code == 200
- assert "Please provide either a plan date or a template ID." in response.text
+ assert "Detailed View for" in response.text
def test_detailed_page_default_date(client, session):
@@ -70,7 +70,7 @@ def test_detailed_page_default_date(client, session):
response = client.get("/detailed?person=Sarah")
assert response.status_code == 200
# The apostrophe is HTML-escaped in the template
- assert "Sarah's Detailed Plan for" in response.text
+ assert "Sarah's Detailed Plan for" in response.text
assert test_date.strftime('%B %d, %Y') in response.text # Check if today's date appears in the formatted date
assert "Fruit Snack" in response.text
@@ -99,7 +99,7 @@ def test_detailed_page_with_plan_date(client, session):
response = client.get(f"/detailed?person=Sarah&plan_date={test_date.isoformat()}")
assert response.status_code == 200
# The apostrophe is HTML-escaped in the template
- assert "Sarah's Detailed Plan for" in response.text
+ assert "Sarah's Detailed Plan for" in response.text
assert "Fruit Snack" in response.text
@@ -139,7 +139,7 @@ def test_detailed_page_with_invalid_plan_date(client):
response = client.get(f"/detailed?person=Sarah&plan_date={invalid_date.isoformat()}")
assert response.status_code == 200
# The apostrophe is HTML-escaped in the template
- assert "Sarah's Detailed Plan for" in response.text
+ assert "Sarah's Detailed Plan for" in response.text
assert "No meals planned for this day." in response.text
@@ -164,10 +164,10 @@ def test_detailed_page_template_dropdown(client, session):
assert response.status_code == 200
# Check that the response contains template selection UI elements
- assert "Select Template..." in response.text
+ assert "View Template" in response.text
assert "Morning Boost" in response.text
assert "Evening Energy" in response.text
# Verify that template IDs are present in the dropdown options
- assert f'value="{template1.id}"' in response.text
- assert f'value="{template2.id}"' in response.text
+ assert f'href="/detailed?template_id={template1.id}"' in response.text
+ assert f'href="/detailed?template_id={template2.id}"' in response.text