mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-04 21:58:54 -05:00
removed ingredient list shopping
This commit is contained in:
@@ -7,124 +7,65 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<td class="d-print-none" v-if="detailed && !show_shopping" @click="done">
|
<td class="d-print-none" v-if="detailed" @click="done">
|
||||||
<i class="far fa-check-circle text-success" v-if="ingredient.checked"></i>
|
<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>
|
<i class="far fa-check-circle text-primary" v-if="!ingredient.checked"></i>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-nowrap" @click="done">
|
<td class="text-nowrap" @click="done">
|
||||||
<span v-if="ingredient.amount !== 0 && !ingredient.no_amount" v-html="calculateAmount(ingredient.amount)"></span>
|
<span v-if="ingredient.amount !== 0 && !ingredient.no_amount"
|
||||||
|
v-html="calculateAmount(ingredient.amount)"></span>
|
||||||
</td>
|
</td>
|
||||||
<td @click="done">
|
<td @click="done">
|
||||||
<span v-if="ingredient.unit !== null && !ingredient.no_amount">{{ ingredient.unit.name }}</span>
|
<span v-if="ingredient.unit !== null && !ingredient.no_amount">{{ ingredient.unit.name }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td @click="done">
|
<td @click="done">
|
||||||
<template v-if="ingredient.food !== null">
|
<template v-if="ingredient.food !== null">
|
||||||
<a :href="resolveDjangoUrl('view_recipe', ingredient.food.recipe.id)" v-if="ingredient.food.recipe !== null" target="_blank" rel="noopener noreferrer">{{ ingredient.food.name }}</a>
|
<a :href="resolveDjangoUrl('view_recipe', ingredient.food.recipe.id)"
|
||||||
|
v-if="ingredient.food.recipe !== null" target="_blank"
|
||||||
|
rel="noopener noreferrer">{{ ingredient.food.name }}</a>
|
||||||
<span v-if="ingredient.food.recipe === null">{{ ingredient.food.name }}</span>
|
<span v-if="ingredient.food.recipe === null">{{ ingredient.food.name }}</span>
|
||||||
</template>
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="detailed && !show_shopping">
|
<td v-if="detailed">
|
||||||
<div v-if="ingredient.note">
|
<div v-if="ingredient.note">
|
||||||
<span v-b-popover.hover="ingredient.note" class="d-print-none touchable p-0 pl-md-2 pr-md-2">
|
<span v-b-popover.hover="ingredient.note" class="d-print-none touchable p-0 pl-md-2 pr-md-2">
|
||||||
<i class="far fa-comment"></i>
|
<i class="far fa-comment"></i>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="d-none d-print-block"><i class="far fa-comment-alt d-print-none"></i> {{ ingredient.note }}</div>
|
<div class="d-none d-print-block"><i class="far fa-comment-alt d-print-none"></i> {{
|
||||||
|
ingredient.note
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td v-else-if="show_shopping" class="text-right text-nowrap">
|
|
||||||
<shopping-badge v-if="ingredient.food.ignore_shopping" :item="shoppingBadgeFood" />
|
|
||||||
<b-button
|
|
||||||
v-if="!ingredient.food.ignore_shopping"
|
|
||||||
class="btn text-decoration-none fas fa-shopping-cart px-2 user-select-none"
|
|
||||||
variant="link"
|
|
||||||
v-b-popover.hover.click.blur.html.top="{ title: ShoppingPopover, variant: 'outline-dark' }"
|
|
||||||
:class="{
|
|
||||||
'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">
|
|
||||||
<input type="checkbox" class="align-middle" v-model="shop" @change="changeShopping" />
|
|
||||||
</span>
|
|
||||||
<on-hand-badge v-if="!ingredient.food.ignore_shopping" :item="ingredient.food" />
|
|
||||||
</td>
|
|
||||||
</template>
|
</template>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { calculateAmount, ResolveUrlMixin, ApiMixin } from "@/utils/utils"
|
import {calculateAmount, ResolveUrlMixin} from "@/utils/utils"
|
||||||
import OnHandBadge from "@/components/Badges/OnHand"
|
|
||||||
import ShoppingBadge from "@/components/Badges/Shopping"
|
|
||||||
|
|
||||||
import Vue from "vue"
|
import Vue from "vue"
|
||||||
import VueSanitize from "vue-sanitize";
|
import VueSanitize from "vue-sanitize";
|
||||||
|
|
||||||
Vue.use(VueSanitize);
|
Vue.use(VueSanitize);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "IngredientComponent",
|
name: "IngredientComponent",
|
||||||
components: { OnHandBadge, ShoppingBadge },
|
|
||||||
props: {
|
props: {
|
||||||
ingredient: Object,
|
ingredient: Object,
|
||||||
ingredient_factor: { type: Number, default: 1 },
|
ingredient_factor: {type: Number, default: 1},
|
||||||
detailed: { type: Boolean, default: true },
|
detailed: {type: Boolean, default: true},
|
||||||
show_shopping: { type: Boolean, default: false },
|
|
||||||
},
|
},
|
||||||
mixins: [ResolveUrlMixin, ApiMixin],
|
mixins: [ResolveUrlMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
checked: false,
|
checked: false,
|
||||||
shop: false, // in shopping list for this recipe: boolean
|
|
||||||
dirty: undefined,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {},
|
||||||
ingredient: {
|
|
||||||
handler() {},
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
"ingredient.shop": function (newVal) {
|
|
||||||
this.shop = newVal
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.shop = this.ingredient?.shop
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
shoppingBadgeFood() {
|
|
||||||
// shopping badge is hidden when ignore_shopping=true.
|
|
||||||
// force true in this context to allow adding to shopping list from recipe view
|
|
||||||
return { ...this.ingredient.food, ignore_shopping: false }
|
|
||||||
},
|
|
||||||
ShoppingPopover() {
|
|
||||||
if (this.ingredient?.shopping_status == false) {
|
|
||||||
return this.$t("NotInShopping", { food: this.ingredient.food.name })
|
|
||||||
} else {
|
|
||||||
let category = this.$t("Category") + ": " + this.ingredient?.category ?? this.$t("Undefined")
|
|
||||||
let popover = []
|
|
||||||
;(this.ingredient?.shopping_list ?? []).forEach((x) => {
|
|
||||||
popover.push(
|
|
||||||
[
|
|
||||||
"<tr style='border-bottom: 1px solid #ccc'>",
|
|
||||||
"<td style='padding: 3px;'><em>",
|
|
||||||
x?.mealplan ?? "",
|
|
||||||
"</em></td>",
|
|
||||||
"<td style='padding: 3px;'>",
|
|
||||||
x?.amount ?? "",
|
|
||||||
"</td>",
|
|
||||||
"<td style='padding: 3px;'>",
|
|
||||||
x?.unit ?? "" + "</td>",
|
|
||||||
"<td style='padding: 3px;'>",
|
|
||||||
x?.food ?? "",
|
|
||||||
"</td></tr>",
|
|
||||||
].join("")
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return "<table class='table-small'><th colspan='4'>" + category + "</th>" + popover.join("") + "</table>"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
calculateAmount: function (x) {
|
calculateAmount: function (x) {
|
||||||
@@ -134,10 +75,6 @@ export default {
|
|||||||
done: function () {
|
done: function () {
|
||||||
this.$emit("checked-state-changed", this.ingredient)
|
this.$emit("checked-state-changed", this.ingredient)
|
||||||
},
|
},
|
||||||
// sends true/false to parent to save all ingredient shopping updates as a batch
|
|
||||||
changeShopping: function () {
|
|
||||||
this.$emit("add-to-shopping", { item: this.ingredient, add: this.shop })
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,42 +6,27 @@
|
|||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<h4 class="card-title"><i class="fas fa-pepper-hot"></i> {{ $t("Ingredients") }}</h4>
|
<h4 class="card-title"><i class="fas fa-pepper-hot"></i> {{ $t("Ingredients") }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 text-right" v-if="header">
|
|
||||||
<h4 class="d-print-none card-title">
|
|
||||||
<i v-if="show_shopping && ShoppingRecipes.length > 0" class="fas fa-trash text-danger px-2"
|
|
||||||
@click="saveShopping(true)"></i>
|
|
||||||
<i v-if="show_shopping" class="fas fa-save text-success px-2" @click="saveShopping()"></i>
|
|
||||||
<i class="fas fa-shopping-cart px-2" @click="getShopping()"></i>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-1 p-md-3">
|
<div class="card-body p-1 p-md-3">
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-12 m-0" :class="{ 'p-0': !header }">
|
<div class="col-12 m-0" :class="{ 'p-0': !header }">
|
||||||
<table class="table table-sm mb-0">
|
<table class="table table-sm mb-0">
|
||||||
<!-- eslint-disable vue/no-v-for-template-key-on-child -->
|
<!-- eslint-disable vue/no-v-for-template-key-on-child -->
|
||||||
<template v-for="s in steps">
|
<template v-for="s in steps">
|
||||||
<tr v-bind:key="s.id" v-if="s.show_as_header && s.name !== '' && !add_shopping_mode && steps.length > 1">
|
<tr v-bind:key="s.id" v-if="s.show_as_header && s.name !== '' && steps.length > 1">
|
||||||
<td colspan="5">
|
<td colspan="5">
|
||||||
<b>{{ s.name }}</b>
|
<b>{{ s.name }}</b>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-for="i in s.ingredients">
|
<template v-for="i in s.ingredients">
|
||||||
<ingredient-component
|
<ingredient-component
|
||||||
:ingredient="prepareIngredient(i)"
|
:ingredient="i"
|
||||||
:ingredient_factor="ingredient_factor"
|
:ingredient_factor="ingredient_factor"
|
||||||
:key="i.id"
|
:key="i.id"
|
||||||
:show_shopping="show_shopping"
|
|
||||||
:detailed="detailed"
|
:detailed="detailed"
|
||||||
@checked-state-changed="$emit('checked-state-changed', $event)"
|
@checked-state-changed="$emit('checked-state-changed', $event)"
|
||||||
@add-to-shopping="addShopping($event)"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -80,7 +65,6 @@ export default {
|
|||||||
servings: {type: Number, default: 1},
|
servings: {type: Number, default: 1},
|
||||||
detailed: {type: Boolean, default: true},
|
detailed: {type: Boolean, default: true},
|
||||||
header: {type: Boolean, default: false},
|
header: {type: Boolean, default: false},
|
||||||
add_shopping_mode: {type: Boolean, default: false},
|
|
||||||
recipe_list: {type: Number, default: undefined},
|
recipe_list: {type: Number, default: undefined},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -108,145 +92,13 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
ShoppingRecipes: function (newVal, oldVal) {
|
|
||||||
if (newVal.length === 0 || this.add_shopping_mode) {
|
|
||||||
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() {
|
mounted() {
|
||||||
if (this.add_shopping_mode) {
|
|
||||||
this.show_shopping = true
|
|
||||||
this.getShopping(false)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getShopping: function (toggle_shopping = true) {
|
|
||||||
if (toggle_shopping) {
|
|
||||||
this.show_shopping = !this.show_shopping
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.show_shopping) {
|
|
||||||
let ingredient_list = this.steps
|
|
||||||
.map((x) => x.ingredients)
|
|
||||||
.flat()
|
|
||||||
.filter((x) => (x.food !== null && x.food !== undefined))
|
|
||||||
.map((x) => x.food.id)
|
|
||||||
|
|
||||||
let params = {
|
|
||||||
id: ingredient_list,
|
|
||||||
checked: "false",
|
|
||||||
}
|
|
||||||
this.genericAPI(this.Models.SHOPPING_LIST, this.Actions.LIST, params).then((result) => {
|
|
||||||
this.shopping_list = result.data
|
|
||||||
|
|
||||||
if (this.add_shopping_mode) {
|
|
||||||
if (this.recipe_list) {
|
|
||||||
this.$emit(
|
|
||||||
"starting-cart",
|
|
||||||
this.shopping_list.filter((x) => x.list_recipe === this.recipe_list).map((x) => x.ingredient)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
saveShopping: function (del_shopping = false) {
|
|
||||||
let servings = this.servings
|
|
||||||
if (del_shopping) {
|
|
||||||
servings = -1
|
|
||||||
}
|
|
||||||
let params = {
|
|
||||||
id: this.recipe,
|
|
||||||
list_recipe: this.selected_shoppingrecipe,
|
|
||||||
ingredients: this.update_shopping,
|
|
||||||
servings: servings,
|
|
||||||
}
|
|
||||||
this.genericAPI(this.Models.RECIPE, this.Actions.SHOPPING, params)
|
|
||||||
.then((result) => {
|
|
||||||
if (del_shopping) {
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_DELETE)
|
|
||||||
} else if (this.selected_shoppingrecipe) {
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
|
||||||
} else {
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
if (del_shopping) {
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err)
|
|
||||||
} else if (this.selected_shoppingrecipe) {
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
|
||||||
} else {
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE, err)
|
|
||||||
}
|
|
||||||
this.$emit("shopping-failed")
|
|
||||||
})
|
|
||||||
},
|
|
||||||
addShopping: function (e) {
|
|
||||||
// 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.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>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user