mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-02 04:39:54 -05:00
Squashed commit of shoppinglist_v2
This commit is contained in:
@@ -1,52 +1,44 @@
|
||||
<template>
|
||||
<span>
|
||||
<linked-recipe v-if="linkedRecipe"
|
||||
:item="item"/>
|
||||
<icon-badge v-if="Icon"
|
||||
:item="item"/>
|
||||
<on-hand-badge v-if="OnHand"
|
||||
:item="item"/>
|
||||
<shopping-badge v-if="Shopping"
|
||||
:item="item"/>
|
||||
<linked-recipe v-if="linkedRecipe" :item="item" />
|
||||
<icon-badge v-if="Icon" :item="item" />
|
||||
<on-hand-badge v-if="OnHand" :item="item" />
|
||||
<shopping-badge v-if="Shopping" :item="item" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LinkedRecipe from "@/components/Badges/LinkedRecipe";
|
||||
import IconBadge from "@/components/Badges/Icon";
|
||||
import OnHandBadge from "@/components/Badges/OnHand";
|
||||
import ShoppingBadge from "@/components/Badges/Shopping";
|
||||
import LinkedRecipe from "@/components/Badges/LinkedRecipe"
|
||||
import IconBadge from "@/components/Badges/Icon"
|
||||
import OnHandBadge from "@/components/Badges/OnHand"
|
||||
import ShoppingBadge from "@/components/Badges/Shopping"
|
||||
|
||||
export default {
|
||||
name: 'CardBadges',
|
||||
components: {LinkedRecipe, IconBadge, OnHandBadge, ShoppingBadge},
|
||||
props: {
|
||||
item: {type: Object},
|
||||
model: {type: Object}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
computed: {
|
||||
linkedRecipe: function () {
|
||||
return this.model?.badges?.linked_recipe ?? false
|
||||
name: "CardBadges",
|
||||
components: { LinkedRecipe, IconBadge, OnHandBadge, ShoppingBadge },
|
||||
props: {
|
||||
item: { type: Object },
|
||||
model: { type: Object },
|
||||
},
|
||||
Icon: function () {
|
||||
return this.model?.badges?.icon ?? false
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
OnHand: function () {
|
||||
return this.model?.badges?.on_hand ?? false
|
||||
mounted() {},
|
||||
computed: {
|
||||
linkedRecipe: function () {
|
||||
return this.model?.badges?.linked_recipe ?? false
|
||||
},
|
||||
Icon: function () {
|
||||
return this.model?.badges?.icon ?? false
|
||||
},
|
||||
OnHand: function () {
|
||||
return this.model?.badges?.food_onhand ?? false
|
||||
},
|
||||
Shopping: function () {
|
||||
return this.model?.badges?.shopping ?? false
|
||||
},
|
||||
},
|
||||
Shopping: function () {
|
||||
return this.model?.badges?.shopping ?? false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
watch: {},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<span>
|
||||
<b-button
|
||||
class="btn text-decoration-none fas px-1 py-0 border-0"
|
||||
class="btn text-decoration-none fas px-1 py-0 border-0"
|
||||
variant="link"
|
||||
v-b-popover.hover.html
|
||||
:title="[onhand ? $t('FoodOnHand', { food: item.name }) : $t('FoodNotOnHand', { food: item.name })]"
|
||||
@@ -26,16 +26,16 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.onhand = this.item.on_hand
|
||||
this.onhand = this.item.food_onhand
|
||||
},
|
||||
watch: {
|
||||
"item.on_hand": function(newVal, oldVal) {
|
||||
"item.food_onhand": function (newVal, oldVal) {
|
||||
this.onhand = newVal
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleOnHand() {
|
||||
let params = { id: this.item.id, on_hand: !this.onhand }
|
||||
let params = { id: this.item.id, food_onhand: !this.onhand }
|
||||
this.genericAPI(this.Models.FOOD, this.Actions.UPDATE, params).then(() => {
|
||||
this.onhand = !this.onhand
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span>
|
||||
<b-button class="btn text-decoration-none px-1 border-0" variant="link" v-if="ShowBadge" :id="`shopping${item.id}`" @click="addShopping()">
|
||||
<b-button class="btn text-decoration-none px-1 border-0" variant="link" :id="`shopping${item.id}`" @click="addShopping()">
|
||||
<i
|
||||
class="fas"
|
||||
v-b-popover.hover.html
|
||||
@@ -8,13 +8,13 @@
|
||||
:class="[shopping ? 'text-success fa-shopping-cart' : 'text-muted fa-cart-plus']"
|
||||
/>
|
||||
</b-button>
|
||||
<b-popover :target="`${ShowConfirmation}`" :ref="'shopping' + item.id" triggers="focus" placement="top">
|
||||
<b-popover v-if="shopping" :target="`${ShowConfirmation}`" :ref="'shopping' + item.id" triggers="focus" placement="top">
|
||||
<template #title>{{ DeleteConfirmation }}</template>
|
||||
<b-row align-h="end">
|
||||
<b-col cols="auto"
|
||||
><b-button class="btn btn-sm btn-info shadow-none px-1 border-0" @click="cancelDelete()">{{ $t("Cancel") }}</b-button>
|
||||
<b-button class="btn btn-sm btn-danger shadow-none px-1" @click="confirmDelete()">{{ $t("Confirm") }}</b-button></b-col
|
||||
>
|
||||
<b-col cols="auto">
|
||||
<b-button class="btn btn-sm btn-info shadow-none px-1 border-0" @click="cancelDelete()">{{ $t("Cancel") }}</b-button>
|
||||
<b-button class="btn btn-sm btn-danger shadow-none px-1" @click="confirmDelete()">{{ $t("Confirm") }}</b-button>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-popover>
|
||||
</span>
|
||||
@@ -27,7 +27,6 @@ export default {
|
||||
name: "ShoppingBadge",
|
||||
props: {
|
||||
item: { type: Object },
|
||||
override_ignore: { type: Boolean, default: false },
|
||||
},
|
||||
mixins: [ApiMixin],
|
||||
data() {
|
||||
@@ -40,13 +39,6 @@ export default {
|
||||
this.shopping = this.item?.shopping //?? random[Math.floor(Math.random() * random.length)]
|
||||
},
|
||||
computed: {
|
||||
ShowBadge() {
|
||||
if (this.override_ignore) {
|
||||
return true
|
||||
} else {
|
||||
return !this.item.ignore_shopping
|
||||
}
|
||||
},
|
||||
DeleteConfirmation() {
|
||||
return this.$t("DeleteShoppingConfirm", { food: this.item.name })
|
||||
},
|
||||
@@ -54,12 +46,12 @@ export default {
|
||||
if (this.shopping) {
|
||||
return "shopping" + this.item.id
|
||||
} else {
|
||||
return "NoDialog"
|
||||
return ""
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
"item.shopping": function(newVal, oldVal) {
|
||||
"item.shopping": function (newVal, oldVal) {
|
||||
this.shopping = newVal
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,74 +1,52 @@
|
||||
<template>
|
||||
<!-- <b-button variant="link" size="sm" class="text-dark shadow-none"><i class="fas fa-chevron-down"></i></b-button> -->
|
||||
<span>
|
||||
<b-dropdown variant="link" toggle-class="text-decoration-none text-dark shadow-none" no-caret
|
||||
style="boundary:window">
|
||||
<template #button-content>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</template>
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_food')">
|
||||
<i class="fas fa-leaf fa-fw"></i> {{ Models['FOOD'].name }}
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_keyword')">
|
||||
<i class="fas fa-tags fa-fw"></i> {{ Models['KEYWORD'].name }}
|
||||
</b-dropdown-item>
|
||||
<!-- <b-button variant="link" size="sm" class="text-dark shadow-none"><i class="fas fa-chevron-down"></i></b-button> -->
|
||||
<span>
|
||||
<b-dropdown variant="link" toggle-class="text-decoration-none text-dark shadow-none" no-caret style="boundary: window">
|
||||
<template #button-content>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</template>
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_food')"> <i class="fas fa-leaf fa-fw"></i> {{ Models["FOOD"].name }} </b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_unit')">
|
||||
<i class="fas fa-balance-scale fa-fw"></i> {{ Models['UNIT'].name }}
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_supermarket')">
|
||||
<i class="fas fa-store-alt fa-fw"></i> {{ Models['SUPERMARKET'].name }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_keyword')"> <i class="fas fa-tags fa-fw"></i> {{ Models["KEYWORD"].name }} </b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_supermarket_category')">
|
||||
<i class="fas fa-cubes fa-fw"></i> {{ Models['SHOPPING_CATEGORY'].name }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_unit')"> <i class="fas fa-balance-scale fa-fw"></i> {{ Models["UNIT"].name }} </b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_automation')">
|
||||
<i class="fas fa-robot fa-fw"></i> {{ Models['AUTOMATION'].name }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_supermarket')"> <i class="fas fa-store-alt fa-fw"></i> {{ Models["SUPERMARKET"].name }} </b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_user_file')">
|
||||
<i class="fas fa-file fa-fw"></i> {{ Models['USERFILE'].name }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_supermarket_category')"> <i class="fas fa-cubes fa-fw"></i> {{ Models["SHOPPING_CATEGORY"].name }} </b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_step')">
|
||||
<i class="fas fa-puzzle-piece fa-fw"></i>{{ Models['STEP'].name }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_automation')"> <i class="fas fa-robot fa-fw"></i> {{ Models["AUTOMATION"].name }} </b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_user_file')"> <i class="fas fa-file fa-fw"></i> {{ Models["USERFILE"].name }} </b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_step')"> <i class="fas fa-puzzle-piece fa-fw"></i>{{ Models["STEP"].name }} </b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from "vue"
|
||||
import { BootstrapVue } from "bootstrap-vue"
|
||||
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||
|
||||
import Vue from 'vue'
|
||||
import {BootstrapVue} from 'bootstrap-vue'
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import {Models} from "@/utils/models";
|
||||
import {ResolveUrlMixin} from "@/utils/utils";
|
||||
|
||||
import { Models } from "@/utils/models"
|
||||
import { ResolveUrlMixin } from "@/utils/utils"
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
export default {
|
||||
name: 'ModelMenu',
|
||||
mixins: [ResolveUrlMixin],
|
||||
data() {
|
||||
return {
|
||||
Models: Models
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
gotoURL: function (model) {
|
||||
return
|
||||
}
|
||||
}
|
||||
name: "ModelMenu",
|
||||
mixins: [ResolveUrlMixin],
|
||||
data() {
|
||||
return {
|
||||
Models: Models,
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
gotoURL: function (model) {
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
>
|
||||
<b-row no-gutters>
|
||||
<b-col no-gutters class="col-sm-3">
|
||||
<b-card-img-lazy style="object-fit: cover; height: 6em;" :src="item_image" v-bind:alt="$t('Recipe_Image')"></b-card-img-lazy>
|
||||
<b-card-img-lazy style="object-fit: cover; height: 6em" :src="item_image" v-bind:alt="$t('Recipe_Image')"></b-card-img-lazy>
|
||||
</b-col>
|
||||
<b-col no-gutters class="col-sm-9">
|
||||
<b-card-body class="m-0 py-0">
|
||||
<b-card-text class=" h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis">
|
||||
<b-card-text class="h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis">
|
||||
<h5 class="m-0 mt-1 text-truncate">{{ item[title] }}</h5>
|
||||
<div class="m-0 text-truncate">{{ item[subtitle] }}</div>
|
||||
<div class="m-0 text-truncate small text-muted" v-if="getFullname">{{ getFullname }}</div>
|
||||
|
||||
<generic-pill v-for="x in itemTags" :key="x.field" :item_list="item[x.field]" :label="x.label" :color="x.color" />
|
||||
<generic-ordered-pill
|
||||
@@ -37,21 +38,11 @@
|
||||
@finish-action="finishAction"
|
||||
/>
|
||||
<div class="mt-auto mb-1" align="right">
|
||||
<span
|
||||
v-if="item[child_count]"
|
||||
class="mx-2 btn btn-link btn-sm"
|
||||
style="z-index: 800;"
|
||||
v-on:click="$emit('item-action', { action: 'get-children', source: item })"
|
||||
>
|
||||
<span v-if="item[child_count]" class="mx-2 btn btn-link btn-sm" style="z-index: 800" v-on:click="$emit('item-action', { action: 'get-children', source: item })">
|
||||
<div v-if="!item.show_children">{{ item[child_count] }} {{ itemName }}</div>
|
||||
<div v-else>{{ text.hide_children }}</div>
|
||||
</span>
|
||||
<span
|
||||
v-if="item[recipe_count]"
|
||||
class="mx-2 btn btn-link btn-sm"
|
||||
style="z-index: 800;"
|
||||
v-on:click="$emit('item-action', { action: 'get-recipes', source: item })"
|
||||
>
|
||||
<span v-if="item[recipe_count]" class="mx-2 btn btn-link btn-sm" style="z-index: 800" v-on:click="$emit('item-action', { action: 'get-recipes', source: item })">
|
||||
<div v-if="!item.show_recipes">{{ item[recipe_count] }} {{ $t("Recipes") }}</div>
|
||||
<div v-else>{{ $t("Hide_Recipes") }}</div>
|
||||
</span>
|
||||
@@ -77,20 +68,19 @@
|
||||
<!-- recursively add child cards -->
|
||||
<div class="row" v-if="item.show_children">
|
||||
<div class="col-md-10 offset-md-2">
|
||||
<generic-horizontal-card v-for="child in item[children]" v-bind:key="child.id" :item="child" :model="model" @item-action="$emit('item-action', $event)">
|
||||
</generic-horizontal-card>
|
||||
<generic-horizontal-card v-for="child in item[children]" v-bind:key="child.id" :item="child" :model="model" @item-action="$emit('item-action', $event)"> </generic-horizontal-card>
|
||||
</div>
|
||||
</div>
|
||||
<!-- conditionally view recipes -->
|
||||
<div class="row" v-if="item.show_recipes">
|
||||
<div class="col-md-10 offset-md-2">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));grid-gap: 1rem;">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-gap: 1rem">
|
||||
<recipe-card v-for="r in item[recipes]" v-bind:key="r.id" :recipe="r"> </recipe-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- this should be made a generic component, would also require mixin for functions that generate the popup and put in parent container-->
|
||||
<b-list-group ref="tooltip" variant="light" v-show="show_menu" v-on-clickaway="closeMenu" style="z-index:9999; cursor:pointer">
|
||||
<b-list-group ref="tooltip" variant="light" v-show="show_menu" v-on-clickaway="closeMenu" style="z-index: 9999; cursor: pointer">
|
||||
<b-list-group-item
|
||||
v-if="useMove"
|
||||
action
|
||||
@@ -176,47 +166,53 @@ export default {
|
||||
this.text.hide_children = this.$t("Hide_" + this.itemName)
|
||||
},
|
||||
computed: {
|
||||
itemName: function() {
|
||||
itemName: function () {
|
||||
return this.model?.name ?? "You Forgot To Set Model Name in model.js"
|
||||
},
|
||||
useMove: function() {
|
||||
useMove: function () {
|
||||
return this.model?.["move"] ?? false ? true : false
|
||||
},
|
||||
useMerge: function() {
|
||||
useMerge: function () {
|
||||
return this.model?.["merge"] ?? false ? true : false
|
||||
},
|
||||
useShopping: function() {
|
||||
useShopping: function () {
|
||||
return this.model?.["shop"] ?? false ? true : false
|
||||
},
|
||||
useOnhand: function() {
|
||||
useOnhand: function () {
|
||||
return this.model?.["onhand"] ?? false ? true : false
|
||||
},
|
||||
useDrag: function() {
|
||||
useDrag: function () {
|
||||
return this.useMove || this.useMerge
|
||||
},
|
||||
itemTags: function() {
|
||||
itemTags: function () {
|
||||
return this.model?.tags ?? []
|
||||
},
|
||||
itemOrderedTags: function() {
|
||||
itemOrderedTags: function () {
|
||||
return this.model?.ordered_tags ?? []
|
||||
},
|
||||
getFullname: function () {
|
||||
if (!this.item?.full_name?.includes(">")) {
|
||||
return undefined
|
||||
}
|
||||
return this.item?.full_name
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleDragStart: function(e) {
|
||||
handleDragStart: function (e) {
|
||||
this.isError = false
|
||||
e.dataTransfer.setData("source", JSON.stringify(this.item))
|
||||
},
|
||||
handleDragEnter: function(e) {
|
||||
handleDragEnter: function (e) {
|
||||
if (!e.currentTarget.contains(e.relatedTarget) && e.relatedTarget != null) {
|
||||
this.over = true
|
||||
}
|
||||
},
|
||||
handleDragLeave: function(e) {
|
||||
handleDragLeave: function (e) {
|
||||
if (!e.currentTarget.contains(e.relatedTarget)) {
|
||||
this.over = false
|
||||
}
|
||||
},
|
||||
handleDragDrop: function(e) {
|
||||
handleDragDrop: function (e) {
|
||||
let source = JSON.parse(e.dataTransfer.getData("source"))
|
||||
if (source.id != this.item.id) {
|
||||
this.source = source
|
||||
@@ -247,7 +243,7 @@ export default {
|
||||
this.isError = true
|
||||
}
|
||||
},
|
||||
generateLocation: function(x = 0, y = 0) {
|
||||
generateLocation: function (x = 0, y = 0) {
|
||||
return () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
@@ -257,10 +253,10 @@ export default {
|
||||
left: x,
|
||||
})
|
||||
},
|
||||
closeMenu: function() {
|
||||
closeMenu: function () {
|
||||
this.show_menu = false
|
||||
},
|
||||
finishAction: function(e) {
|
||||
finishAction: function (e) {
|
||||
this.$emit("finish-action", e)
|
||||
},
|
||||
},
|
||||
|
||||
@@ -10,12 +10,7 @@
|
||||
export default {
|
||||
name: "GenericPill",
|
||||
props: {
|
||||
item_list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
item_list: { type: Object },
|
||||
label: { type: String, default: "name" },
|
||||
color: { type: String, default: "light" },
|
||||
},
|
||||
|
||||
@@ -33,31 +33,20 @@
|
||||
</div>
|
||||
</td>
|
||||
<td v-else-if="show_shopping" class="text-right text-nowrap">
|
||||
<!-- in shopping mode and ingredient is not ignored -->
|
||||
<div v-if="!ingredient.food.ignore_shopping">
|
||||
<b-button
|
||||
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': shopping_status === true,
|
||||
'text-muted': shopping_status === false,
|
||||
'text-warning': shopping_status === null,
|
||||
}"
|
||||
/>
|
||||
<span class="px-2">
|
||||
<input type="checkbox" class="align-middle" v-model="shop" @change="changeShopping" />
|
||||
</span>
|
||||
<on-hand-badge :item="ingredient.food" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- or in shopping mode and food is ignored: Shopping Badge bypasses linking ingredient to Recipe which would get ignored -->
|
||||
<shopping-badge :item="ingredient.food" :override_ignore="true" class="px-1" />
|
||||
<span class="px-2">
|
||||
<input type="checkbox" class="align-middle" disabled v-b-popover.hover.click.blur :title="$t('IgnoredFood', { food: ingredient.food.name })" />
|
||||
</span>
|
||||
<on-hand-badge :item="ingredient.food" />
|
||||
</div>
|
||||
<b-button
|
||||
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': shopping_status === true,
|
||||
'text-muted': shopping_status === false,
|
||||
'text-warning': shopping_status === null,
|
||||
}"
|
||||
/>
|
||||
<span class="px-2">
|
||||
<input type="checkbox" class="align-middle" v-model="shop" @change="changeShopping" />
|
||||
</span>
|
||||
<on-hand-badge :item="ingredient.food" />
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
@@ -66,11 +55,10 @@
|
||||
<script>
|
||||
import { calculateAmount, ResolveUrlMixin, ApiMixin } from "@/utils/utils"
|
||||
import OnHandBadge from "@/components/Badges/OnHand"
|
||||
import ShoppingBadge from "@/components/Badges/Shopping"
|
||||
|
||||
export default {
|
||||
name: "IngredientComponent",
|
||||
components: { OnHandBadge, ShoppingBadge },
|
||||
components: { OnHandBadge },
|
||||
props: {
|
||||
ingredient: Object,
|
||||
ingredient_factor: { type: Number, default: 1 },
|
||||
@@ -89,9 +77,9 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
checked: false,
|
||||
shopping_status: null,
|
||||
shopping_status: null, // in any shopping list: boolean + null=in shopping list, but not for this recipe
|
||||
shopping_items: [],
|
||||
shop: false,
|
||||
shop: false, // in shopping list for this recipe: boolean
|
||||
dirty: undefined,
|
||||
}
|
||||
},
|
||||
@@ -99,6 +87,13 @@ export default {
|
||||
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) {
|
||||
@@ -108,34 +103,39 @@ export default {
|
||||
let count_shopping_recipes = [...new Set(filtered_list.map((x) => x.list_recipe))].length
|
||||
let count_shopping_ingredient = filtered_list.filter((x) => x.ingredient == this.ingredient.id).length
|
||||
|
||||
if (count_shopping_recipes > 1) {
|
||||
if (count_shopping_recipes >= 1) {
|
||||
// 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
|
||||
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 {
|
||||
this.shopping_status = false // food is not in any shopping list
|
||||
// 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.$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
|
||||
this.shop = true
|
||||
// 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.shop = false
|
||||
this.shopping_status = null
|
||||
} else {
|
||||
// the food is not in any shopping list
|
||||
this.shop = false
|
||||
this.shopping_status = false
|
||||
}
|
||||
}
|
||||
// if we are in add shopping mode start with all checks marked
|
||||
|
||||
if (this.add_shopping_mode) {
|
||||
this.shop = !this.ingredient.food.on_hand && !this.ingredient.food.ignore_shopping && !this.ingredient.food.recipe
|
||||
// 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
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-modal :id="'modal_' + id" @hidden="cancelAction">
|
||||
<template v-slot:modal-title
|
||||
><h4>{{ form.title }}</h4></template
|
||||
>
|
||||
<template v-slot:modal-title>
|
||||
<h4>{{ form.title }}</h4>
|
||||
</template>
|
||||
<div v-for="(f, i) in form.fields" v-bind:key="i">
|
||||
<p v-if="f.type == 'instruction'">{{ f.label }}</p>
|
||||
<!-- this lookup is single selection -->
|
||||
<lookup-input v-if="f.type == 'lookup'" :form="f" :model="listModel(f.list)" @change="storeValue" />
|
||||
<!-- TODO: add multi-selection input list -->
|
||||
<checkbox-input v-if="f.type == 'checkbox'" :label="f.label" :value="f.value" :field="f.field" />
|
||||
<text-input v-if="f.type == 'text'" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" />
|
||||
<choice-input v-if="f.type == 'choice'" :label="f.label" :value="f.value" :field="f.field" :options="f.options" :placeholder="f.placeholder" />
|
||||
<emoji-input v-if="f.type == 'emoji'" :label="f.label" :value="f.value" :field="f.field" @change="storeValue" />
|
||||
<file-input v-if="f.type == 'file'" :label="f.label" :value="f.value" :field="f.field" @change="storeValue" />
|
||||
<p v-if="visibleCondition(f, 'instruction')">{{ f.label }}</p>
|
||||
<lookup-input v-if="visibleCondition(f, 'lookup')" :form="f" :model="listModel(f.list)" @change="storeValue" />
|
||||
<checkbox-input class="mb-3" v-if="visibleCondition(f, 'checkbox')" :label="f.label" :value="f.value" :field="f.field" />
|
||||
<text-input v-if="visibleCondition(f, 'text')" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" />
|
||||
<choice-input v-if="visibleCondition(f, 'choice')" :label="f.label" :value="f.value" :field="f.field" :options="f.options" :placeholder="f.placeholder" />
|
||||
<emoji-input v-if="visibleCondition(f, 'emoji')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue" />
|
||||
<file-input v-if="visibleCondition(f, 'file')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue" />
|
||||
<small-text v-if="visibleCondition(f, 'smalltext')" :value="f.value" />
|
||||
</div>
|
||||
|
||||
<template v-slot:modal-footer>
|
||||
@@ -39,14 +38,20 @@ import TextInput from "@/components/Modals/TextInput"
|
||||
import EmojiInput from "@/components/Modals/EmojiInput"
|
||||
import ChoiceInput from "@/components/Modals/ChoiceInput"
|
||||
import FileInput from "@/components/Modals/FileInput"
|
||||
import SmallText from "@/components/Modals/SmallText"
|
||||
|
||||
export default {
|
||||
name: "GenericModalForm",
|
||||
components: { FileInput, CheckboxInput, LookupInput, TextInput, EmojiInput, ChoiceInput },
|
||||
components: { FileInput, CheckboxInput, LookupInput, TextInput, EmojiInput, ChoiceInput, SmallText },
|
||||
mixins: [ApiMixin, ToastMixin],
|
||||
props: {
|
||||
model: { required: true, type: Object },
|
||||
action: { type: Object },
|
||||
action: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
item1: {
|
||||
type: Object,
|
||||
default() {
|
||||
@@ -247,6 +252,21 @@ export default {
|
||||
apiClient.createAutomation(automation)
|
||||
}
|
||||
},
|
||||
visibleCondition(field, field_type) {
|
||||
let type_match = field?.type == field_type
|
||||
let checks = true
|
||||
if (type_match && field?.condition) {
|
||||
if (field.condition?.condition === "exists") {
|
||||
if ((this.item1[field.condition.field] != undefined) === field.condition.value) {
|
||||
checks = true
|
||||
} else {
|
||||
checks = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return type_match && checks
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -106,7 +106,7 @@ export default {
|
||||
...this.steps
|
||||
.map((x) => x.ingredients)
|
||||
.flat()
|
||||
.filter((x) => !x?.food?.on_hand && !x?.food?.ignore_shopping)
|
||||
.filter((x) => !x?.food?.food_onhand)
|
||||
.map((x) => x.id),
|
||||
]
|
||||
this.recipe_servings = result.data?.servings
|
||||
@@ -141,7 +141,7 @@ export default {
|
||||
.flat()
|
||||
.map((x) => x.ingredients)
|
||||
.flat()
|
||||
.filter((x) => !x.food.on_hand && !x.food.ignore_shopping)
|
||||
.filter((x) => !x.food.override_ignore)
|
||||
.map((x) => x.id),
|
||||
]
|
||||
})
|
||||
|
||||
20
vue/src/components/Modals/SmallText.vue
Normal file
20
vue/src/components/Modals/SmallText.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="small text-muted">
|
||||
{{ value }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TextInput",
|
||||
props: {
|
||||
value: { type: String, default: "" },
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
mounted() {},
|
||||
watch: {},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
@@ -1,42 +1,34 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-form-group
|
||||
v-bind:label="label"
|
||||
class="mb-3">
|
||||
<b-form-input
|
||||
v-model="new_value"
|
||||
type="string"
|
||||
:placeholder="placeholder"
|
||||
></b-form-input>
|
||||
<b-form-group v-bind:label="label" class="mb-3">
|
||||
<b-form-input v-model="new_value" type="text" :placeholder="placeholder"></b-form-input>
|
||||
</b-form-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'TextInput',
|
||||
props: {
|
||||
field: {type: String, default: 'You Forgot To Set Field Name'},
|
||||
label: {type: String, default: 'Text Field'},
|
||||
value: {type: String, default: ''},
|
||||
placeholder: {type: String, default: 'You Should Add Placeholder Text'},
|
||||
show_merge: {type: Boolean, default: false},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
new_value: undefined,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.new_value = this.value
|
||||
},
|
||||
watch: {
|
||||
'new_value': function () {
|
||||
this.$root.$emit('change', this.field, this.new_value)
|
||||
name: "TextInput",
|
||||
props: {
|
||||
field: { type: String, default: "You Forgot To Set Field Name" },
|
||||
label: { type: String, default: "Text Field" },
|
||||
value: { type: String, default: "" },
|
||||
placeholder: { type: String, default: "You Should Add Placeholder Text" },
|
||||
show_merge: { type: Boolean, default: false },
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
data() {
|
||||
return {
|
||||
new_value: undefined,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.new_value = this.value
|
||||
},
|
||||
watch: {
|
||||
new_value: function () {
|
||||
this.$root.$emit("change", this.field, this.new_value)
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<!-- detail rows -->
|
||||
<div class="card no-body" v-if="showDetails">
|
||||
<b-container fluid>
|
||||
<div v-for="(e, z) in entries" :key="z">
|
||||
<div v-for="e in entries" :key="e.id">
|
||||
<b-row class="ml-2 small">
|
||||
<b-col cols="6" md="4" class="overflow-hidden text-nowrap">
|
||||
<button
|
||||
@@ -63,7 +63,10 @@
|
||||
</button>
|
||||
</b-col>
|
||||
<b-col cols="6" md="4" class="col-md-4 text-muted">{{ formatOneMealPlan(e) }}</b-col>
|
||||
<b-col cols="12" md="4" class="col-md-4 text-muted text-right overflow-hidden text-nowrap">{{ formatOneCreatedBy(e) }}</b-col>
|
||||
<b-col cols="12" md="4" class="col-md-4 text-muted text-right overflow-hidden text-nowrap">
|
||||
{{ formatOneCreatedBy(e) }}
|
||||
<div v-if="formatOneCompletedAt(e)">{{ formatOneCompletedAt(e) }}</div>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<b-row class="ml-2 light">
|
||||
@@ -240,9 +243,6 @@ export default {
|
||||
formatOneFood: function (item) {
|
||||
return item.food.name
|
||||
},
|
||||
formatOneChecked: function (item) {
|
||||
return item.checked
|
||||
},
|
||||
formatOneDelayUntil: function (item) {
|
||||
if (!item.delay_until || (item.delay_until && item.checked)) {
|
||||
return false
|
||||
@@ -273,12 +273,13 @@ export default {
|
||||
})
|
||||
},
|
||||
updateChecked: function (e, item) {
|
||||
let update = undefined
|
||||
if (!item) {
|
||||
let update = { entries: this.entries.map((x) => x.id), checked: !this.formatChecked }
|
||||
this.$emit("update-checkbox", update)
|
||||
update = { entries: this.entries.map((x) => x.id), checked: !this.formatChecked }
|
||||
} else {
|
||||
this.$emit("update-checkbox", { id: item.id, checked: !item.checked })
|
||||
update = { entries: [item], checked: !item.checked }
|
||||
}
|
||||
this.$emit("update-checkbox", update)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -11,11 +11,7 @@
|
||||
<small style="margin-left: 4px" class="text-muted" v-if="step.time !== 0"><i class="fas fa-user-clock"></i> {{ step.time }} {{ $t("min") }} </small>
|
||||
<small v-if="start_time !== ''" class="d-print-none">
|
||||
<b-link :id="`id_reactive_popover_${step.id}`" @click="openPopover" href="#">
|
||||
{{
|
||||
moment(start_time)
|
||||
.add(step.time_offset, "minutes")
|
||||
.format("HH:mm")
|
||||
}}
|
||||
{{ moment(start_time).add(step.time_offset, "minutes").format("HH:mm") }}
|
||||
</b-link>
|
||||
</small>
|
||||
</h5>
|
||||
@@ -57,11 +53,7 @@
|
||||
</h4>
|
||||
<span style="margin-left: 4px" class="text-muted" v-if="step.time !== 0"><i class="fa fa-stopwatch"></i> {{ step.time }} {{ $t("min") }}</span>
|
||||
<b-link class="d-print-none" :id="`id_reactive_popover_${step.id}`" @click="openPopover" href="#" v-if="start_time !== ''">
|
||||
{{
|
||||
moment(start_time)
|
||||
.add(step.time_offset, "minutes")
|
||||
.format("HH:mm")
|
||||
}}
|
||||
{{ moment(start_time).add(step.time_offset, "minutes").format("HH:mm") }}
|
||||
</b-link>
|
||||
</div>
|
||||
|
||||
@@ -106,14 +98,14 @@
|
||||
<a :href="resolveDjangoUrl('view_recipe', step.step_recipe_data.id)">{{ step.step_recipe_data.name }}</a>
|
||||
</h2>
|
||||
<div v-for="(sub_step, index) in step.step_recipe_data.steps" v-bind:key="`substep_${sub_step.id}`">
|
||||
<Step
|
||||
<step-component
|
||||
:recipe="step.step_recipe_data"
|
||||
:step="sub_step"
|
||||
:ingredient_factor="ingredient_factor"
|
||||
:index="index"
|
||||
:start_time="start_time"
|
||||
:force_ingredients="true"
|
||||
></Step>
|
||||
></step-component>
|
||||
</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
@@ -128,8 +120,8 @@
|
||||
</div>
|
||||
<div class="row" style="margin-top: 1vh">
|
||||
<div class="col-12" style="text-align: right">
|
||||
<b-button @click="closePopover" size="sm" variant="secondary" style="margin-right:8px">Cancel</b-button>
|
||||
<b-button @click="updateTime" size="sm" variant="primary">Ok</b-button>
|
||||
<b-button @click="closePopover" size="sm" variant="secondary" style="margin-right: 8px">{{ $t("Cancel") }}</b-button>
|
||||
<b-button @click="updateTime" size="sm" variant="primary">{{ $t("Ok") }}</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</b-popover>
|
||||
@@ -172,16 +164,14 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.set_time_input = moment(this.start_time)
|
||||
.add(this.step.time_offset, "minutes")
|
||||
.format("yyyy-MM-DDTHH:mm")
|
||||
this.set_time_input = moment(this.start_time).add(this.step.time_offset, "minutes").format("yyyy-MM-DDTHH:mm")
|
||||
},
|
||||
methods: {
|
||||
calculateAmount: function(x) {
|
||||
calculateAmount: function (x) {
|
||||
// used by the jinja2 template
|
||||
return calculateAmount(x, this.ingredient_factor)
|
||||
},
|
||||
updateTime: function() {
|
||||
updateTime: function () {
|
||||
let new_start_time = moment(this.set_time_input)
|
||||
.add(this.step.time_offset * -1, "minutes")
|
||||
.format("yyyy-MM-DDTHH:mm")
|
||||
@@ -189,10 +179,10 @@ export default {
|
||||
this.$emit("update-start-time", new_start_time)
|
||||
this.closePopover()
|
||||
},
|
||||
closePopover: function() {
|
||||
closePopover: function () {
|
||||
this.$refs[`id_reactive_popover_${this.step.id}`].$emit("close")
|
||||
},
|
||||
openPopover: function() {
|
||||
openPopover: function () {
|
||||
this.$refs[`id_reactive_popover_${this.step.id}`].$emit("open")
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user