mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-04-09 22:45:36 -04:00
feat: Adjust linked recipe unit and seperate when adding to shopping list (#7260)
This commit is contained in:
@@ -86,6 +86,19 @@
|
|||||||
class="text-center"
|
class="text-center"
|
||||||
>
|
>
|
||||||
{{ recipeSection.recipeName }}
|
{{ recipeSection.recipeName }}
|
||||||
|
<v-tooltip v-if="recipeSection.parentRecipe?.name" location="top">
|
||||||
|
<template #activator="{ props: tooltipProps }">
|
||||||
|
<v-icon
|
||||||
|
v-bind="tooltipProps"
|
||||||
|
size="tiny"
|
||||||
|
class="mb-2 ml-2"
|
||||||
|
style="cursor: pointer"
|
||||||
|
>
|
||||||
|
{{ $globals.icons.potSteam }}
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
|
<span>{{ $t("shopping-list.ingredient-of-recipe", { recipe: recipeSection.parentRecipe.name }) }}</span>
|
||||||
|
</v-tooltip>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row
|
<v-row
|
||||||
@@ -203,6 +216,7 @@ export interface ShoppingListRecipeIngredientSection {
|
|||||||
recipeName: string;
|
recipeName: string;
|
||||||
recipeScale: number;
|
recipeScale: number;
|
||||||
ingredientSections: ShoppingListIngredientSection[];
|
ingredientSections: ShoppingListIngredientSection[];
|
||||||
|
parentRecipe?: Recipe;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -258,8 +272,71 @@ watch([dialog, () => preferences.value.viewAllLists], () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function buildIngredientSections(ingredients: ShoppingListIngredient[]): ShoppingListIngredientSection[] {
|
||||||
|
let currentTitle = "";
|
||||||
|
const onHandIngs: ShoppingListIngredient[] = [];
|
||||||
|
const sections = ingredients.reduce((acc, ing) => {
|
||||||
|
if (ing.ingredient.title) {
|
||||||
|
currentTitle = ing.ingredient.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acc.length || currentTitle !== acc[acc.length - 1].sectionName) {
|
||||||
|
if (acc.length) {
|
||||||
|
acc[acc.length - 1].ingredients.push(...onHandIngs);
|
||||||
|
onHandIngs.length = 0;
|
||||||
|
}
|
||||||
|
acc.push({ sectionName: currentTitle, ingredients: [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const householdsWithFood = ing.ingredient?.food?.householdsWithIngredientFood || [];
|
||||||
|
if (householdsWithFood.includes(currentHouseholdSlug.value)) {
|
||||||
|
onHandIngs.push(ing);
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[acc.length - 1].ingredients.push(ing);
|
||||||
|
return acc;
|
||||||
|
}, [] as ShoppingListIngredientSection[]);
|
||||||
|
|
||||||
|
if (sections.length) {
|
||||||
|
sections[sections.length - 1].ingredients.push(...onHandIngs);
|
||||||
|
}
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
async function consolidateRecipesIntoSections(recipes: RecipeWithScale[]) {
|
async function consolidateRecipesIntoSections(recipes: RecipeWithScale[]) {
|
||||||
const recipeSectionMap = new Map<string, ShoppingListRecipeIngredientSection>();
|
const recipeSectionMap = new Map<string, ShoppingListRecipeIngredientSection>();
|
||||||
|
|
||||||
|
function addSubRecipeToMap(ing: RecipeIngredient, parentQuantity: number, parentScale: number, parentRecipe: Recipe) {
|
||||||
|
const ref = ing.referencedRecipe!;
|
||||||
|
const key = ref.id || ref.slug || "";
|
||||||
|
const ownIngs: ShoppingListIngredient[] = [];
|
||||||
|
const subRefIngs: RecipeIngredient[] = [];
|
||||||
|
|
||||||
|
for (const subIng of ref.recipeIngredient ?? []) {
|
||||||
|
if (subIng.referencedRecipe) {
|
||||||
|
subRefIngs.push(subIng);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const householdsWithFood = subIng.food?.householdsWithIngredientFood || [];
|
||||||
|
ownIngs.push({
|
||||||
|
checked: !householdsWithFood.includes(currentHouseholdSlug.value),
|
||||||
|
ingredient: { ...subIng, quantity: (ing.quantity || 1) * (subIng.quantity || 1) },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recipeSectionMap.set(key, {
|
||||||
|
recipeId: ref.id || "",
|
||||||
|
recipeName: ref.name || "",
|
||||||
|
recipeScale: parentQuantity * parentScale,
|
||||||
|
ingredientSections: buildIngredientSections(ownIngs),
|
||||||
|
parentRecipe,
|
||||||
|
});
|
||||||
|
|
||||||
|
subRefIngs.forEach(subIng => addSubRecipeToMap(subIng, (ing.quantity || 1) * (subIng.quantity || 1), parentScale, ref));
|
||||||
|
}
|
||||||
|
|
||||||
for (const recipe of recipes) {
|
for (const recipe of recipes) {
|
||||||
if (!recipe.slug) {
|
if (!recipe.slug) {
|
||||||
continue;
|
continue;
|
||||||
@@ -291,81 +368,29 @@ async function consolidateRecipesIntoSections(recipes: RecipeWithScale[]) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shoppingListIngredients: ShoppingListIngredient[] = [];
|
const ownIngs: ShoppingListIngredient[] = [];
|
||||||
function flattenRecipeIngredients(ing: RecipeIngredient, parentTitle = ""): ShoppingListIngredient[] {
|
const subRefIngs: RecipeIngredient[] = [];
|
||||||
|
recipeData.recipeIngredient.forEach((ing) => {
|
||||||
if (ing.referencedRecipe) {
|
if (ing.referencedRecipe) {
|
||||||
// Recursively flatten all ingredients in the referenced recipe
|
subRefIngs.push(ing);
|
||||||
return (ing.referencedRecipe.recipeIngredient ?? []).flatMap((subIng) => {
|
|
||||||
const calculatedQty = (ing.quantity || 1) * (subIng.quantity || 1);
|
|
||||||
// Pass the referenced recipe name as the section title
|
|
||||||
return flattenRecipeIngredients(
|
|
||||||
{ ...subIng, quantity: calculatedQty },
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Regular ingredient
|
|
||||||
const householdsWithFood = ing.food?.householdsWithIngredientFood || [];
|
const householdsWithFood = ing.food?.householdsWithIngredientFood || [];
|
||||||
return [{
|
ownIngs.push({
|
||||||
checked: !householdsWithFood.includes(currentHouseholdSlug.value),
|
checked: !householdsWithFood.includes(currentHouseholdSlug.value),
|
||||||
ingredient: {
|
ingredient: ing,
|
||||||
...ing,
|
|
||||||
title: ing.title || parentTitle,
|
|
||||||
},
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recipeData.recipeIngredient.forEach((ing) => {
|
|
||||||
const flattened = flattenRecipeIngredients(ing, "");
|
|
||||||
shoppingListIngredients.push(...flattened);
|
|
||||||
});
|
|
||||||
|
|
||||||
let currentTitle = "";
|
|
||||||
const onHandIngs: ShoppingListIngredient[] = [];
|
|
||||||
const shoppingListIngredientSections = shoppingListIngredients.reduce((sections, ing) => {
|
|
||||||
if (ing.ingredient.title) {
|
|
||||||
currentTitle = ing.ingredient.title;
|
|
||||||
}
|
|
||||||
else if (ing.ingredient.referencedRecipe?.name) {
|
|
||||||
currentTitle = ing.ingredient.referencedRecipe.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is the first item in the section, create a new section
|
|
||||||
if (sections.length === 0 || currentTitle !== sections[sections.length - 1].sectionName) {
|
|
||||||
if (sections.length) {
|
|
||||||
// Add the on-hand ingredients to the previous section
|
|
||||||
sections[sections.length - 1].ingredients.push(...onHandIngs);
|
|
||||||
onHandIngs.length = 0;
|
|
||||||
}
|
|
||||||
sections.push({
|
|
||||||
sectionName: currentTitle,
|
|
||||||
ingredients: [],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// Store the on-hand ingredients for later
|
|
||||||
const householdsWithFood = (ing.ingredient?.food?.householdsWithIngredientFood || []);
|
|
||||||
if (householdsWithFood.includes(currentHouseholdSlug.value)) {
|
|
||||||
onHandIngs.push(ing);
|
|
||||||
return sections;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the ingredient to previous section
|
|
||||||
sections[sections.length - 1].ingredients.push(ing);
|
|
||||||
return sections;
|
|
||||||
}, [] as ShoppingListIngredientSection[]);
|
|
||||||
|
|
||||||
// Add remaining on-hand ingredients to the previous section
|
|
||||||
shoppingListIngredientSections[shoppingListIngredientSections.length - 1].ingredients.push(...onHandIngs);
|
|
||||||
|
|
||||||
recipeSectionMap.set(recipe.slug, {
|
recipeSectionMap.set(recipe.slug, {
|
||||||
recipeId: recipeData.id,
|
recipeId: recipeData.id,
|
||||||
recipeName: recipeData.name,
|
recipeName: recipeData.name,
|
||||||
recipeScale: recipeData.scale,
|
recipeScale: recipeData.scale,
|
||||||
ingredientSections: shoppingListIngredientSections,
|
ingredientSections: buildIngredientSections(ownIngs),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
subRefIngs.forEach(ing => addSubRecipeToMap(ing, ing.quantity || 1, recipeData.scale, recipeData));
|
||||||
}
|
}
|
||||||
|
|
||||||
recipeIngredientSections.value = Array.from(recipeSectionMap.values());
|
recipeIngredientSections.value = Array.from(recipeSectionMap.values());
|
||||||
|
|||||||
@@ -44,9 +44,8 @@
|
|||||||
</v-number-input>
|
</v-number-input>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col
|
<v-col
|
||||||
v-if="!state.isRecipe"
|
|
||||||
sm="12"
|
sm="12"
|
||||||
md="3"
|
md="2"
|
||||||
cols="12"
|
cols="12"
|
||||||
>
|
>
|
||||||
<v-autocomplete
|
<v-autocomplete
|
||||||
@@ -104,7 +103,7 @@
|
|||||||
<v-col
|
<v-col
|
||||||
v-if="!state.isRecipe"
|
v-if="!state.isRecipe"
|
||||||
m="12"
|
m="12"
|
||||||
md="3"
|
md="4"
|
||||||
cols="12"
|
cols="12"
|
||||||
class=""
|
class=""
|
||||||
>
|
>
|
||||||
@@ -162,7 +161,7 @@
|
|||||||
<v-col
|
<v-col
|
||||||
v-if="state.isRecipe"
|
v-if="state.isRecipe"
|
||||||
m="12"
|
m="12"
|
||||||
md="6"
|
md="4"
|
||||||
cols="12"
|
cols="12"
|
||||||
class=""
|
class=""
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -904,6 +904,7 @@
|
|||||||
"all-lists": "All Lists",
|
"all-lists": "All Lists",
|
||||||
"create-shopping-list": "Create Shopping List",
|
"create-shopping-list": "Create Shopping List",
|
||||||
"from-recipe": "From Recipe",
|
"from-recipe": "From Recipe",
|
||||||
|
"ingredient-of-recipe": "Ingredient of {recipe}",
|
||||||
"list-name": "List Name",
|
"list-name": "List Name",
|
||||||
"new-list": "New List",
|
"new-list": "New List",
|
||||||
"quantity": "Quantity: {0}",
|
"quantity": "Quantity: {0}",
|
||||||
|
|||||||
Reference in New Issue
Block a user