From 0775072ffa2577b7c7e5549f38e605e6f8da7d6d Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:11:10 -0600 Subject: [PATCH] feat: add meal plan to shopping list (#2653) * refactored recipe add to shopping list dialog * added context menu to meal plan day * cleaned up presentation * consolidate repeated recipes * added alerts * lint * lint v2 * fixed undefined recipeRef bug * lint * made scale + slug implementation less horrible --- .../Group/GroupMealPlanDayContextMenu.vue | 145 +++++++++ .../Domain/Recipe/RecipeContextMenu.vue | 167 +--------- .../Recipe/RecipeDialogAddToShoppingList.vue | 303 ++++++++++++++++++ frontend/lang/messages/en-US.json | 2 + .../pages/group/mealplan/planner/view.vue | 92 +++--- 5 files changed, 523 insertions(+), 186 deletions(-) create mode 100644 frontend/components/Domain/Group/GroupMealPlanDayContextMenu.vue create mode 100644 frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue diff --git a/frontend/components/Domain/Group/GroupMealPlanDayContextMenu.vue b/frontend/components/Domain/Group/GroupMealPlanDayContextMenu.vue new file mode 100644 index 000000000000..9eb740a81a46 --- /dev/null +++ b/frontend/components/Domain/Group/GroupMealPlanDayContextMenu.vue @@ -0,0 +1,145 @@ + + + + + + + {{ icon }} + + + + + + {{ item.icon }} + + {{ item.title }} + + + + + + + diff --git a/frontend/components/Domain/Recipe/RecipeContextMenu.vue b/frontend/components/Domain/Recipe/RecipeContextMenu.vue index 1dc79fb813c6..fa1d6133f0e2 100644 --- a/frontend/components/Domain/Recipe/RecipeContextMenu.vue +++ b/frontend/components/Domain/Recipe/RecipeContextMenu.vue @@ -69,77 +69,12 @@ > - - - - - {{ list.name }} - - - - - - - - - - - - - - - - - + import { computed, defineComponent, reactive, toRefs, useContext, useRoute, useRouter, ref } from "@nuxtjs/composition-api"; -import RecipeIngredientListItem from "./RecipeIngredientListItem.vue"; +import RecipeDialogAddToShoppingList from "./RecipeDialogAddToShoppingList.vue"; import RecipeDialogPrintPreferences from "./RecipeDialogPrintPreferences.vue"; import RecipeDialogShare from "./RecipeDialogShare.vue"; import { useLoggedInState } from "~/composables/use-logged-in-state"; import { useUserApi } from "~/composables/api"; import { alert } from "~/composables/use-toast"; import { usePlanTypeOptions } from "~/composables/use-group-mealplan"; -import { Recipe, RecipeIngredient } from "~/lib/api/types/recipe"; +import { Recipe } from "~/lib/api/types/recipe"; import { ShoppingListSummary } from "~/lib/api/types/group"; import { PlanEntryType } from "~/lib/api/types/meal-plan"; import { useAxiosDownloader } from "~/composables/api/use-axios-download"; @@ -204,9 +139,9 @@ export interface ContextMenuItem { export default defineComponent({ components: { + RecipeDialogAddToShoppingList, RecipeDialogPrintPreferences, RecipeDialogShare, - RecipeIngredientListItem }, props: { useItems: { @@ -279,7 +214,6 @@ export default defineComponent({ recipeDeleteDialog: false, mealplannerDialog: false, shoppingListDialog: false, - shoppingListIngredientDialog: false, recipeDuplicateDialog: false, recipeName: props.name, loading: false, @@ -374,7 +308,7 @@ export default defineComponent({ } } - // Add leading and Apppending Items + // Add leading and Appending Items state.menuItems = [...state.menuItems, ...props.leadingItems, ...props.appendItems]; const icon = props.menuIcon || $globals.icons.dotsVertical; @@ -383,9 +317,8 @@ export default defineComponent({ // Context Menu Event Handler const shoppingLists = ref(); - const selectedShoppingList = ref(); const recipeRef = ref(props.recipe); - const recipeIngredients = ref<{ checked: boolean; ingredient: RecipeIngredient, disableAmount: boolean }[]>([]); + const recipeRefWithScale = computed(() => recipeRef.value ? { scale: props.recipeScale, ...recipeRef.value } : undefined); async function getShoppingLists() { const { data } = await api.shopping.lists.getAll(); @@ -401,61 +334,6 @@ export default defineComponent({ } } - async function openShoppingListIngredientDialog(list: ShoppingListSummary) { - selectedShoppingList.value = list; - if (!recipeRef.value) { - await refreshRecipe(); - } - - if (recipeRef.value?.recipeIngredient) { - recipeIngredients.value = recipeRef.value.recipeIngredient.map((ingredient) => { - return { - checked: true, - ingredient, - disableAmount: recipeRef.value.settings?.disableAmount || false - }; - }); - } - - state.shoppingListDialog = false; - state.shoppingListIngredientDialog = true; - } - - function bulkCheckIngredients(value = true) { - recipeIngredients.value.forEach((data) => { - data.checked = value; - }); - } - - async function addRecipeToList() { - if (!selectedShoppingList.value) { - return; - } - - const ingredients: RecipeIngredient[] = []; - recipeIngredients.value.forEach((data) => { - if (data.checked) { - ingredients.push(data.ingredient); - } - }); - - if (!ingredients.length) { - return; - } - - const { data } = await api.shopping.lists.addRecipe( - selectedShoppingList.value.id, - props.recipeId, - props.recipeScale, - ingredients - ); - if (data) { - alert.success(i18n.t("recipe.recipe-added-to-list") as string); - state.shoppingListDialog = false; - state.shoppingListIngredientDialog = false; - } - } - const router = useRouter(); async function deleteRecipe() { @@ -516,10 +394,12 @@ export default defineComponent({ state.printPreferencesDialog = true; }, shoppingList: () => { - getShoppingLists(); + const promises: Promise[] = [getShoppingLists()]; + if (!recipeRef.value) { + promises.push(refreshRecipe()); + } - state.shoppingListDialog = true; - state.shoppingListIngredientDialog = false; + Promise.allSettled(promises).then(() => { state.shoppingListDialog = true }); }, share: () => { state.shareDialog = true; @@ -544,28 +424,15 @@ export default defineComponent({ return { ...toRefs(state), recipeRef, + recipeRefWithScale, shoppingLists, - selectedShoppingList, - openShoppingListIngredientDialog, - addRecipeToList, - bulkCheckIngredients, duplicateRecipe, contextMenuEventHandler, deleteRecipe, addRecipeToPlan, icon, planTypeOptions, - recipeIngredients, }; }, }); - - diff --git a/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue new file mode 100644 index 000000000000..a3621043685c --- /dev/null +++ b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue @@ -0,0 +1,303 @@ + + + + + + + {{ list.name }} + + + + + + + + + + + + + {{ section.recipeName }} + + + + + + ({{ $tc("recipe.quantity") }}: {{ section.recipeScale }}) + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/lang/messages/en-US.json b/frontend/lang/messages/en-US.json index c42c09c00cf9..899fe7c7f08e 100644 --- a/frontend/lang/messages/en-US.json +++ b/frontend/lang/messages/en-US.json @@ -463,7 +463,9 @@ "add-to-plan": "Add to Plan", "add-to-timeline": "Add to Timeline", "recipe-added-to-list": "Recipe added to list", + "recipes-added-to-list": "Recipes added to list", "recipe-added-to-mealplan": "Recipe added to mealplan", + "failed-to-add-recipes-to-list": "Failed to add recipe to list", "failed-to-add-recipe-to-mealplan": "Failed to add recipe to mealplan", "yield": "Yield", "quantity": "Quantity", diff --git a/frontend/pages/group/mealplan/planner/view.vue b/frontend/pages/group/mealplan/planner/view.vue index 415eeb1b2600..028d2cfdbf3d 100644 --- a/frontend/pages/group/mealplan/planner/view.vue +++ b/frontend/pages/group/mealplan/planner/view.vue @@ -1,51 +1,65 @@ - - - - - {{ $d(day.date, "short") }} - - - - - - - {{ section.title }} - - + + + + + + + + + {{ $d(day.date, "short") }} + + + + + + + + + + + + + {{ section.title }} + + - - - - + + + + +
- {{ $d(day.date, "short") }} -
- {{ section.title }} -
+ {{ $d(day.date, "short") }} +
+ {{ section.title }} +