feat: Adjust linked recipe unit and seperate when adding to shopping list (#7260)

This commit is contained in:
Michael Genson
2026-03-16 16:30:07 -05:00
committed by GitHub
parent c303198857
commit b94b24640b
3 changed files with 91 additions and 66 deletions

View File

@@ -86,6 +86,19 @@
class="text-center"
>
{{ 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-row>
<v-row
@@ -203,6 +216,7 @@ export interface ShoppingListRecipeIngredientSection {
recipeName: string;
recipeScale: number;
ingredientSections: ShoppingListIngredientSection[];
parentRecipe?: Recipe;
}
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[]) {
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) {
if (!recipe.slug) {
continue;
@@ -291,81 +368,29 @@ async function consolidateRecipesIntoSections(recipes: RecipeWithScale[]) {
continue;
}
const shoppingListIngredients: ShoppingListIngredient[] = [];
function flattenRecipeIngredients(ing: RecipeIngredient, parentTitle = ""): ShoppingListIngredient[] {
const ownIngs: ShoppingListIngredient[] = [];
const subRefIngs: RecipeIngredient[] = [];
recipeData.recipeIngredient.forEach((ing) => {
if (ing.referencedRecipe) {
// Recursively flatten all ingredients in the referenced recipe
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 },
"",
);
});
subRefIngs.push(ing);
}
else {
// Regular ingredient
const householdsWithFood = ing.food?.householdsWithIngredientFood || [];
return [{
ownIngs.push({
checked: !householdsWithFood.includes(currentHouseholdSlug.value),
ingredient: {
...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: [],
ingredient: ing,
});
}
// 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, {
recipeId: recipeData.id,
recipeName: recipeData.name,
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());

View File

@@ -44,9 +44,8 @@
</v-number-input>
</v-col>
<v-col
v-if="!state.isRecipe"
sm="12"
md="3"
md="2"
cols="12"
>
<v-autocomplete
@@ -104,7 +103,7 @@
<v-col
v-if="!state.isRecipe"
m="12"
md="3"
md="4"
cols="12"
class=""
>
@@ -162,7 +161,7 @@
<v-col
v-if="state.isRecipe"
m="12"
md="6"
md="4"
cols="12"
class=""
>

View File

@@ -904,6 +904,7 @@
"all-lists": "All Lists",
"create-shopping-list": "Create Shopping List",
"from-recipe": "From Recipe",
"ingredient-of-recipe": "Ingredient of {recipe}",
"list-name": "List Name",
"new-list": "New List",
"quantity": "Quantity: {0}",