Squashed commit of the following:

commit c5461420e7f1022019b79517d853a26301add9e9
Author: jableader <jacobdunk@gmail.com>
Date:   Sat Sep 28 14:55:18 2024 +1000

    meal scaling improvements

commit 37a444ff1fb5cbd1942161a8097a44fa7483b14c
Author: jableader <jacobdunk@gmail.com>
Date:   Sat Sep 28 13:13:06 2024 +1000

    Implementation
This commit is contained in:
jableader 2024-09-28 14:57:32 +10:00
parent e59d4aa0ed
commit 531e4f057f
5 changed files with 85 additions and 21 deletions

View file

@ -1,5 +1,5 @@
{ {
"name": "doof-front", "name": "doof",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {

View file

@ -11,11 +11,17 @@
<div class="recipes"> <div class="recipes">
<h2>Recipes</h2> <h2>Recipes</h2>
<ul v-if="meal.recipes && meal.recipes.length"> <ul v-if="meal.recipes && meal.recipes.length">
<li v-for="recipe in meal.recipes" :key="recipe.id" class="saved-recipe"> <li v-for="meal_recipe in meal.recipes" :key="meal_recipe.recipe.id" class="saved-recipe">
<p class="recipe-card"> <p class="recipe-card">
<recipe-card :recipe="recipe" /> <recipe-card :recipe="meal_recipe.recipe" />
</p> </p>
<button @click="removeRecipe(recipe)">
<p class="servings">
<input type="number" v-model="meal_recipe.servings" min="1" />
<small><em>servings</em></small>
</p>
<button class="delete-button" @click="removeRecipe(meal_recipe)">
<img class="icon" :src="require('@/assets/trash.svg')" /> <img class="icon" :src="require('@/assets/trash.svg')" />
</button> </button>
</li> </li>
@ -31,7 +37,10 @@
<h2>Sides & Additional Ingredients</h2> <h2>Sides & Additional Ingredients</h2>
<editable-ingredients-panel :ingredients="meal.extra_ingredients" @on-add="addIngredient" @on-delete="deleteIngredient" @on-update-ingredient="updateIngredient" @on-editing="onEditAdditionalIngredients" /> <editable-ingredients-panel :ingredients="meal.extra_ingredients" @on-add="addIngredient" @on-delete="deleteIngredient" @on-update-ingredient="updateIngredient" @on-editing="onEditAdditionalIngredients" />
</div> </div>
<button @click="saveMeal" v-if="!meal.purchase_date">Save</button>
<button @click="saveMeal">Save</button>
<p v-if="meal.purchase_date"><em>Purchased {{ formatDate(meal.purchase_date) }}</em></p>
</div> </div>
</template> </template>
@ -90,13 +99,36 @@ export default {
} }
} }
this.meal.recipes.push(recipe); this.meal.recipes.push({ recipe, recipe_id: recipe.id, meal_id: this.meal.id, servings: recipe.serves });
}, },
selectDate(date) { selectDate(date) {
this.meal.suggested_date = date; this.meal.suggested_date = date;
}, },
removeRecipe(recipe) { formatDate(date) {
this.meal.recipes = this.meal.recipes.filter(r => r.id !== recipe.id); // If it's today, show 'x hours ago'
// If it's within the last week, show 'x days ago'
// If it's within the last month weeks, show 'x weeks ago'
// Otherwise, show the date as dd/mm
const now = new Date();
const diff = now - date;
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (days === 0) {
const hours = Math.floor(diff / (1000 * 60 * 60));
return `${hours} hour${hours === 1 ? '' : 's'} ago`;
}
if (days < 7) {
return `${days} day${days === 1 ? '' : 's'} ago`;
}
if (days < 30) {
return `${Math.floor(days / 7)} week${days === 7 ? '' : 's'} ago`;
}
return date.toLocaleDateString('en-au', { day: 'numeric', month: 'numeric' });
},
removeRecipe(mealRecipe) {
this.meal.recipes = this.meal.recipes.filter(r => r != mealRecipe);
}, },
addIngredient() { addIngredient() {
this.meal.extra_ingredients = [{ line: '', product: null }, ...this.meal.extra_ingredients]; this.meal.extra_ingredients = [{ line: '', product: null }, ...this.meal.extra_ingredients];
@ -198,4 +230,35 @@ li {
margin: 0; margin: 0;
} }
.recipe-card {
flex: 1;
}
.servings {
display: inline-block;
margin-right: 1em;
}
.servings input {
width: 3em;
border: none;
border-bottom: 1px solid #000;
text-align: center;
font-size: large;
font-style: italic;
}
.delete-button {
background: none;
border: none;
cursor: pointer;
border-radius: 1em;
padding: 0.5em;
}
.delete-button:hover {
/* Invert the colors of the trash icon */
filter: invert(1);
}
</style> </style>

View file

@ -3,8 +3,8 @@
<h3>{{ dayOfWeek }} <small>{{ date }}</small></h3> <h3>{{ dayOfWeek }} <small>{{ date }}</small></h3>
<p v-if="meal.recipes.length && meal.extra_ingredients.length"> <p v-if="meal.recipes.length && meal.extra_ingredients.length">
<span v-for="(recipe, index) in meal.recipes" :key="recipe.id"> <span v-for="(meal_recipe, index) in meal.recipes" :key="meal_recipe.recipe.id">
{{ recipe.name }}{{ seperator(index, meal.recipes) }} {{ meal_recipe.recipe.name }}{{ seperator(index, meal.recipes) }}
</span> </span>
with with
<span v-for="(ingredient, index) in meal.extra_ingredients" :key="ingredient.id"> <span v-for="(ingredient, index) in meal.extra_ingredients" :key="ingredient.id">
@ -12,8 +12,8 @@
</span> </span>
</p> </p>
<p v-else-if="meal.recipes.length"> <p v-else-if="meal.recipes.length">
<span v-for="(recipe, index) in meal.recipes" :key="recipe.id"> <span v-for="(meal_recipe, index) in meal.recipes" :key="meal_recipe.recipe.id">
{{ recipe.name }}{{ seperator(index, meal.recipes) }} {{ meal_recipe.recipe.name }}{{ seperator(index, meal.recipes) }}
</span> </span>
</p> </p>
<p v-else-if="meal.extra_ingredients.length"> <p v-else-if="meal.extra_ingredients.length">

View file

@ -93,7 +93,8 @@ const formatDate = (date) => {
const getUrl = (meal) => { const getUrl = (meal) => {
// First non empty value in meal.recipe/image_urls // First non empty value in meal.recipe/image_urls
for (const recipe of meal.recipes) { for (const mr of meal.recipes) {
const recipe = mr.recipe;
if (recipe.image_urls.length && recipe.image_urls[0]) { if (recipe.image_urls.length && recipe.image_urls[0]) {
return recipe.image_urls[0]; return recipe.image_urls[0];
} }

View file

@ -1,19 +1,19 @@
import units from '@/units.js'; import units from '@/units.js';
function scaleFactor(meal, recipe) { function scaleFactor(mealRecipe) {
const numYield = recipe.serves; const numRecipe = mealRecipe.recipe.serves;
const numConsumers = meal.consumers.length; const numRequested = mealRecipe.servings;
return numConsumers / numYield; return numRequested / numRecipe;
} }
export function mealToShoppingListSources(meal) { export function mealToShoppingListSources(meal) {
const sources = []; const sources = [];
for (const recipe of meal.recipes) { for (const mealRecipe of meal.recipes) {
const scale = scaleFactor(meal, recipe); const scale = scaleFactor(mealRecipe);
for (const ingredient of recipe.ingredients) { for (const ingredient of mealRecipe.recipe.ingredients) {
const quantity = ingredient.quantity * scale; const quantity = ingredient.quantity * scale;
sources.push({ meal, recipe, ingredient: { ...ingredient, quantity, }, }); sources.push({ meal, mealRecipe, recipe: mealRecipe.recipe, ingredient: { ...ingredient, quantity, }, });
} }
} }