mirror of
https://github.com/sstent/foodplanner.git
synced 2026-02-20 17:55:24 +00:00
fix remove food, efit food. assed playwright tests
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-primary btn-lg mb-4" data-bs-toggle="modal" data-bs-target="#addFoodModal">
|
||||
<button type="button" class="btn btn-primary btn-lg mb-4" data-bs-toggle="modal" data-bs-target="#addFoodModal" data-testid="add-new-food">
|
||||
<i class="bi bi-plus-circle"></i> Add New Food
|
||||
</button>
|
||||
|
||||
@@ -27,21 +27,22 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for food in foods %}
|
||||
<tr>
|
||||
<td><input type="checkbox" name="selected_foods" value="{{ food.id }}"></td>
|
||||
<td>{{ food.name }}</td>
|
||||
<td>{{ food.brand or '' }}</td>
|
||||
<td>{{ food.serving_size }} {{ food.serving_unit }}</td>
|
||||
<td>{{ "%.2f"|format(food.calories) }}</td>
|
||||
<td>{{ "%.2f"|format(food.protein) }}g</td>
|
||||
<td>{{ "%.2f"|format(food.carbs) }}g</td>
|
||||
<td>{{ "%.2f"|format(food.fat) }}g</td>
|
||||
<td>{{ "%.2f"|format(food.fiber) }}g</td>
|
||||
<td>{{ "%.2f"|format(food.sodium) }}mg</td>
|
||||
<td>{{ "%.2f"|format(food.calcium) }}mg</td>
|
||||
{% set food_name_slug = food.name|lower|replace(' ', '-')|replace(',', '')|replace('.', '') %}
|
||||
<tr data-testid="food-row-{{ food_name_slug }}-{{ loop.index }}">
|
||||
<td><input type="checkbox" name="selected_foods" value="{{ food.id }}" data-testid="select-food-{{ food_name_slug }}-{{ loop.index }}"></td>
|
||||
<td data-testid="food-name-{{ food_name_slug }}-{{ loop.index }}">{{ food.name }}</td>
|
||||
<td data-testid="food-brand-{{ food_name_slug }}-{{ loop.index }}">{{ food.brand or '' }}</td>
|
||||
<td data-testid="food-serving-{{ food_name_slug }}-{{ loop.index }}">{{ food.serving_size }} {{ food.serving_unit }}</td>
|
||||
<td data-testid="food-calories-{{ food_name_slug }}-{{ loop.index }}">{{ "%.2f"|format(food.calories) }}</td>
|
||||
<td data-testid="food-protein-{{ food_name_slug }}-{{ loop.index }}">{{ "%.2f"|format(food.protein) }}g</td>
|
||||
<td data-testid="food-carbs-{{ food_name_slug }}-{{ loop.index }}">{{ "%.2f"|format(food.carbs) }}g</td>
|
||||
<td data-testid="food-fat-{{ food_name_slug }}-{{ loop.index }}">{{ "%.2f"|format(food.fat) }}g</td>
|
||||
<td data-testid="food-fiber-{{ food_name_slug }}-{{ loop.index }}">{{ "%.2f"|format(food.fiber) }}g</td>
|
||||
<td data-testid="food-sodium-{{ food_name_slug }}-{{ loop.index }}">{{ "%.2f"|format(food.sodium) }}mg</td>
|
||||
<td data-testid="food-calcium-{{ food_name_slug }}-{{ loop.index }}">{{ "%.2f"|format(food.calcium) }}mg</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
onclick="editFood({{ food.id }})">
|
||||
onclick="editFood({{ food.id }})" data-testid="edit-food-{{ food_name_slug }}-{{ loop.index }}">
|
||||
<i class="bi bi-pencil"></i> Edit
|
||||
</button>
|
||||
</td>
|
||||
@@ -50,7 +51,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button class="btn btn-danger mt-3" onclick="deleteSelectedFoods()">
|
||||
<button class="btn btn-danger mt-3" onclick="deleteSelectedFoods()" data-testid="delete-selected-foods">
|
||||
<i class="bi bi-trash"></i> Delete Selected Foods
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-primary btn-lg mb-4" data-bs-toggle="modal" data-bs-target="#addMealModal">
|
||||
<button type="button" class="btn btn-primary btn-lg mb-4" data-bs-toggle="modal" data-bs-target="#addMealModal" data-testid="create-new-meal">
|
||||
<i class="bi bi-plus-circle"></i> Create New Meal
|
||||
</button>
|
||||
|
||||
@@ -19,14 +19,16 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for meal in meals %}
|
||||
<tr>
|
||||
<td><input type="checkbox" name="selected_meals" value="{{ meal.id }}"></td>
|
||||
<td>{{ meal.name }}</td>
|
||||
{% set meal_name_slug = meal.name|lower|replace(' ', '-')|replace(',', '')|replace('.', '') %}
|
||||
<tr data-testid="meal-row-{{ meal_name_slug }}-{{ loop.index }}">
|
||||
<td><input type="checkbox" name="selected_meals" value="{{ meal.id }}" data-testid="select-meal-{{ meal_name_slug }}-{{ loop.index }}"></td>
|
||||
<td data-testid="meal-name-{{ meal_name_slug }}-{{ loop.index }}">{{ meal.name }}</td>
|
||||
<td>
|
||||
{% if meal.meal_foods %}
|
||||
<ul class="list-unstyled">
|
||||
{% for meal_food in meal.meal_foods %}
|
||||
<li>{{ meal_food.quantity }}g of {{ meal_food.food.name }} ({{ (meal_food.quantity / meal_food.food.serving_size)|round(2) }} servings of {{ meal_food.food.serving_size }}{{ meal_food.food.serving_unit }})</li>
|
||||
{% set food_name_slug = meal_food.food.name|lower|replace(' ', '-')|replace(',', '')|replace('.', '') %}
|
||||
<li data-testid="meal-food-{{ meal_name_slug }}-{{ loop.index }}-{{ food_name_slug }}-{{ loop.index }}">{{ meal_food.quantity }}g of {{ meal_food.food.name }} ({{ (meal_food.quantity / meal_food.food.serving_size)|round(2) }} servings of {{ meal_food.food.serving_size }}{{ meal_food.food.serving_unit }})</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
@@ -35,11 +37,11 @@
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
onclick="manageMeal({{ meal.id }})">
|
||||
onclick="manageMeal({{ meal.id }})" data-testid="manage-meal-{{ meal_name_slug }}-{{ loop.index }}">
|
||||
<i class="bi bi-pencil-square"></i> Manage
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-info"
|
||||
onclick="cloneMeal({{ meal.id }})">
|
||||
onclick="cloneMeal({{ meal.id }})" data-testid="clone-meal-{{ meal_name_slug }}-{{ loop.index }}">
|
||||
<i class="bi bi-front"></i> Clone
|
||||
</button>
|
||||
</td>
|
||||
@@ -48,7 +50,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button class="btn btn-danger mt-3" onclick="deleteSelectedMeals()">
|
||||
<button class="btn btn-danger mt-3" onclick="deleteSelectedMeals()" data-testid="delete-selected-meals">
|
||||
<i class="bi bi-trash"></i> Delete Selected Meals
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -44,4 +44,45 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Function to add food dynamically to the edit meal list
|
||||
async function addFoodToTrackedMeal() {
|
||||
const foodSelect = document.getElementById('foodSelectTrackedMeal');
|
||||
const foodId = foodSelect.value;
|
||||
const foodName = foodSelect.options[foodSelect.selectedIndex].text;
|
||||
const quantityInput = document.querySelector('#addFoodToTrackedMealForm input[name="quantity"]');
|
||||
const quantity = quantityInput.value;
|
||||
|
||||
if (!foodId || !quantity) {
|
||||
alert('Please select a food and enter a quantity.');
|
||||
return;
|
||||
}
|
||||
|
||||
const container = document.getElementById('editMealFoodsList');
|
||||
// If "No foods added yet" message exists, remove it
|
||||
const noFoodsMessage = container.querySelector('em');
|
||||
if (noFoodsMessage) {
|
||||
noFoodsMessage.remove();
|
||||
}
|
||||
|
||||
const foodDiv = document.createElement('div');
|
||||
foodDiv.className = 'd-flex justify-content-between align-items-center mb-2';
|
||||
foodDiv.innerHTML = `
|
||||
<span>${foodName}</span>
|
||||
<div class="input-group w-50">
|
||||
<input type="number" step="0.01" class="form-control form-control-sm" value="${parseFloat(quantity).toFixed(2)}" data-food-id="${foodId}" data-item-id="0" data-is-custom="true" data-testid="food-quantity-${foodId}">
|
||||
<span class="input-group-text">g</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeFoodFromTrackedMeal(${foodId}, true)" data-testid="delete-food-${foodId}">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(foodDiv);
|
||||
|
||||
// Clear the form for next entry
|
||||
foodSelect.value = '';
|
||||
quantityInput.value = '100'; // Reset to default
|
||||
}
|
||||
</script>
|
||||
@@ -4,35 +4,38 @@
|
||||
<div class="col-md-8">
|
||||
<!-- Date Navigation -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<button class="btn btn-outline-secondary" onclick="navigateDate('{{ prev_date }}')">
|
||||
<button class="btn btn-outline-secondary" onclick="navigateDate('{{ prev_date }}')" data-testid="navigate-yesterday">
|
||||
<i class="bi bi-chevron-left"></i> Yesterday
|
||||
</button>
|
||||
<div class="text-center">
|
||||
<h3>{{ current_date.strftime('%A, %B %d, %Y') }}</h3>
|
||||
<h3 data-testid="current-date-display">{{ current_date.strftime('%A, %B %d, %Y') }}</h3>
|
||||
{% if is_modified %}
|
||||
<span class="badge bg-warning text-dark">Custom</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">As Planned</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary" onclick="navigateDate('{{ next_date }}')">
|
||||
<button class="btn btn-outline-secondary" onclick="navigateDate('{{ next_date }}')" data-testid="navigate-tomorrow">
|
||||
Tomorrow <i class="bi bi-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Template Actions -->
|
||||
<div class="d-flex justify-content-center gap-2 mb-4">
|
||||
<button class="btn btn-success" onclick="saveAsTemplate()">
|
||||
<button class="btn btn-success" onclick="saveAsTemplate()" data-testid="save-as-template">
|
||||
<i class="bi bi-save"></i> Save as Template
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick="applyTemplate()">
|
||||
<button class="btn btn-primary" onclick="applyTemplate()" data-testid="apply-template">
|
||||
<i class="bi bi-upload"></i> Apply Template
|
||||
</button>
|
||||
{% if is_modified %}
|
||||
<button class="btn btn-outline-primary" onclick="resetToPlan()">
|
||||
<button class="btn btn-outline-primary" onclick="resetToPlan()" data-testid="reset-to-plan">
|
||||
<i class="bi bi-arrow-counterclockwise"></i> Reset to Plan
|
||||
</button>
|
||||
{% endif %}
|
||||
<button class="btn btn-outline-danger" onclick="resetPage()" data-testid="reset-page">
|
||||
<i class="bi bi-x-circle"></i> Reset Page
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Meal Times -->
|
||||
@@ -42,16 +45,16 @@
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">{{ meal_time }}</h5>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-success" onclick="addMealToTime('{{ meal_time }}')">
|
||||
<button class="btn btn-sm btn-outline-success" onclick="addMealToTime('{{ meal_time }}')" data-testid="add-meal-{{ meal_time|slugify }}">
|
||||
<i class="bi bi-plus"></i> Add Meal
|
||||
</button>
|
||||
<button class="btn btn-sm btn-info text-white" onclick="addSingleFoodToTime('{{ meal_time }}')">
|
||||
<button class="btn btn-sm btn-info text-white" onclick="addSingleFoodToTime('{{ meal_time }}')" data-testid="add-food-{{ meal_time|slugify }}">
|
||||
<i class="bi bi-plus-circle"></i> Add Food
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="meals-{{ meal_time|lower|replace(' ', '-') }}">
|
||||
<div id="meals-{{ meal_time|slugify }}">
|
||||
{% set meals_for_time = [] %}
|
||||
{% for tracked_meal in tracked_meals %}
|
||||
{% if tracked_meal.meal_time == meal_time %}
|
||||
@@ -60,25 +63,31 @@
|
||||
{% endfor %}
|
||||
|
||||
{% if meals_for_time %}
|
||||
{% for tracked_meal in meals_for_time %}
|
||||
<div class="mb-3 p-3 bg-light rounded">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<div>
|
||||
<strong>{{ tracked_meal.meal.name }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-outline-secondary me-1" onclick="editTrackedMeal('{{ tracked_meal.id }}')" title="Edit Meal">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="removeMeal('{{ tracked_meal.id }}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Food Breakdown -->
|
||||
<div class="ms-3">
|
||||
<div class="row row-cols-1 row-cols-sm-2">
|
||||
{% for tracked_meal in meals_for_time %}
|
||||
{# 1. Create stable slugs #}
|
||||
{% set meal_time_slug = meal_time|slugify %}
|
||||
{% set meal_name_safe = tracked_meal.meal.name|slugify %}
|
||||
|
||||
{# 2. Construct the core Unique Meal ID for non-ambiguous locating #}
|
||||
{% set unique_meal_id = meal_time_slug + '-' + meal_name_safe + '-' + loop.index|string %}
|
||||
<div class="mb-3 p-3 bg-light rounded" data-testid="meal-card-{{ unique_meal_id }}">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<div>
|
||||
<strong data-testid="meal-name-{{ unique_meal_id }}">{{ tracked_meal.meal.name }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-outline-secondary me-1" onclick="editTrackedMeal('{{ tracked_meal.id }}')" title="Edit Meal" data-testid="edit-meal-{{ unique_meal_id }}">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="removeMeal('{{ tracked_meal.id }}')" data-testid="delete-meal-{{ unique_meal_id }}">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Food Breakdown -->
|
||||
<div class="ms-3">
|
||||
<div class="row row-cols-1 row-cols-sm-2">
|
||||
{% set overrides = {} %}
|
||||
{% set all_override_ids = [] %}
|
||||
{% set deleted_food_ids = [] %}
|
||||
@@ -98,7 +107,8 @@
|
||||
{# Only show base meal food if it's not deleted and there's no active override for it #}
|
||||
{% if meal_food.food_id not in deleted_food_ids and meal_food.food_id not in overrides.keys() %}
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-between small text-muted">
|
||||
{% set food_name_safe = meal_food.food.name|slugify %}
|
||||
<div class="d-flex justify-content-between small text-muted" data-testid="food-display-{{ unique_meal_id }}-{{ food_name_safe }}-{{ loop.index }}">
|
||||
<span>• {{ meal_food.food.name }}</span>
|
||||
<span class="text-end">{{ meal_food.quantity }} {{ meal_food.food.serving_unit }}</span>
|
||||
</div>
|
||||
@@ -111,8 +121,9 @@
|
||||
|
||||
{# Display overridden and new foods #}
|
||||
{% for food_id, tmf in overrides.items() %}
|
||||
{% set food_name_safe = tmf.food.name|slugify %}
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-between small text-muted">
|
||||
<div class="d-flex justify-content-between small text-muted" data-testid="food-display-{{ unique_meal_id }}-{{ food_name_safe }}-{{ loop.index }}">
|
||||
<span>• {{ tmf.food.name }}</span>
|
||||
<span class="text-end">{{ tmf.quantity }} g</span>
|
||||
</div>
|
||||
@@ -384,9 +395,9 @@
|
||||
foodDiv.innerHTML = `
|
||||
<span>${food.food_name}</span>
|
||||
<div class="input-group w-50">
|
||||
<input type="number" step="0.01" class="form-control form-control-sm" value="${food.quantity.toFixed(2)}" data-food-id="${food.food_id}" data-item-id="${food.id}" data-is-custom="${food.is_custom}">
|
||||
<input type="number" step="0.01" class="form-control form-control-sm" value="${food.quantity.toFixed(2)}" data-food-id="${food.food_id}" data-item-id="${food.id}" data-is-custom="${food.is_custom}" data-testid="food-quantity-${food.food_id}">
|
||||
<span class="input-group-text">g</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeFoodFromTrackedMeal(${food.food_id}, ${food.is_custom})">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeFoodFromTrackedMeal(${food.food_id}, ${food.id}, ${food.is_custom})" data-testid="delete-food-${food.food_id}">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -404,17 +415,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
function removeFoodFromTrackedMeal(foodId, isCustom) {
|
||||
function removeFoodFromTrackedMeal(foodId, itemId, isCustom) {
|
||||
// Find the button that was clicked
|
||||
const button = event.target.closest('button');
|
||||
if (button) {
|
||||
// Find the parent div and remove it
|
||||
const foodDiv = button.closest('.d-flex');
|
||||
if (foodDiv) {
|
||||
const foodDataId = parseInt(foodDiv.querySelector('input').dataset.foodId);
|
||||
removedFoodIds.push(foodDataId);
|
||||
// We need to push the foodId, not the itemId, to the removedFoodIds array
|
||||
// The backend expects food_id for removals to mark the correct override
|
||||
removedFoodIds.push(foodId);
|
||||
foodDiv.remove();
|
||||
console.log('Removed food with ID:', foodDataId, 'and removedFoodIds is now:', removedFoodIds);
|
||||
console.log('Removed food with foodId:', foodId, 'itemId:', itemId, 'isCustom:', isCustom, 'and removedFoodIds is now:', removedFoodIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,7 +438,7 @@
|
||||
const foods = [];
|
||||
inputs.forEach(input => {
|
||||
const foodData = {
|
||||
id: parseInt(input.dataset.itemId),
|
||||
id: parseInt(input.dataset.itemId), // Pass the item ID to the backend
|
||||
food_id: parseInt(input.dataset.foodId),
|
||||
grams: parseFloat(input.value), // Renamed to grams to match backend
|
||||
is_custom: input.dataset.isCustom === 'true'
|
||||
@@ -533,5 +545,31 @@
|
||||
alert('Error: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset page (clear all meals and foods)
|
||||
async function resetPage() {
|
||||
if (confirm('Are you sure you want to clear all meals and foods for this day? This action cannot be undone.')) {
|
||||
const formData = new FormData();
|
||||
formData.append('person', '{{ person }}');
|
||||
formData.append('date', '{{ current_date.isoformat() }}');
|
||||
|
||||
try {
|
||||
const response = await fetch('/tracker/clear_page', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error: ' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user