mirror of
https://github.com/sstent/foodplanner.git
synced 2025-12-06 08:01:47 +00:00
372 lines
16 KiB
HTML
372 lines
16 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h3>Weekly Plan for {{ person }}</h3>
|
|
<div class="d-flex align-items-center">
|
|
<button class="btn btn-outline-secondary me-2" onclick="navigateWeek('{{ prev_week }}')">
|
|
<i class="bi bi-chevron-left"></i> Previous Week
|
|
</button>
|
|
<div class="input-group me-2" style="width: 200px;">
|
|
<input type="date" class="form-control" id="weekPicker" value="{{ week_start_date }}">
|
|
<button class="btn btn-outline-primary" onclick="jumpToWeek()">Go</button>
|
|
</div>
|
|
<button class="btn btn-outline-secondary" onclick="navigateWeek('{{ next_week }}')">
|
|
<i class="bi bi-chevron-right"></i> Next Week
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-info mb-3">
|
|
<strong>Week: {{ week_range }}</strong>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 10%;">Day</th>
|
|
<th style="width: 25%;">Meals</th>
|
|
<th style="width: 10%;">Calories</th>
|
|
<th style="width: 12%;">Protein</th>
|
|
<th style="width: 12%;">Carbs</th>
|
|
<th style="width: 12%;">Fat</th>
|
|
<th style="width: 9%;">Net Carbs</th>
|
|
<th style="width: 10%;">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for day in days %}
|
|
<tr>
|
|
<td>
|
|
<strong>{{ day.name }}</strong><br>
|
|
<small class="text-muted">{{ day.display }}</small>
|
|
</td>
|
|
<td>
|
|
{% set meals_by_time = {} %}
|
|
{% for plan in plans[day.date.isoformat()] %}
|
|
{% if plan.meal_time not in meals_by_time %}
|
|
{% set _ = meals_by_time.update({plan.meal_time: []}) %}
|
|
{% endif %}
|
|
{% set _ = meals_by_time[plan.meal_time].append(plan) %}
|
|
{% endfor %}
|
|
|
|
{% for meal_time in ["Breakfast", "Lunch", "Dinner", "Snack 1", "Snack 2", "Beverage 1", "Beverage 2"] %}
|
|
<div class="mb-1">
|
|
<strong>{{ meal_time }}:</strong>
|
|
{% if meals_by_time[meal_time] %}
|
|
{% for plan in meals_by_time[meal_time] %}
|
|
<span class="badge bg-info me-1">{{ plan.meal.name }}</span>
|
|
{% endfor %}
|
|
{% else %}
|
|
<em class="text-muted">No meals</em>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
{% if not plans[day.date.isoformat()] %}
|
|
<em class="text-muted">No meals</em>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ "%.0f"|format(daily_totals[day.date.isoformat()].calories or 0) }}</td>
|
|
<td>{{ "%.1f"|format(daily_totals[day.date.isoformat()].protein or 0) }}g<br><small class="text-muted">({{ daily_totals[day.date.isoformat()].protein_pct or 0 }}%)</small></td>
|
|
<td>{{ "%.1f"|format(daily_totals[day.date.isoformat()].carbs or 0) }}g<br><small class="text-muted">({{ daily_totals[day.date.isoformat()].carbs_pct or 0 }}%)</small></td>
|
|
<td>{{ "%.1f"|format(daily_totals[day.date.isoformat()].fat or 0) }}g<br><small class="text-muted">({{ daily_totals[day.date.isoformat()].fat_pct or 0 }}%)</small></td>
|
|
<td>{{ "%.1f"|format(daily_totals[day.date.isoformat()].net_carbs or 0) }}g</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-primary" onclick="editDay('{{ day.date.isoformat() }}', '{{ person }}')">
|
|
<i class="bi bi-pencil"></i> Edit
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-success" onclick="quickAddMeal('{{ day.date.isoformat() }}')">
|
|
<i class="bi bi-plus"></i> Add
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Quick Add Meal Modal -->
|
|
<div class="modal fade" id="quickAddMealModal" tabindex="-1" aria-labelledby="quickAddMealModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="quickAddMealModalLabel">Add Meal to <span id="quickAddDay"></span></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="quickAddMealForm">
|
|
<input type="hidden" id="quickAddPlanDay" name="plan_date">
|
|
<input type="hidden" name="person" value="{{ person }}">
|
|
<div class="mb-3">
|
|
<label class="form-label">Select Meal</label>
|
|
<select class="form-control" name="meal_id" required>
|
|
<option value="">Choose meal...</option>
|
|
{% for meal in meals %}
|
|
<option value="{{ meal.id }}">{{ meal.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</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>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="submitQuickAddMeal()">Add Meal</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Day Modal -->
|
|
<div class="modal fade" id="editDayModal" tabindex="-1" aria-labelledby="editDayModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="editDayModalLabel">Edit Meals for <span id="editDayName"></span></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6><i class="bi bi-list"></i> Current Meals</h6>
|
|
<div id="currentDayMeals" class="mb-3">
|
|
<!-- This will be populated dynamically -->
|
|
</div>
|
|
|
|
<h6><i class="bi bi-plus-circle"></i> Add Meal</h6>
|
|
<form id="addMealToDayForm">
|
|
<input type="hidden" id="editDayPerson" name="person">
|
|
<input type="hidden" id="editDayValue" name="plan_date">
|
|
<div class="mb-3">
|
|
<select class="form-control" id="mealSelectForDay" name="meal_id" required>
|
|
<option value="">Choose meal...</option>
|
|
{% for meal in meals %}
|
|
<option value="{{ meal.id }}">{{ meal.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</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()">
|
|
<i class="bi bi-plus"></i> Add Selected Meal
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6><i class="bi bi-calculator"></i> Day Preview</h6>
|
|
<div id="dayNutritionPreview" class="p-3 bg-light rounded">
|
|
<!-- This will show live nutrition totals -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentEditingDay = null;
|
|
let currentEditingPerson = null;
|
|
|
|
// Week navigation function
|
|
function navigateWeek(weekStartDate) {
|
|
const url = new URL(window.location);
|
|
url.searchParams.set('week_start_date', weekStartDate);
|
|
window.location.href = url.toString();
|
|
}
|
|
|
|
// Jump to specific week
|
|
function jumpToWeek() {
|
|
const weekPicker = document.getElementById('weekPicker');
|
|
const selectedDate = weekPicker.value;
|
|
if (selectedDate) {
|
|
navigateWeek(selectedDate);
|
|
}
|
|
}
|
|
|
|
// Quick add meal function
|
|
function quickAddMeal(date) {
|
|
// Format date for display
|
|
const dateObj = new Date(date);
|
|
const options = { weekday: 'long', month: 'short', day: 'numeric' };
|
|
document.getElementById('quickAddDay').textContent = dateObj.toLocaleDateString('en-US', options);
|
|
document.getElementById('quickAddPlanDay').value = date;
|
|
new bootstrap.Modal(document.getElementById('quickAddMealModal')).show();
|
|
}
|
|
|
|
function submitQuickAddMeal() {
|
|
const form = document.getElementById('quickAddMealForm');
|
|
const formData = new FormData(form);
|
|
|
|
fetch('/plan/add', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
bootstrap.Modal.getInstance(document.getElementById('quickAddMealModal')).hide();
|
|
location.reload();
|
|
} else {
|
|
alert('Error: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('Error: ' + error.message);
|
|
});
|
|
}
|
|
|
|
// Edit day function
|
|
async function editDay(date, person) {
|
|
currentEditingDay = date;
|
|
currentEditingPerson = person;
|
|
|
|
// Format date for display
|
|
const dateObj = new Date(date);
|
|
const options = { weekday: 'long', month: 'short', day: 'numeric', year: 'numeric' };
|
|
document.getElementById('editDayName').textContent = dateObj.toLocaleDateString('en-US', options);
|
|
document.getElementById('editDayPerson').value = person;
|
|
document.getElementById('editDayValue').value = date;
|
|
|
|
// Load current meals for this date
|
|
await loadCurrentDayMeals(person, date);
|
|
|
|
new bootstrap.Modal(document.getElementById('editDayModal')).show();
|
|
}
|
|
|
|
// Load current meals for the date
|
|
async function loadCurrentDayMeals(person, date) {
|
|
try {
|
|
const response = await fetch(`/plan/${person}/${date}`);
|
|
const data = await response.json(); // Now data contains both meals and day_totals
|
|
const meals = data.meals;
|
|
const dayTotals = data.day_totals;
|
|
|
|
const container = document.getElementById('currentDayMeals');
|
|
if (meals.length === 0) {
|
|
container.innerHTML = '<em class="text-muted">No meals planned</em>';
|
|
} else {
|
|
container.innerHTML = meals.map(meal => `
|
|
<div class="d-flex justify-content-between align-items-center mb-2 p-2 bg-light rounded">
|
|
<span><strong>${meal.meal_time}:</strong> ${meal.meal_name}</span>
|
|
<button class="btn btn-sm btn-outline-danger" onclick="removeMealFromDay(${meal.id})">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// Update nutrition preview with actual totals
|
|
updateDayNutritionPreview(dayTotals);
|
|
} catch (error) {
|
|
console.error('Error loading day meals:', error);
|
|
}
|
|
}
|
|
|
|
// Add meal to current day
|
|
async function addMealToCurrentDay() {
|
|
const form = document.getElementById('addMealToDayForm');
|
|
const formData = new FormData(form);
|
|
|
|
try {
|
|
const response = await fetch('/plan/add', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.status === 'success') {
|
|
// Reset meal selector
|
|
document.getElementById('mealSelectForDay').value = '';
|
|
|
|
// Reload current meals
|
|
await loadCurrentDayMeals(currentEditingPerson, currentEditingDay);
|
|
} else {
|
|
alert('Error adding meal: ' + result.message);
|
|
}
|
|
} catch (error) {
|
|
alert('Error adding meal: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// Remove meal from day
|
|
async function removeMealFromDay(planId) {
|
|
if (confirm('Remove this meal from the day?')) {
|
|
try {
|
|
const response = await fetch(`/plan/${planId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.status === 'success') {
|
|
await loadCurrentDayMeals(currentEditingPerson, currentEditingDay);
|
|
} else {
|
|
alert('Error removing meal: ' + result.message);
|
|
}
|
|
} catch (error) {
|
|
alert('Error removing meal: ' + error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update nutrition preview (simplified - you could enhance this with actual calculations)
|
|
function updateDayNutritionPreview(dayTotals) {
|
|
const preview = document.getElementById('dayNutritionPreview');
|
|
preview.innerHTML = `
|
|
<div class="row text-center">
|
|
<div class="col-6 mb-2">
|
|
<strong>${dayTotals.calories ? dayTotals.calories.toFixed(0) : 0}</strong><br>
|
|
<small>Calories</small>
|
|
</div>
|
|
<div class="col-6 mb-2">
|
|
<strong>${dayTotals.protein ? dayTotals.protein.toFixed(1) : 0}g</strong><br>
|
|
<small>Protein (${dayTotals.protein_pct ? dayTotals.protein_pct.toFixed(1) : 0}%)</small>
|
|
</div>
|
|
<div class="col-6 mb-2">
|
|
<strong>${dayTotals.carbs ? dayTotals.carbs.toFixed(1) : 0}g</strong><br>
|
|
<small>Carbs (${dayTotals.carbs_pct ? dayTotals.carbs_pct.toFixed(1) : 0}%)</small>
|
|
</div>
|
|
<div class="col-6 mb-2">
|
|
<strong>${dayTotals.fat ? dayTotals.fat.toFixed(1) : 0}g</strong><br>
|
|
<small>Fat (${dayTotals.fat_pct ? dayTotals.fat_pct.toFixed(1) : 0}%)</small>
|
|
</div>
|
|
<div class="col-12">
|
|
<strong>Net Carbs:</strong> ${dayTotals.net_carbs ? dayTotals.net_carbs.toFixed(1) : 0}g
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Handle modal cleanup
|
|
document.getElementById('editDayModal').addEventListener('hidden.bs.modal', function () {
|
|
// Reload the page to refresh all totals
|
|
location.reload();
|
|
});
|
|
</script>
|
|
{% endblock %} |