mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
feat: Improvements To Adding A Recipe To A Shopping List (#3036)
* tweaked dialogs to make grammatical sense * refactor ingredient rendering on recipe shopping list dialog
This commit is contained in:
parent
8db5f7cce3
commit
c6fbf8bce8
@ -26,54 +26,69 @@
|
|||||||
>
|
>
|
||||||
<div style="max-height: 70vh; overflow-y: auto">
|
<div style="max-height: 70vh; overflow-y: auto">
|
||||||
<v-card
|
<v-card
|
||||||
v-for="(section, sectionIndex) in recipeIngredientSections" :key="section.recipeId + sectionIndex"
|
v-for="(recipeSection, recipeSectionIndex) in recipeIngredientSections" :key="recipeSection.recipeId + recipeSectionIndex"
|
||||||
elevation="0"
|
elevation="0"
|
||||||
height="fit-content"
|
height="fit-content"
|
||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<v-divider v-if="sectionIndex > 0" class="mt-3" />
|
<v-divider v-if="recipeSectionIndex > 0" class="mt-3" />
|
||||||
<v-card-title
|
<v-card-title
|
||||||
v-if="recipeIngredientSections.length > 1"
|
v-if="recipeIngredientSections.length > 1"
|
||||||
class="justify-center"
|
class="justify-center text-h5"
|
||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<v-container style="width: 100%;">
|
<v-container style="width: 100%;">
|
||||||
<v-row no-gutters class="ma-0 pa-0">
|
<v-row no-gutters class="ma-0 pa-0">
|
||||||
<v-col cols="12" align-self="center" class="text-center">
|
<v-col cols="12" align-self="center" class="text-center">
|
||||||
{{ section.recipeName }}
|
{{ recipeSection.recipeName }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="section.recipeScale > 1" no-gutters class="ma-0 pa-0">
|
<v-row v-if="recipeSection.recipeScale > 1" no-gutters class="ma-0 pa-0">
|
||||||
<!-- TODO: make this editable in the dialog and visible on single-recipe lists -->
|
<!-- TODO: make this editable in the dialog and visible on single-recipe lists -->
|
||||||
<v-col cols="12" align-self="center" class="text-center">
|
<v-col cols="12" align-self="center" class="text-center">
|
||||||
({{ $tc("recipe.quantity") }}: {{ section.recipeScale }})
|
({{ $tc("recipe.quantity") }}: {{ recipeSection.recipeScale }})
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<div
|
<div>
|
||||||
:class="$vuetify.breakpoint.smAndDown ? '' : 'ingredient-grid'"
|
<div
|
||||||
:style="$vuetify.breakpoint.smAndDown ? '' : { gridTemplateRows: `repeat(${Math.ceil(section.ingredients.length / 2)}, min-content)` }"
|
v-for="(ingredientSection, ingredientSectionIndex) in recipeSection.ingredientSections"
|
||||||
>
|
:key="recipeSection.recipeId + recipeSectionIndex + ingredientSectionIndex"
|
||||||
<v-list-item
|
|
||||||
v-for="(ingredientData, i) in section.ingredients"
|
|
||||||
:key="'ingredient' + i"
|
|
||||||
dense
|
|
||||||
@click="recipeIngredientSections[sectionIndex].ingredients[i].checked = !recipeIngredientSections[sectionIndex].ingredients[i].checked"
|
|
||||||
>
|
>
|
||||||
<v-checkbox
|
<v-card-title v-if="ingredientSection.sectionName" class="ingredient-title mt-2 pb-0 text-h6">
|
||||||
hide-details
|
{{ ingredientSection.sectionName }}
|
||||||
:input-value="ingredientData.checked"
|
</v-card-title>
|
||||||
class="pt-0 my-auto py-auto"
|
<div
|
||||||
color="secondary"
|
:class="$vuetify.breakpoint.smAndDown ? '' : 'ingredient-grid'"
|
||||||
/>
|
:style="$vuetify.breakpoint.smAndDown ? '' : { gridTemplateRows: `repeat(${Math.ceil(ingredientSection.ingredients.length / 2)}, min-content)` }"
|
||||||
<v-list-item-content :key="ingredientData.ingredient.quantity">
|
>
|
||||||
<RecipeIngredientListItem
|
<v-list-item
|
||||||
:ingredient="ingredientData.ingredient"
|
v-for="(ingredientData, i) in ingredientSection.ingredients"
|
||||||
:disable-amount="ingredientData.disableAmount"
|
:key="recipeSection.recipeId + recipeSectionIndex + ingredientSectionIndex + i"
|
||||||
:scale="section.recipeScale" />
|
dense
|
||||||
</v-list-item-content>
|
@click="recipeIngredientSections[recipeSectionIndex]
|
||||||
</v-list-item>
|
.ingredientSections[ingredientSectionIndex]
|
||||||
|
.ingredients[i].checked = !recipeIngredientSections[recipeSectionIndex]
|
||||||
|
.ingredientSections[ingredientSectionIndex]
|
||||||
|
.ingredients[i]
|
||||||
|
.checked"
|
||||||
|
>
|
||||||
|
<v-checkbox
|
||||||
|
hide-details
|
||||||
|
:input-value="ingredientData.checked"
|
||||||
|
class="pt-0 my-auto py-auto"
|
||||||
|
color="secondary"
|
||||||
|
/>
|
||||||
|
<v-list-item-content :key="ingredientData.ingredient.quantity">
|
||||||
|
<RecipeIngredientListItem
|
||||||
|
:ingredient="ingredientData.ingredient"
|
||||||
|
:disable-amount="ingredientData.disableAmount"
|
||||||
|
:scale="recipeSection.recipeScale" />
|
||||||
|
</v-list-item-content>
|
||||||
|
</v-list-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
@ -112,17 +127,22 @@ export interface RecipeWithScale extends Recipe {
|
|||||||
scale: number;
|
scale: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShoppingListRecipeIngredient {
|
export interface ShoppingListIngredient {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
ingredient: RecipeIngredient;
|
ingredient: RecipeIngredient;
|
||||||
disableAmount: boolean;
|
disableAmount: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ShoppingListIngredientSection {
|
||||||
|
sectionName: string;
|
||||||
|
ingredients: ShoppingListIngredient[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface ShoppingListRecipeIngredientSection {
|
export interface ShoppingListRecipeIngredientSection {
|
||||||
recipeId: string;
|
recipeId: string;
|
||||||
recipeName: string;
|
recipeName: string;
|
||||||
recipeScale: number;
|
recipeScale: number;
|
||||||
ingredients: ShoppingListRecipeIngredient[];
|
ingredientSections: ShoppingListIngredientSection[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -191,7 +211,7 @@ export default defineComponent({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shoppingListIngredients: ShoppingListRecipeIngredient[] = recipe.recipeIngredient.map((ing) => {
|
const shoppingListIngredients: ShoppingListIngredient[] = recipe.recipeIngredient.map((ing) => {
|
||||||
return {
|
return {
|
||||||
checked: true,
|
checked: true,
|
||||||
ingredient: ing,
|
ingredient: ing,
|
||||||
@ -199,11 +219,35 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const shoppingListIngredientSections = shoppingListIngredients.reduce((sections, ing) => {
|
||||||
|
// if title append new section to the end of the array
|
||||||
|
if (ing.ingredient.title) {
|
||||||
|
sections.push({
|
||||||
|
sectionName: ing.ingredient.title,
|
||||||
|
ingredients: [ing],
|
||||||
|
});
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
// append new section if first
|
||||||
|
if (sections.length === 0) {
|
||||||
|
sections.push({
|
||||||
|
sectionName: "",
|
||||||
|
ingredients: [ing],
|
||||||
|
});
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise add ingredient to last section in the array
|
||||||
|
sections[sections.length - 1].ingredients.push(ing);
|
||||||
|
return sections;
|
||||||
|
}, [] as ShoppingListIngredientSection[]);
|
||||||
|
|
||||||
recipeSectionMap.set(recipe.slug, {
|
recipeSectionMap.set(recipe.slug, {
|
||||||
recipeId: recipe.id,
|
recipeId: recipe.id,
|
||||||
recipeName: recipe.name,
|
recipeName: recipe.name,
|
||||||
recipeScale: recipe.scale,
|
recipeScale: recipe.scale,
|
||||||
ingredients: shoppingListIngredients,
|
ingredientSections: shoppingListIngredientSections,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,9 +275,11 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function bulkCheckIngredients(value = true) {
|
function bulkCheckIngredients(value = true) {
|
||||||
recipeIngredientSections.value.forEach((section) => {
|
recipeIngredientSections.value.forEach((recipeSection) => {
|
||||||
section.ingredients.forEach((ing) => {
|
recipeSection.ingredientSections.forEach((ingSection) => {
|
||||||
ing.checked = value;
|
ingSection.ingredients.forEach((ing) => {
|
||||||
|
ing.checked = value;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -246,10 +292,12 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ingredients: RecipeIngredient[] = [];
|
const ingredients: RecipeIngredient[] = [];
|
||||||
section.ingredients.forEach((ing) => {
|
section.ingredientSections.forEach((ingSection) => {
|
||||||
if (ing.checked) {
|
ingSection.ingredients.forEach((ing) => {
|
||||||
ingredients.push(ing.ingredient);
|
if (ing.checked) {
|
||||||
}
|
ingredients.push(ing.ingredient);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!ingredients.length) {
|
if (!ingredients.length) {
|
||||||
@ -272,7 +320,11 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
success ? alert.success(i18n.t("recipe.recipes-added-to-list") as string)
|
const successMessage = promises.length === 1
|
||||||
|
? i18n.t("recipe.successfully-added-to-list") as string
|
||||||
|
: i18n.t("recipe.failed-to-add-to-list") as string;
|
||||||
|
|
||||||
|
success ? alert.success(successMessage)
|
||||||
: alert.error(i18n.t("failed-to-add-recipes-to-list") as string)
|
: alert.error(i18n.t("failed-to-add-recipes-to-list") as string)
|
||||||
|
|
||||||
state.shoppingListDialog = false;
|
state.shoppingListDialog = false;
|
||||||
|
@ -473,9 +473,11 @@
|
|||||||
"add-to-timeline": "Add to Timeline",
|
"add-to-timeline": "Add to Timeline",
|
||||||
"recipe-added-to-list": "Recipe added to list",
|
"recipe-added-to-list": "Recipe added to list",
|
||||||
"recipes-added-to-list": "Recipes added to list",
|
"recipes-added-to-list": "Recipes added to list",
|
||||||
|
"successfully-added-to-list": "Successfully added to list",
|
||||||
"recipe-added-to-mealplan": "Recipe added to mealplan",
|
"recipe-added-to-mealplan": "Recipe added to mealplan",
|
||||||
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
|
"failed-to-add-recipes-to-list": "Failed to add recipe to list",
|
||||||
"failed-to-add-recipe-to-mealplan": "Failed to add recipe to mealplan",
|
"failed-to-add-recipe-to-mealplan": "Failed to add recipe to mealplan",
|
||||||
|
"failed-to-add-to-list": "Failed to add to list",
|
||||||
"yield": "Yield",
|
"yield": "Yield",
|
||||||
"quantity": "Quantity",
|
"quantity": "Quantity",
|
||||||
"choose-unit": "Choose Unit",
|
"choose-unit": "Choose Unit",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user