refactor IngredientComponent, move shopping logic to card
This commit is contained in:
@ -457,7 +457,7 @@ export default {
|
||||
this.pagination_count = result.data.count
|
||||
|
||||
this.facets = result.data.facets
|
||||
this.recipes = this.removeDuplicates(result.data.results, (recipe) => recipe.id)
|
||||
this.recipes = [...this.removeDuplicates(result.data.results, (recipe) => recipe.id)]
|
||||
if (!this.searchFiltered()) {
|
||||
// if meal plans are being shown - filter out any meal plan recipes from the recipe list
|
||||
let mealPlans = []
|
||||
|
@ -101,6 +101,7 @@
|
||||
:servings="servings"
|
||||
:header="true"
|
||||
@checked-state-changed="updateIngredientCheckedState"
|
||||
@change-servings="servings = $event"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -280,5 +281,4 @@ export default {
|
||||
#app > div > div {
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -190,6 +190,9 @@
|
||||
<td class="block-inline">
|
||||
<b-form-input min="1" type="number" :debounce="300" :value="r.recipe_mealplan.servings" @input="updateServings($event, r.list_recipe)"></b-form-input>
|
||||
</td>
|
||||
<td>
|
||||
<i class="btn text-primary far fa-eye fa-lg px-2 border-0" variant="link" :title="$t('view_recipe')" @click="editRecipeList($event, r)" />
|
||||
</td>
|
||||
<td>
|
||||
<i class="btn text-danger fas fa-trash fa-lg px-2 border-0" variant="link" :title="$t('Delete')" @click="deleteRecipe($event, r.list_recipe)" />
|
||||
</td>
|
||||
@ -432,7 +435,10 @@
|
||||
<div class="col col-md-6 text-right">
|
||||
<generic-multiselect
|
||||
size="sm"
|
||||
@change="settings.shopping_share = $event.val;saveSettings()"
|
||||
@change="
|
||||
settings.shopping_share = $event.val
|
||||
saveSettings()
|
||||
"
|
||||
:model="Models.USER"
|
||||
:initial_selection="settings.shopping_share"
|
||||
label="username"
|
||||
@ -656,7 +662,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<shopping-modal v-if="new_recipe.id" :recipe="new_recipe" :servings="parseInt(add_recipe_servings)" :modal_id="new_recipe.id" @finish="finishShopping" />
|
||||
<shopping-modal v-if="new_recipe.id" :recipe="new_recipe" :servings="parseInt(add_recipe_servings)" :modal_id="new_recipe.id" @finish="finishShopping" :list_recipe="new_recipe.list_recipe" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1399,11 +1405,23 @@ export default {
|
||||
window.removeEventListener("offline", this.updateOnlineStatus)
|
||||
},
|
||||
addRecipeToShopping() {
|
||||
console.log(this.new_recipe)
|
||||
this.$bvModal.show(`shopping_${this.new_recipe.id}`)
|
||||
},
|
||||
finishShopping() {
|
||||
this.add_recipe_servings = 1
|
||||
this.new_recipe = { id: undefined }
|
||||
this.edit_recipe_list = undefined
|
||||
this.getShoppingList()
|
||||
},
|
||||
editRecipeList(e, r) {
|
||||
this.new_recipe = { id: r.recipe_mealplan.recipe, name: r.recipe_mealplan.recipe_name, servings: r.recipe_mealplan.servings, list_recipe: r.list_recipe }
|
||||
this.$nextTick(function () {
|
||||
this.$bvModal.show(`shopping_${this.new_recipe.id}`)
|
||||
})
|
||||
|
||||
// this.$bvModal.show(`shopping_${this.new_recipe.id}`)
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
hover: {
|
||||
|
@ -7,7 +7,7 @@
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<td class="d-print-non" v-if="detailed && !add_shopping_mode" @click="done">
|
||||
<td class="d-print-non" v-if="detailed && !show_shopping" @click="done">
|
||||
<i class="far fa-check-circle text-success" v-if="ingredient.checked"></i>
|
||||
<i class="far fa-check-circle text-primary" v-if="!ingredient.checked"></i>
|
||||
</td>
|
||||
@ -39,9 +39,9 @@
|
||||
variant="link"
|
||||
v-b-popover.hover.click.blur.html.top="{ title: ShoppingPopover, variant: 'outline-dark' }"
|
||||
:class="{
|
||||
'text-success': shopping_status === true,
|
||||
'text-muted': shopping_status === false,
|
||||
'text-warning': shopping_status === null,
|
||||
'text-success': ingredient.shopping_status === true,
|
||||
'text-muted': ingredient.shopping_status === false,
|
||||
'text-warning': ingredient.shopping_status === null,
|
||||
}"
|
||||
/>
|
||||
<span v-if="!ingredient.food.ignore_shopping" class="px-2">
|
||||
@ -64,111 +64,49 @@ export default {
|
||||
ingredient: Object,
|
||||
ingredient_factor: { type: Number, default: 1 },
|
||||
detailed: { type: Boolean, default: true },
|
||||
recipe_list: { type: Number }, // ShoppingListRecipe ID, to filter ShoppingStatus
|
||||
show_shopping: { type: Boolean, default: false },
|
||||
add_shopping_mode: { type: Boolean, default: false },
|
||||
shopping_list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
}, // list of unchecked ingredients in shopping list
|
||||
},
|
||||
mixins: [ResolveUrlMixin, ApiMixin],
|
||||
data() {
|
||||
return {
|
||||
checked: false,
|
||||
shopping_status: null, // in any shopping list: boolean + null=in shopping list, but not for this recipe
|
||||
shopping_items: [],
|
||||
shop: false, // in shopping list for this recipe: boolean
|
||||
dirty: undefined,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
ShoppingListAndFilter: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
// this whole sections is overly complicated
|
||||
// trying to infer status of shopping for THIS recipe and THIS ingredient
|
||||
// without know which recipe it is.
|
||||
// If refactored:
|
||||
// ## Needs to handle same recipe (multiple mealplans) being in shopping list multiple times
|
||||
// ## Needs to handle same recipe being added as ShoppingListRecipe AND ingredients added from recipe as one-off
|
||||
|
||||
let filtered_list = this.shopping_list
|
||||
// if a recipe list is provided, filter the shopping list
|
||||
if (this.recipe_list) {
|
||||
filtered_list = filtered_list.filter((x) => x.list_recipe == this.recipe_list)
|
||||
}
|
||||
// how many ShoppingListRecipes are there for this recipe?
|
||||
let count_shopping_recipes = [...new Set(filtered_list.filter((x) => x.list_recipe))].length
|
||||
let count_shopping_ingredient = filtered_list.filter((x) => x.ingredient == this.ingredient.id).length
|
||||
|
||||
if (count_shopping_recipes >= 1 && this.recipe_list) {
|
||||
// This recipe is in the shopping list
|
||||
this.shop = false // don't check any boxes until user selects a shopping list to edit
|
||||
if (count_shopping_ingredient >= 1) {
|
||||
this.shopping_status = true // ingredient is in the shopping list - probably (but not definitely, this ingredient)
|
||||
} else if (this.ingredient?.food?.shopping) {
|
||||
this.shopping_status = null // food is in the shopping list, just not for this ingredient/recipe
|
||||
} else {
|
||||
// food is not in any shopping list
|
||||
this.shopping_status = false
|
||||
}
|
||||
} else {
|
||||
// there are not recipes in the shopping list
|
||||
// set default value
|
||||
this.shop = !this.ingredient?.food?.food_onhand && !this.ingredient?.food?.recipe && !this.ingredient?.food?.ignore_shopping
|
||||
this.$emit("add-to-shopping", { item: this.ingredient, add: this.shop })
|
||||
// mark checked if the food is in the shopping list for this ingredient/recipe
|
||||
if (count_shopping_ingredient >= 1) {
|
||||
// ingredient is in this shopping list (not entirely sure how this could happen?)
|
||||
this.shopping_status = true
|
||||
} else if (count_shopping_ingredient == 0 && this.ingredient?.food?.shopping) {
|
||||
// food is in the shopping list, just not for this ingredient/recipe
|
||||
this.shopping_status = null
|
||||
} else {
|
||||
// the food is not in any shopping list
|
||||
this.shopping_status = false
|
||||
}
|
||||
}
|
||||
|
||||
if (this.add_shopping_mode) {
|
||||
// if we are in add shopping mode (e.g. recipe_shopping_modal) start with all checks marked
|
||||
// except if on_hand (could be if recipe too?)
|
||||
this.shop = !this.ingredient?.food?.food_onhand && !this.ingredient?.food?.recipe && !this.ingredient?.food?.ignore_shopping
|
||||
}
|
||||
ingredient: {
|
||||
handler() {},
|
||||
deep: true,
|
||||
},
|
||||
"ingredient.shop": function (newVal) {
|
||||
this.shop = newVal
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.shop = this.ingredient?.shop
|
||||
},
|
||||
mounted() {},
|
||||
computed: {
|
||||
ShoppingListAndFilter() {
|
||||
// hack to watch the shopping list and the recipe list at the same time
|
||||
return this.shopping_list.map((x) => x.id).join(this.recipe_list)
|
||||
},
|
||||
ShoppingPopover() {
|
||||
if (this.shopping_status == false) {
|
||||
if (this.ingredient?.shopping_status == false) {
|
||||
return this.$t("NotInShopping", { food: this.ingredient.food.name })
|
||||
} else {
|
||||
let list = this.shopping_list.filter((x) => x.food.id == this.ingredient.food.id)
|
||||
let category = this.$t("Category") + ": " + this.ingredient?.food?.supermarket_category?.name ?? this.$t("Undefined")
|
||||
let category = this.$t("Category") + ": " + this.ingredient?.category ?? this.$t("Undefined")
|
||||
let popover = []
|
||||
|
||||
list.forEach((x) => {
|
||||
;(this.ingredient?.shopping_list ?? []).forEach((x) => {
|
||||
popover.push(
|
||||
[
|
||||
"<tr style='border-bottom: 1px solid #ccc'>",
|
||||
"<td style='padding: 3px;'><em>",
|
||||
x?.recipe_mealplan?.name ?? "",
|
||||
x?.mealplan ?? "",
|
||||
"</em></td>",
|
||||
"<td style='padding: 3px;'>",
|
||||
x?.amount ?? "",
|
||||
"</td>",
|
||||
"<td style='padding: 3px;'>",
|
||||
x?.unit?.name ?? "" + "</td>",
|
||||
x?.unit ?? "" + "</td>",
|
||||
"<td style='padding: 3px;'>",
|
||||
x?.food?.name ?? "",
|
||||
x?.food ?? "",
|
||||
"</td></tr>",
|
||||
].join("")
|
||||
)
|
||||
|
@ -13,7 +13,7 @@
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-right" v-if="ShoppingRecipes.length > 1">
|
||||
<div class="row text-right" v-if="ShoppingRecipes.length > 1 && !add_shopping_mode">
|
||||
<div class="col col-md-6 offset-md-6 text-right">
|
||||
<b-form-select v-model="selected_shoppingrecipe" :options="ShoppingRecipes" size="sm"></b-form-select>
|
||||
</div>
|
||||
@ -31,14 +31,11 @@
|
||||
</tr>
|
||||
<template v-for="i in s.ingredients">
|
||||
<ingredient-component
|
||||
:ingredient="i"
|
||||
:ingredient="prepareIngredient(i)"
|
||||
:ingredient_factor="ingredient_factor"
|
||||
:key="i.id"
|
||||
:show_shopping="show_shopping"
|
||||
:shopping_list="shopping_list"
|
||||
:add_shopping_mode="add_shopping_mode"
|
||||
:detailed="detailed"
|
||||
:recipe_list="selected_shoppingrecipe"
|
||||
@checked-state-changed="$emit('checked-state-changed', $event)"
|
||||
@add-to-shopping="addShopping($event)"
|
||||
/>
|
||||
@ -59,6 +56,7 @@ import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||
|
||||
import IngredientComponent from "@/components/IngredientComponent"
|
||||
import { ApiMixin, StandardToasts } from "@/utils/utils"
|
||||
import ShoppingListViewVue from "../apps/ShoppingListView/ShoppingListView.vue"
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
@ -79,6 +77,7 @@ export default {
|
||||
detailed: { type: Boolean, default: true },
|
||||
header: { type: Boolean, default: false },
|
||||
add_shopping_mode: { type: Boolean, default: false },
|
||||
recipe_list: { type: Number, default: undefined },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -107,13 +106,14 @@ export default {
|
||||
watch: {
|
||||
ShoppingRecipes: function (newVal, oldVal) {
|
||||
if (newVal.length === 0 || this.add_shopping_mode) {
|
||||
this.selected_shoppingrecipe = undefined
|
||||
this.selected_shoppingrecipe = this.recipe_list
|
||||
} else if (newVal.length === 1) {
|
||||
this.selected_shoppingrecipe = newVal[0].value
|
||||
}
|
||||
},
|
||||
selected_shoppingrecipe: function (newVal, oldVal) {
|
||||
this.update_shopping = this.shopping_list.filter((x) => x.list_recipe === newVal).map((x) => x.ingredient)
|
||||
this.$emit("change-servings", this.ShoppingRecipes.filter((x) => x.value === this.selected_shoppingrecipe)[0].servings)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
@ -140,6 +140,32 @@ export default {
|
||||
}
|
||||
this.genericAPI(this.Models.SHOPPING_LIST, this.Actions.LIST, params).then((result) => {
|
||||
this.shopping_list = result.data
|
||||
|
||||
if (this.add_shopping_mode) {
|
||||
console.log("add shopping mode", this.recipe_list, this.steps)
|
||||
if (this.recipe_list) {
|
||||
this.$emit(
|
||||
"starting-cart",
|
||||
this.shopping_list.filter((x) => x.list_recipe === this.recipe_list).map((x) => x.ingredient)
|
||||
)
|
||||
} else {
|
||||
console.log(
|
||||
this.steps
|
||||
.map((x) => x.ingredients)
|
||||
.flat()
|
||||
.filter((x) => x?.food?.food_onhand == false && x?.food?.ignore_shopping == false)
|
||||
.map((x) => x.id)
|
||||
)
|
||||
this.$emit(
|
||||
"starting-cart",
|
||||
this.steps
|
||||
.map((x) => x.ingredients)
|
||||
.flat()
|
||||
.filter((x) => x?.food?.food_onhand == false && x?.food?.ignore_shopping == false)
|
||||
.map((x) => x.id)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
@ -155,7 +181,7 @@ export default {
|
||||
servings: servings,
|
||||
}
|
||||
this.genericAPI(this.Models.RECIPE, this.Actions.SHOPPING, params)
|
||||
.then(() => {
|
||||
.then((result) => {
|
||||
if (del_shopping) {
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE)
|
||||
} else if (this.selected_shoppingrecipe) {
|
||||
@ -164,13 +190,6 @@ export default {
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (!this.add_shopping_mode) {
|
||||
return this.getShopping(false)
|
||||
} else {
|
||||
this.$emit("shopping-added")
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (del_shopping) {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_DELETE)
|
||||
@ -186,13 +205,51 @@ export default {
|
||||
// ALERT: this will all break if ingredients are re-used between recipes
|
||||
if (e.add) {
|
||||
this.update_shopping.push(e.item.id)
|
||||
this.shopping_list.push({
|
||||
id: Math.random(),
|
||||
amount: e.item.amount,
|
||||
ingredient: e.item.id,
|
||||
food: e.item.food,
|
||||
list_recipe: this.selected_shoppingrecipe,
|
||||
})
|
||||
} else {
|
||||
this.update_shopping = this.update_shopping.filter((x) => x !== e.item.id)
|
||||
this.update_shopping = [...this.update_shopping.filter((x) => x !== e.item.id)]
|
||||
this.shopping_list = [...this.shopping_list.filter((x) => !(x.ingredient === e.item.id && x.list_recipe === this.selected_shoppingrecipe))]
|
||||
}
|
||||
if (this.add_shopping_mode) {
|
||||
this.$emit("add-to-shopping", e)
|
||||
}
|
||||
},
|
||||
prepareIngredient: function (i) {
|
||||
let shopping = this.shopping_list.filter((x) => x.ingredient === i.id)
|
||||
let selected_list = this.shopping_list.filter((x) => x.list_recipe === this.selected_shoppingrecipe && x.ingredient === i.id)
|
||||
// checked = in the selected shopping list OR if in shoppping mode without a selected recipe, the default value true unless it is ignored or onhand
|
||||
let checked = selected_list.length > 0 || (this.add_shopping_mode && !this.selected_shoppingrecipe && !i?.food?.ignore_recipe && !i?.food?.food_onhand)
|
||||
|
||||
let shopping_status = false // not in shopping list
|
||||
if (shopping.length > 0) {
|
||||
if (selected_list.length > 0) {
|
||||
shopping_status = true // in shopping list for *this* recipe
|
||||
} else {
|
||||
shopping_status = null // in shopping list but not *this* recipe
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...i,
|
||||
shop: checked,
|
||||
shopping_status: shopping_status, // possible values: true, false, null
|
||||
category: i.food.supermarket_category?.name,
|
||||
shopping_list: shopping.map((x) => {
|
||||
return {
|
||||
mealplan: x?.recipe_mealplan?.name,
|
||||
amount: x.amount,
|
||||
food: x.food?.name,
|
||||
unit: x.unit?.name,
|
||||
}
|
||||
}),
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-modal :id="`shopping_${this.modal_id}`" hide-footer @show="loadRecipe">
|
||||
<b-modal :id="`shopping_${this.modal_id}`" @show="loadRecipe">
|
||||
<template v-slot:modal-title
|
||||
><h4>{{ $t("Add_Servings_to_Shopping", { servings: recipe_servings }) }}</h4></template
|
||||
>
|
||||
@ -16,10 +16,11 @@
|
||||
:recipe="recipe.id"
|
||||
:ingredient_factor="ingredient_factor"
|
||||
:servings="recipe_servings"
|
||||
:show_shopping="true"
|
||||
:add_shopping_mode="true"
|
||||
:recipe_list="list_recipe"
|
||||
:header="false"
|
||||
@add-to-shopping="addShopping($event)"
|
||||
@starting-cart="add_shopping = $event"
|
||||
/>
|
||||
</b-collapse>
|
||||
<!-- eslint-disable vue/no-v-for-template-key-on-child -->
|
||||
@ -34,10 +35,11 @@
|
||||
:recipe="r.recipe.id"
|
||||
:ingredient_factor="ingredient_factor"
|
||||
:servings="recipe_servings"
|
||||
:show_shopping="true"
|
||||
:add_shopping_mode="true"
|
||||
:recipe_list="list_recipe"
|
||||
:header="false"
|
||||
@add-to-shopping="addShopping($event)"
|
||||
@starting-cart="add_shopping = [...add_shopping, ...$event]"
|
||||
/>
|
||||
</b-collapse>
|
||||
</b-card>
|
||||
@ -46,7 +48,8 @@
|
||||
</b-card>
|
||||
</div>
|
||||
|
||||
<b-input-group class="my-3">
|
||||
<template #modal-footer="">
|
||||
<b-input-group class="mr-3">
|
||||
<b-input-group-prepend is-text>
|
||||
{{ $t("Servings") }}
|
||||
</b-input-group-prepend>
|
||||
@ -58,6 +61,7 @@
|
||||
<b-button variant="success" @click="saveShopping">{{ $t("Save") }} </b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</template>
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
@ -81,6 +85,7 @@ export default {
|
||||
servings: { type: Number, default: undefined },
|
||||
modal_id: { required: true, type: Number },
|
||||
mealplan: { type: Number, default: undefined },
|
||||
list_recipe: { type: Number, default: undefined },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -121,14 +126,6 @@ export default {
|
||||
this.steps = result.data.steps
|
||||
// ALERT: this will all break if ingredients are re-used between recipes
|
||||
// ALERT: this also doesn't quite work right if the same recipe appears multiple time in the related recipes
|
||||
this.add_shopping = [
|
||||
...this.add_shopping,
|
||||
...this.steps
|
||||
.map((x) => x.ingredients)
|
||||
.flat()
|
||||
.filter((x) => !x?.food?.food_onhand)
|
||||
.map((x) => x.id),
|
||||
]
|
||||
if (!this.recipe_servings) {
|
||||
this.recipe_servings = result.data?.servings
|
||||
}
|
||||
@ -155,18 +152,20 @@ export default {
|
||||
})
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(() => {
|
||||
this.add_shopping = [
|
||||
...this.add_shopping,
|
||||
...this.related_recipes
|
||||
.map((x) => x.steps)
|
||||
.flat()
|
||||
.map((x) => x.ingredients)
|
||||
.flat()
|
||||
.filter((x) => !x.food.override_ignore)
|
||||
.map((x) => x.id),
|
||||
]
|
||||
})
|
||||
// .then(() => {
|
||||
// if (!this.list_recipe) {
|
||||
// this.add_shopping = [
|
||||
// ...this.add_shopping,
|
||||
// ...this.related_recipes
|
||||
// .map((x) => x.steps)
|
||||
// .flat()
|
||||
// .map((x) => x.ingredients)
|
||||
// .flat()
|
||||
// .filter((x) => !x.food.override_ignore)
|
||||
// .map((x) => x.id),
|
||||
// ]
|
||||
// }
|
||||
// })
|
||||
})
|
||||
},
|
||||
addShopping: function (e) {
|
||||
@ -183,6 +182,7 @@ export default {
|
||||
ingredients: this.add_shopping,
|
||||
servings: this.recipe_servings,
|
||||
mealplan: this.mealplan,
|
||||
list_recipe: this.list_recipe,
|
||||
}
|
||||
let apiClient = new ApiApiFactory()
|
||||
apiClient
|
||||
|
@ -201,7 +201,6 @@ export default {
|
||||
navigator.share(shareData)
|
||||
},
|
||||
addToShopping() {
|
||||
console.log("opening shopping modal")
|
||||
this.$bvModal.show(`shopping_${this.modal_id}`)
|
||||
},
|
||||
},
|
||||
|
@ -295,5 +295,6 @@
|
||||
"shopping_category_help": "Supermarkets can be ordered and filtered by Shopping Category according to the layout of the aisles.",
|
||||
"food_recipe_help": "Linking a recipe here will include the linked recipe in any other recipe that use this food",
|
||||
"Foods": "Foods",
|
||||
"review_shopping": "Review shopping entries before saving"
|
||||
"review_shopping": "Review shopping entries before saving",
|
||||
"view_recipe": "View Recipe"
|
||||
}
|
||||
|
Reference in New Issue
Block a user