add to shopping from card context menu
This commit is contained in:
parent
867e2d4fbf
commit
60d7e63da8
@ -673,7 +673,7 @@ class ShoppingListEntrySerializer(WritableNestedModelSerializer):
|
|||||||
recipe_mealplan = ShoppingListRecipeSerializer(source='list_recipe', read_only=True)
|
recipe_mealplan = ShoppingListRecipeSerializer(source='list_recipe', read_only=True)
|
||||||
amount = CustomDecimalField()
|
amount = CustomDecimalField()
|
||||||
created_by = UserNameSerializer(read_only=True)
|
created_by = UserNameSerializer(read_only=True)
|
||||||
completed_at = serializers.DateTimeField(allow_null=True)
|
completed_at = serializers.DateTimeField(allow_null=True, required=False)
|
||||||
|
|
||||||
def get_fields(self, *args, **kwargs):
|
def get_fields(self, *args, **kwargs):
|
||||||
fields = super().get_fields(*args, **kwargs)
|
fields = super().get_fields(*args, **kwargs)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% comment %} TODO: Deprecate {% endcomment %}
|
||||||
|
|
||||||
<div class="modal" tabindex="-1" role="dialog" id="id_modal_cook_log">
|
<div class="modal" tabindex="-1" role="dialog" id="id_modal_cook_log">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
@ -77,4 +78,4 @@
|
|||||||
$('#id_rating_show').html(rating.val() + '/5')
|
$('#id_rating_show').html(rating.val() + '/5')
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
{% extends "base.html" %} {% comment %} TODO: Deprecate {% endcomment %} {% load django_tables2 %} {% load crispy_forms_tags %} {% load static %} {% load i18n %} {% block title
|
{% extends "base.html" %}
|
||||||
%}{% trans "Shopping List" %}{% endblock %} {% block extra_head %} {% include 'include/vue_base.html' %}
|
{% comment %} TODO: Deprecate {% endcomment %}
|
||||||
|
{% load django_tables2 %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Shopping List" %}{% endblock %}
|
||||||
|
{% block extra_head %}
|
||||||
|
{% include 'include/vue_base.html' %}
|
||||||
|
|
||||||
<link rel="stylesheet" href="{% static 'css/vue-multiselect-bs4.min.css' %}" />
|
<link rel="stylesheet" href="{% static 'css/vue-multiselect-bs4.min.css' %}" />
|
||||||
<script src="{% static 'js/vue-multiselect.min.js' %}"></script>
|
<script src="{% static 'js/vue-multiselect.min.js' %}"></script>
|
||||||
@ -907,7 +914,7 @@
|
|||||||
this.makeToast(gettext('Error'), gettext("There was an error loading a resource!") + err.bodyText, 'danger')
|
this.makeToast(gettext('Error'), gettext("There was an error loading a resource!") + err.bodyText, 'danger')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
searchSupermarket: function (query) { //TODO move to central component
|
searchSupermarket: function (query) {
|
||||||
this.supermarkets_loading = true
|
this.supermarkets_loading = true
|
||||||
this.$http.get("{% url 'api:supermarket-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
this.$http.get("{% url 'api:supermarket-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||||
this.supermarkets = response.data
|
this.supermarkets = response.data
|
||||||
|
@ -714,7 +714,6 @@
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
makeToast: function (title, message, variant = null) {
|
makeToast: function (title, message, variant = null) {
|
||||||
//TODO remove duplicate function in favor of central one
|
|
||||||
this.$bvToast.toast(message, {
|
this.$bvToast.toast(message, {
|
||||||
title: title,
|
title: title,
|
||||||
variant: variant,
|
variant: variant,
|
||||||
|
@ -657,7 +657,6 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
|||||||
servings = request.data.get('servings', obj.servings)
|
servings = request.data.get('servings', obj.servings)
|
||||||
list_recipe = request.data.get('list_recipe', None)
|
list_recipe = request.data.get('list_recipe', None)
|
||||||
content = {'msg': _(f'{obj.name} was added to the shopping list.')}
|
content = {'msg': _(f'{obj.name} was added to the shopping list.')}
|
||||||
# TODO: Consider if this should be a Recipe method
|
|
||||||
list_from_recipe(list_recipe=list_recipe, recipe=obj, ingredients=ingredients, servings=servings, space=request.space, created_by=request.user)
|
list_from_recipe(list_recipe=list_recipe, recipe=obj, ingredients=ingredients, servings=servings, space=request.space, created_by=request.user)
|
||||||
|
|
||||||
return Response(content, status=status.HTTP_204_NO_CONTENT)
|
return Response(content, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -90,7 +90,7 @@ export default {
|
|||||||
left_counts: { max: 9999, current: 0 },
|
left_counts: { max: 9999, current: 0 },
|
||||||
this_model: undefined,
|
this_model: undefined,
|
||||||
model_menu: undefined,
|
model_menu: undefined,
|
||||||
this_action: undefined,
|
this_action: {},
|
||||||
this_recipe_param: undefined,
|
this_recipe_param: undefined,
|
||||||
this_item: {},
|
this_item: {},
|
||||||
this_target: {},
|
this_target: {},
|
||||||
@ -193,6 +193,13 @@ export default {
|
|||||||
this.getRecipes(param, source)
|
this.getRecipes(param, source)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case "add-shopping":
|
||||||
|
//TODO: add modal to edit units and amount
|
||||||
|
this.addShopping(e.source)
|
||||||
|
break
|
||||||
|
case "add-onhand":
|
||||||
|
this.addOnhand(e.source)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
finishAction: function (e) {
|
finishAction: function (e) {
|
||||||
@ -232,26 +239,6 @@ export default {
|
|||||||
let results = result.data?.results ?? result.data
|
let results = result.data?.results ?? result.data
|
||||||
|
|
||||||
if (results?.length) {
|
if (results?.length) {
|
||||||
// let secondaryRequest = undefined;
|
|
||||||
// if (this['items_' + column]?.length < getConfig(this.this_model, this.Actions.LIST).config.pageSize.default * (params.page - 1)) {
|
|
||||||
// // the item list is smaller than it should be based on the site the user is own
|
|
||||||
// // this happens when an item is deleted (or merged)
|
|
||||||
// // to prevent issues insert the last item of the previous search page before loading the new results
|
|
||||||
// params.page = params.page - 1
|
|
||||||
// secondaryRequest = this.genericAPI(this.this_model, this.Actions.LIST, params).then((result) => {
|
|
||||||
// let prev_page_results = result.data?.results ?? result.data
|
|
||||||
// if (prev_page_results?.length) {
|
|
||||||
// results = [prev_page_results[prev_page_results.length]].concat(results)
|
|
||||||
//
|
|
||||||
// this['items_' + column] = this['items_' + column].concat(results) //TODO duplicate code, find some elegant workaround
|
|
||||||
// this[column + '_counts']['current'] = getConfig(this.this_model, this.Actions.LIST).config.pageSize.default * (params.page - 1) + results.length
|
|
||||||
// this[column + '_counts']['max'] = result.data?.count ?? 0
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// } else {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
this["items_" + column] = this["items_" + column].concat(results)
|
this["items_" + column] = this["items_" + column].concat(results)
|
||||||
this[column + "_counts"]["current"] = getConfig(this.this_model, this.Actions.LIST).config.pageSize.default * (params.page - 1) + results.length
|
this[column + "_counts"]["current"] = getConfig(this.this_model, this.Actions.LIST).config.pageSize.default * (params.page - 1) + results.length
|
||||||
this[column + "_counts"]["max"] = result.data?.count ?? 0
|
this[column + "_counts"]["max"] = result.data?.count ?? 0
|
||||||
@ -276,6 +263,21 @@ export default {
|
|||||||
// this creates a deep copy to make sure that columns stay independent
|
// this creates a deep copy to make sure that columns stay independent
|
||||||
this.items_right = [{ ...item }].concat(this.destroyCard(item?.id, this.items_right))
|
this.items_right = [{ ...item }].concat(this.destroyCard(item?.id, this.items_right))
|
||||||
},
|
},
|
||||||
|
// this currently assumes shopping is only applicable on FOOD model
|
||||||
|
addShopping: function (food) {
|
||||||
|
let api = new ApiApiFactory()
|
||||||
|
food.shopping = true
|
||||||
|
api.createShoppingListEntry({ food: food, amount: 1 }).then(() => {
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||||
|
this.refreshCard(food, this.items_left)
|
||||||
|
this.refreshCard({ ...food }, this.items_right)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addOnhand: function (item) {
|
||||||
|
item.on_hand = true
|
||||||
|
this.saveThis(item)
|
||||||
|
},
|
||||||
|
|
||||||
updateThis: function (item) {
|
updateThis: function (item) {
|
||||||
this.refreshThis(item.id)
|
this.refreshThis(item.id)
|
||||||
},
|
},
|
||||||
@ -300,7 +302,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
makeToast(this.$t("Error"), err.bodyText, "danger")
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_MOVE, err?.bodyText)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
moveUpdateItem: function (source_id, target_id) {
|
moveUpdateItem: function (source_id, target_id) {
|
||||||
@ -336,12 +338,12 @@ export default {
|
|||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.mergeUpdateItem(source_id, target_id)
|
this.mergeUpdateItem(source_id, target_id)
|
||||||
// TODO make standard toast
|
// TODO make standard toast
|
||||||
makeToast(this.$t("Success"), "Succesfully merged resource", "success")
|
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_MERGE)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
//TODO error checking not working with OpenAPI methods
|
//TODO error checking not working with OpenAPI methods
|
||||||
console.log("Error", err)
|
console.log("Error", err)
|
||||||
makeToast(this.$t("Error"), err.bodyText, "danger")
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_MOVE, err?.bodyText)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (automate) {
|
if (automate) {
|
||||||
@ -425,7 +427,7 @@ export default {
|
|||||||
},
|
},
|
||||||
clearState: function () {
|
clearState: function () {
|
||||||
this.show_modal = false
|
this.show_modal = false
|
||||||
this.this_action = undefined
|
this.this_action = {}
|
||||||
this.this_item = undefined
|
this.this_item = undefined
|
||||||
this.this_target = undefined
|
this.this_target = undefined
|
||||||
},
|
},
|
||||||
|
@ -1,40 +1,45 @@
|
|||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<b-button class="btn text-decoration-none fas px-1 py-0 border-0" variant="link" v-b-popover.hover.html
|
<b-button
|
||||||
:title="[onhand ? $t('FoodOnHand', {'food': item.name}) : $t('FoodNotOnHand', {'food': item.name})]"
|
class="btn text-decoration-none fas px-1 py-0 border-0"
|
||||||
:class="[onhand ? 'text-success fa-clipboard-check' : 'text-muted fa-clipboard' ]"
|
variant="link"
|
||||||
@click="toggleOnHand"
|
v-b-popover.hover.html
|
||||||
|
:title="[onhand ? $t('FoodOnHand', { food: item.name }) : $t('FoodNotOnHand', { food: item.name })]"
|
||||||
|
:class="[onhand ? 'text-success fa-clipboard-check' : 'text-muted fa-clipboard']"
|
||||||
|
@click="toggleOnHand"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { ApiMixin } from "@/utils/utils"
|
||||||
import {ApiMixin} from "@/utils/utils";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'OnHandBadge',
|
name: "OnHandBadge",
|
||||||
props: {
|
props: {
|
||||||
item: {type: Object}
|
item: { type: Object },
|
||||||
},
|
},
|
||||||
mixins: [ ApiMixin ],
|
mixins: [ApiMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
onhand: false
|
onhand: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.onhand = this.item.on_hand
|
this.onhand = this.item.on_hand
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
},
|
"item.on_hand": function(newVal, oldVal) {
|
||||||
methods: {
|
this.onhand = newVal
|
||||||
toggleOnHand() {
|
},
|
||||||
let params = {'id': this.item.id, 'on_hand': !this.onhand}
|
},
|
||||||
this.genericAPI(this.Models.FOOD, this.Actions.UPDATE, params).then(() => {
|
methods: {
|
||||||
this.onhand = !this.onhand
|
toggleOnHand() {
|
||||||
})
|
let params = { id: this.item.id, on_hand: !this.onhand }
|
||||||
}
|
this.genericAPI(this.Models.FOOD, this.Actions.UPDATE, params).then(() => {
|
||||||
}
|
this.onhand = !this.onhand
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,94 +1,96 @@
|
|||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<b-button class="btn text-decoration-none px-1 border-0" variant="link"
|
<b-button class="btn text-decoration-none px-1 border-0" variant="link" v-if="ShowBadge" :id="`shopping${item.id}`" @click="addShopping()">
|
||||||
v-if="ShowBadge"
|
<i
|
||||||
:id="`shopping${item.id}`"
|
class="fas"
|
||||||
@click="addShopping()">
|
v-b-popover.hover.html
|
||||||
<i class="fas"
|
:title="[shopping ? $t('RemoveFoodFromShopping', { food: item.name }) : $t('AddFoodToShopping', { food: item.name })]"
|
||||||
v-b-popover.hover.html
|
:class="[shopping ? 'text-success fa-shopping-cart' : 'text-muted fa-cart-plus']"
|
||||||
:title="[shopping ? $t('RemoveFoodFromShopping', {'food': item.name}) : $t('AddFoodToShopping', {'food': item.name})]"
|
/>
|
||||||
: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-button>
|
<template #title>{{ DeleteConfirmation }}</template>
|
||||||
<b-popover :target="`${ShowConfirmation}`" :ref="'shopping'+item.id" triggers="focus" placement="top" >
|
<b-row align-h="end">
|
||||||
<template #title>{{DeleteConfirmation}}</template>
|
<b-col cols="auto"
|
||||||
<b-row align-h="end">
|
><b-button class="btn btn-sm btn-info shadow-none px-1 border-0" @click="cancelDelete()">{{ $t("Cancel") }}</b-button>
|
||||||
<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-button class="btn btn-sm btn-danger shadow-none px-1" @click="confirmDelete()">{{$t("Confirm")}}</b-button></b-col>
|
>
|
||||||
</b-row >
|
</b-row>
|
||||||
</b-popover>
|
</b-popover>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {ApiMixin, StandardToasts} from "@/utils/utils";
|
import { ApiMixin, StandardToasts } from "@/utils/utils"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ShoppingBadge',
|
name: "ShoppingBadge",
|
||||||
props: {
|
props: {
|
||||||
item: {type: Object},
|
item: { type: Object },
|
||||||
override_ignore: {type: Boolean, default: false}
|
override_ignore: { type: Boolean, default: false },
|
||||||
},
|
|
||||||
mixins: [ ApiMixin ],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
shopping: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// let random = [true, false,]
|
|
||||||
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() {
|
mixins: [ApiMixin],
|
||||||
return this.$t('DeleteShoppingConfirm',{'food':this.item.name})
|
data() {
|
||||||
|
return {
|
||||||
|
shopping: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// let random = [true, false,]
|
||||||
|
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 })
|
||||||
|
},
|
||||||
|
ShowConfirmation() {
|
||||||
|
if (this.shopping) {
|
||||||
|
return "shopping" + this.item.id
|
||||||
|
} else {
|
||||||
|
return "NoDialog"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
"item.shopping": function(newVal, oldVal) {
|
||||||
|
this.shopping = newVal
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addShopping() {
|
||||||
|
if (this.shopping) {
|
||||||
|
return
|
||||||
|
} // if item already in shopping list, excution handled after confirmation
|
||||||
|
let params = {
|
||||||
|
id: this.item.id,
|
||||||
|
amount: 1,
|
||||||
|
}
|
||||||
|
this.genericAPI(this.Models.FOOD, this.Actions.SHOPPING, params).then((result) => {
|
||||||
|
this.shopping = true
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancelDelete() {
|
||||||
|
this.$refs["shopping" + this.item.id].$emit("close")
|
||||||
|
},
|
||||||
|
confirmDelete() {
|
||||||
|
let params = {
|
||||||
|
id: this.item.id,
|
||||||
|
_delete: "true",
|
||||||
|
}
|
||||||
|
this.genericAPI(this.Models.FOOD, this.Actions.SHOPPING, params).then(() => {
|
||||||
|
this.shopping = false
|
||||||
|
this.$refs["shopping" + this.item.id].$emit("close")
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE)
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ShowConfirmation() {
|
|
||||||
if (this.shopping) {
|
|
||||||
return 'shopping' + this.item.id
|
|
||||||
} else {
|
|
||||||
return 'NoDialog'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addShopping() {
|
|
||||||
if (this.shopping) {return} // if item already in shopping list, excution handled after confirmation
|
|
||||||
let params = {
|
|
||||||
'id': this.item.id,
|
|
||||||
'amount': 1
|
|
||||||
}
|
|
||||||
this.genericAPI(this.Models.FOOD, this.Actions.SHOPPING, params).then((result) => {
|
|
||||||
this.shopping = true
|
|
||||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
cancelDelete() {
|
|
||||||
this.$refs['shopping' + this.item.id].$emit('close')
|
|
||||||
},
|
|
||||||
confirmDelete() {
|
|
||||||
let params = {
|
|
||||||
'id': this.item.id,
|
|
||||||
'_delete': 'true'
|
|
||||||
}
|
|
||||||
this.genericAPI(this.Models.FOOD, this.Actions.SHOPPING, params).then(() => {
|
|
||||||
this.shopping = false
|
|
||||||
this.$refs['shopping' + this.item.id].$emit('close')
|
|
||||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
</script>
|
||||||
</script>
|
|
||||||
|
@ -1,42 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<b-dropdown variant="link" toggle-class="text-decoration-none" right no-caret style="boundary:window">
|
<b-dropdown variant="link" toggle-class="text-decoration-none" right no-caret style="boundary:window">
|
||||||
<template #button-content>
|
<template #button-content>
|
||||||
<i class="fas fa-ellipsis-v" ></i>
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
</template>
|
</template>
|
||||||
<b-dropdown-item v-on:click="$emit('item-action', 'edit')" v-if="show_edit">
|
<b-dropdown-item v-on:click="$emit('item-action', 'edit')" v-if="show_edit"> <i class="fas fa-pencil-alt fa-fw"></i> {{ $t("Edit") }} </b-dropdown-item>
|
||||||
<i class="fas fa-pencil-alt fa-fw"></i> {{ $t('Edit') }}
|
|
||||||
</b-dropdown-item>
|
|
||||||
|
|
||||||
<b-dropdown-item v-on:click="$emit('item-action', 'delete')" v-if="show_delete">
|
|
||||||
<i class="fas fa-trash-alt fa-fw"></i> {{ $t('Delete') }}
|
|
||||||
</b-dropdown-item>
|
|
||||||
|
|
||||||
<b-dropdown-item v-on:click="$emit('item-action', 'move')" v-if="show_move">
|
<b-dropdown-item v-on:click="$emit('item-action', 'delete')" v-if="show_delete"> <i class="fas fa-trash-alt fa-fw"></i> {{ $t("Delete") }} </b-dropdown-item>
|
||||||
<i class="fas fa-expand-arrows-alt fa-fw"></i> {{ $t('Move') }}
|
<b-dropdown-item v-on:click="$emit('item-action', 'add-shopping')" v-if="show_shopping">
|
||||||
</b-dropdown-item>
|
<i class="fas fa-cart-plus fa-fw"></i> {{ $t("Add_to_Shopping") }}
|
||||||
|
</b-dropdown-item>
|
||||||
<b-dropdown-item v-if="show_merge" v-on:click="$emit('item-action', 'merge')">
|
<b-dropdown-item v-on:click="$emit('item-action', 'add-onhand')" v-if="show_onhand"> <i class="fas fa-clipboard-check fa-fw"></i> {{ $t("OnHand") }} </b-dropdown-item>
|
||||||
<i class="fas fa-compress-arrows-alt fa-fw"></i> {{ $t('Merge') }}
|
|
||||||
</b-dropdown-item>
|
|
||||||
|
|
||||||
<b-dropdown-item v-if="show_merge" v-on:click="$emit('item-action', 'merge-automate')">
|
<b-dropdown-item v-on:click="$emit('item-action', 'move')" v-if="show_move"> <i class="fas fa-expand-arrows-alt fa-fw"></i> {{ $t("Move") }} </b-dropdown-item>
|
||||||
<i class="fas fa-robot fa-fw"></i> {{$t('Merge')}} & {{$t('Automate')}} <b-badge v-b-tooltip.hover :title="$t('warning_feature_beta')">BETA</b-badge>
|
|
||||||
</b-dropdown-item>
|
|
||||||
|
|
||||||
</b-dropdown>
|
<b-dropdown-item v-if="show_merge" v-on:click="$emit('item-action', 'merge')"> <i class="fas fa-compress-arrows-alt fa-fw"></i> {{ $t("Merge") }} </b-dropdown-item>
|
||||||
</span>
|
|
||||||
|
<b-dropdown-item v-if="show_merge" v-on:click="$emit('item-action', 'merge-automate')">
|
||||||
|
<i class="fas fa-robot fa-fw"></i> {{ $t("Merge") }} & {{ $t("Automate") }} <b-badge v-b-tooltip.hover :title="$t('warning_feature_beta')">BETA</b-badge>
|
||||||
|
</b-dropdown-item>
|
||||||
|
</b-dropdown>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GenericContextMenu',
|
name: "GenericContextMenu",
|
||||||
props: {
|
props: {
|
||||||
show_edit: {type: Boolean, default: true},
|
show_edit: { type: Boolean, default: true },
|
||||||
show_delete: {type: Boolean, default: true},
|
show_delete: { type: Boolean, default: true },
|
||||||
show_move: {type: Boolean, default: false},
|
show_move: { type: Boolean, default: false },
|
||||||
show_merge: {type: Boolean, default: false},
|
show_merge: { type: Boolean, default: false },
|
||||||
}
|
show_shopping: { type: Boolean, default: false },
|
||||||
|
show_onhand: { type: Boolean, default: false },
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<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>
|
<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">{{ item[subtitle] }}</div>
|
||||||
<!-- <span>{{this_item[itemTags.field]}}</span> -->
|
|
||||||
<generic-pill v-for="x in itemTags" :key="x.field" :item_list="item[x.field]" :label="x.label" :color="x.color" />
|
<generic-pill v-for="x in itemTags" :key="x.field" :item_list="item[x.field]" :label="x.label" :color="x.color" />
|
||||||
<generic-ordered-pill
|
<generic-ordered-pill
|
||||||
v-for="x in itemOrderedTags"
|
v-for="x in itemOrderedTags"
|
||||||
@ -66,6 +66,8 @@
|
|||||||
class="p-0"
|
class="p-0"
|
||||||
:show_merge="useMerge"
|
:show_merge="useMerge"
|
||||||
:show_move="useMove"
|
:show_move="useMove"
|
||||||
|
:show_shopping="useShopping"
|
||||||
|
:show_onhand="useOnhand"
|
||||||
@item-action="$emit('item-action', { action: $event, source: item })"
|
@item-action="$emit('item-action', { action: $event, source: item })"
|
||||||
>
|
>
|
||||||
</generic-context-menu>
|
</generic-context-menu>
|
||||||
@ -126,8 +128,6 @@
|
|||||||
<b-list-group-item action v-on:click="closeMenu()">
|
<b-list-group-item action v-on:click="closeMenu()">
|
||||||
<i class="fas fa-times fa-fw"></i> <b>{{ $t("Cancel") }}</b>
|
<i class="fas fa-times fa-fw"></i> <b>{{ $t("Cancel") }}</b>
|
||||||
</b-list-group-item>
|
</b-list-group-item>
|
||||||
<!-- TODO add to shopping list -->
|
|
||||||
<!-- TODO toggle onhand -->
|
|
||||||
</b-list-group>
|
</b-list-group>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -185,6 +185,12 @@ export default {
|
|||||||
useMerge: function() {
|
useMerge: function() {
|
||||||
return this.model?.["merge"] ?? false ? true : false
|
return this.model?.["merge"] ?? false ? true : false
|
||||||
},
|
},
|
||||||
|
useShopping: function() {
|
||||||
|
return this.model?.["shop"] ?? false ? true : false
|
||||||
|
},
|
||||||
|
useOnhand: function() {
|
||||||
|
return this.model?.["onhand"] ?? false ? true : false
|
||||||
|
},
|
||||||
useDrag: function() {
|
useDrag: function() {
|
||||||
return this.useMove || this.useMerge
|
return this.useMove || this.useMerge
|
||||||
},
|
},
|
||||||
|
@ -1,72 +1,75 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<draggable v-if="itemList" v-model="this_list" tag="span" group="ordered_items" z-index="500"
|
<draggable v-if="itemList" v-model="this_list" tag="span" group="ordered_items" z-index="500" @change="orderChanged">
|
||||||
@change="orderChanged">
|
<span :key="k.id" v-for="k in itemList" class="pl-1">
|
||||||
<span :key="k.id" v-for="k in itemList" class="pl-1">
|
<b-badge squared :variant="color"
|
||||||
<b-badge squared :variant="color"><i class="fas fa-grip-lines-vertical text-muted"></i><span class="ml-1">{{thisLabel(k)}}</span></b-badge>
|
><i class="fas fa-grip-lines-vertical text-muted"></i><span class="ml-1">{{ thisLabel(k) }}</span></b-badge
|
||||||
</span>
|
>
|
||||||
|
</span>
|
||||||
</draggable>
|
</draggable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// you can't use this component with a horizontal card that is also draggable
|
// you can't use this component with a horizontal card that is also draggable
|
||||||
import draggable from 'vuedraggable'
|
import draggable from "vuedraggable"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GenericOrderedPill',
|
name: "GenericOrderedPill",
|
||||||
components: {draggable},
|
components: { draggable },
|
||||||
props: {
|
props: {
|
||||||
item_list: {required: true, type: Array},
|
item_list: {
|
||||||
label: {type: String, default: 'name'},
|
type: Array,
|
||||||
color: {type: String, default: 'light'},
|
default() {
|
||||||
field: {type: String, required: true},
|
return []
|
||||||
item: {type: Object},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
label: { type: String, default: "name" },
|
||||||
return {
|
color: { type: String, default: "light" },
|
||||||
this_list: [],
|
field: { type: String, required: true },
|
||||||
}
|
item: { type: Object },
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
itemList: function() {
|
|
||||||
if(Array.isArray(this.this_list)) {
|
|
||||||
return this.this_list
|
|
||||||
} else if (!this.this_list?.name) {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return [this.this_list]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
},
|
return {
|
||||||
mounted() {
|
this_list: [],
|
||||||
this.this_list = this.item_list
|
}
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'item_list': function (newVal) {
|
|
||||||
this.this_list = newVal
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
thisLabel: function (item) {
|
|
||||||
let fields = this.label.split('::')
|
|
||||||
let value = item
|
|
||||||
fields.forEach(x => {
|
|
||||||
value = value[x]
|
|
||||||
});
|
|
||||||
return value
|
|
||||||
},
|
},
|
||||||
orderChanged: function(e){
|
computed: {
|
||||||
let order = 0
|
itemList: function() {
|
||||||
this.this_list.forEach(x => {
|
if (Array.isArray(this.this_list)) {
|
||||||
x['order'] = order
|
return this.this_list
|
||||||
order++
|
} else if (!this.this_list?.name) {
|
||||||
})
|
return false
|
||||||
let new_order = {...this.item}
|
} else {
|
||||||
new_order[this.field] = this.this_list
|
return [this.this_list]
|
||||||
this.$emit('finish-action', {'action':'save','form_data': new_order })
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.this_list = this.item_list
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
item_list: function(newVal) {
|
||||||
|
this.this_list = newVal
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
thisLabel: function(item) {
|
||||||
|
let fields = this.label.split("::")
|
||||||
|
let value = item
|
||||||
|
fields.forEach((x) => {
|
||||||
|
value = value[x]
|
||||||
|
})
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
orderChanged: function(e) {
|
||||||
|
let order = 0
|
||||||
|
this.this_list.forEach((x) => {
|
||||||
|
x["order"] = order
|
||||||
|
order++
|
||||||
|
})
|
||||||
|
let new_order = { ...this.item }
|
||||||
|
new_order[this.field] = this.this_list
|
||||||
|
this.$emit("finish-action", { action: "save", form_data: new_order })
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -83,7 +83,6 @@ export default {
|
|||||||
show: function () {
|
show: function () {
|
||||||
if (this.show) {
|
if (this.show) {
|
||||||
this.form = getForm(this.model, this.action, this.item1, this.item2)
|
this.form = getForm(this.model, this.action, this.item1, this.item2)
|
||||||
// TODO: I don't know how to generalize this, but Food needs default values to drive inheritance
|
|
||||||
if (this.form?.form_function) {
|
if (this.form?.form_function) {
|
||||||
this.form = formFunctions[this.form.form_function](this.form)
|
this.form = formFunctions[this.form.form_function](this.form)
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,9 @@ export default {
|
|||||||
footer_text: String,
|
footer_text: String,
|
||||||
footer_icon: String,
|
footer_icon: String,
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log(this.recipe)
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
detailed: function() {
|
detailed: function() {
|
||||||
return this.recipe?.steps !== undefined
|
return this.recipe?.steps !== undefined
|
||||||
|
@ -65,6 +65,8 @@ export class Models {
|
|||||||
paginated: true,
|
paginated: true,
|
||||||
move: true,
|
move: true,
|
||||||
merge: true,
|
merge: true,
|
||||||
|
shop: true,
|
||||||
|
onhand: true,
|
||||||
badges: {
|
badges: {
|
||||||
linked_recipe: true,
|
linked_recipe: true,
|
||||||
on_hand: true,
|
on_hand: true,
|
||||||
|
@ -2160,7 +2160,7 @@ export interface ShoppingListEntries {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof ShoppingListEntries
|
* @memberof ShoppingListEntries
|
||||||
*/
|
*/
|
||||||
completed_at: string | null;
|
completed_at?: string | null;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@ -2251,7 +2251,7 @@ export interface ShoppingListEntry {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof ShoppingListEntry
|
* @memberof ShoppingListEntry
|
||||||
*/
|
*/
|
||||||
completed_at: string | null;
|
completed_at?: string | null;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@ -3014,10 +3014,10 @@ export interface UserPreference {
|
|||||||
food_ignore_default?: string;
|
food_ignore_default?: string;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {number}
|
* @type {string}
|
||||||
* @memberof UserPreference
|
* @memberof UserPreference
|
||||||
*/
|
*/
|
||||||
default_delay?: number;
|
default_delay?: string;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@ -3036,6 +3036,12 @@ export interface UserPreference {
|
|||||||
* @memberof UserPreference
|
* @memberof UserPreference
|
||||||
*/
|
*/
|
||||||
shopping_share?: Array<number>;
|
shopping_share?: Array<number>;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof UserPreference
|
||||||
|
*/
|
||||||
|
shopping_recent_days?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,7 +89,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO make this conditional on .env DEBUG = FALSE
|
|
||||||
config.optimization.minimize(false)
|
config.optimization.minimize(false)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user