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