add to shopping from card context menu

This commit is contained in:
smilerz 2021-10-31 15:18:00 -05:00
parent 867e2d4fbf
commit 60d7e63da8
16 changed files with 279 additions and 250 deletions

View File

@ -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)

View File

@ -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">

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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
}, },

View File

@ -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>

View File

@ -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>

View File

@ -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"> <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-trash-alt fa-fw"></i> {{ $t('Delete') }} <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-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>
<b-dropdown-item v-on:click="$emit('item-action', 'move')" v-if="show_move"> <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-expand-arrows-alt fa-fw"></i> {{ $t('Move') }}
</b-dropdown-item>
<b-dropdown-item v-if="show_merge" v-on:click="$emit('item-action', 'merge')"> <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>
<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-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> <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-item>
</b-dropdown>
</b-dropdown> </span>
</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>

View File

@ -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
}, },

View File

@ -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>

View File

@ -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)
} }

View File

@ -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

View File

@ -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,

View File

@ -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;
} }
/** /**

View File

@ -89,7 +89,6 @@ module.exports = {
}, },
}, },
}, },
// TODO make this conditional on .env DEBUG = FALSE
config.optimization.minimize(false) config.optimization.minimize(false)
) )