diff --git a/vue/src/apps/ShoppingListView/ShoppingListView.vue b/vue/src/apps/ShoppingListView/ShoppingListView.vue index 1e31282d..09b5e4e1 100644 --- a/vue/src/apps/ShoppingListView/ShoppingListView.vue +++ b/vue/src/apps/ShoppingListView/ShoppingListView.vue @@ -21,7 +21,7 @@ + /> @@ -72,84 +72,33 @@ - {{ $t('Recipes') + ` (${Recipes.length})` }} + {{ $t('Recipes') + ` (${Object.keys(shopping_list_store.getAssociatedRecipes()).length})` }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ $t("Meal_Plan") }} - {{ $t("Recipe") }} - {{ $t("Servings") }} - - - - - {{ r.recipe_mealplan.name }} - {{ - r.recipe_mealplan.recipe_name - }} - - - - - - - - - - - - - - + + + + + + + + + + + + {{ r.recipe_name }} + {{ r.recipe_name }} + + + + + + + + @@ -409,8 +358,7 @@ - + @@ -652,13 +600,6 @@ export default { }, watch: { - new_recipe: { - handler() { - this.add_recipe_servings = this.new_recipe.servings - }, - deep: true, - }, - "settings.shopping_auto_sync": function (newVal, oldVal) { clearInterval(this.autosync_id) this.autosync_id = undefined @@ -744,42 +685,18 @@ export default { }) } }, - deleteRecipe: function (e, recipe) { + /** + * delete shopping list recipe, associated entries are deleted automatically by database + * @param shopping_list_recipe_id id of shopping list recipe to delete + */ + deleteRecipe: function (shopping_list_recipe_id) { let api = new ApiApiFactory() - api.destroyShoppingListRecipe(recipe) - .then((x) => { - this.items = this.items.filter((x) => x.list_recipe !== recipe) - StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_DELETE) - }) - .catch((err) => { - StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err) - }) - }, - deleteThis: function (item) { - let api = new ApiApiFactory() - let entries = [] - let promises = [] - if (Array.isArray(item)) { - entries = item.map((x) => x.id) - } else { - entries = [item.id] - } - - entries.forEach((x) => { - promises.push( - api.destroyShoppingListEntry(x).catch((err) => { - StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err) - }) - ) + //TODO properly integrate into store logic + api.destroyShoppingListRecipe(shopping_list_recipe_id).then((x) => { + useShoppingListStore().refreshFromAPI() + }).catch((err) => { + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err) }) - - Promise.all(promises).then((result) => { - this.items = this.items.filter((x) => !entries.includes(x.id)) - StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_DELETE) - }) - }, - foodName: function (value) { - return value?.food?.name ?? value?.[0]?.food?.name ?? "" }, getShoppingCategories: function () { let api = new ApiApiFactory() @@ -836,9 +753,7 @@ export default { this.supermarkets = result.data }) }, - getThis: function (id) { - return this.genericAPI(this.Models.SHOPPING_CATEGORY, this.Actions.FETCH, {id: id}) - }, + mergeShoppingList: function (data) { this.items.map((x) => data.map((y) => { @@ -899,71 +814,11 @@ export default { }) }) }, - openContextMenu(e, value, section = false) { - if (section) { - value = Object.values(value).flat() - } else { - this.shopcat = value?.food?.supermarket_category?.id ?? value?.[0]?.food?.supermarket_category?.id ?? undefined - } - this.$refs.menu.open(e, value) - }, - saveThis: function (thisItem, toast = true) { - let api = new ApiApiFactory() - if (!thisItem?.id) { - // if there is no item id assume it's a new item - return api - .createShoppingListEntry(thisItem) - .then((result) => { - if (toast) { - StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE) - } - }) - .catch((err) => { - StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE, err) - }) - } else { - return api - .partialUpdateShoppingListEntry(thisItem.id, thisItem) - .then((result) => { - if (toast) { - StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) - } - }) - .catch((err) => { - StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) - }) - } - }, sectionID: function (a, b) { return (a + b).replace(/\W/g, "") }, - updateChecked: function (update) { - // when checking a sub item don't refresh the screen until all entries complete but change class to cross out - this.auto_sync_blocked = true - let promises = [] - update.entries.forEach((x) => { - const id = x?.id ?? x - let completed_at = undefined - if (update.checked) { - completed_at = new Date().toISOString() - } - promises.push(this.saveThis({id: id, checked: update.checked}, false)) - let item = this.items.filter((entry) => entry.id == id)[0] - Vue.set(item, "checked", update.checked) - Vue.set(item, "completed_at", completed_at) - }) - - Promise.all(promises) - .then(() => { - this.auto_sync_blocked = false - }) - .catch((err) => { - this.auto_sync_blocked = false - StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) - }) - }, updateFood: function (food, field) { let api = new ApiApiFactory() if (field) { @@ -983,12 +838,20 @@ export default { StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) }) }, - updateServings(e, plan) { - // maybe this needs debounced? - let api = new ApiApiFactory() - api.partialUpdateShoppingListRecipe(plan, {id: plan, servings: e}).then(() => { - this.getShoppingList() - }) + /** + * change number of servings of a shopping list recipe + * backend handles scaling of associated entries + * @param shopping_list_recipe_id shopping list recipe to update + * @param servings number of servings to set shopping list recipe to + */ + updateServings(shopping_list_recipe_id, servings) { + if (servings !== 0 && servings !== "") { + console.log('NEW SERVINGS', servings) + let api = new ApiApiFactory() + api.partialUpdateShoppingListRecipe(shopping_list_recipe_id, {id: shopping_list_recipe_id, servings: servings}).then(() => { + useShoppingListStore().refreshFromAPI() + }) + } }, deleteSupermarket(index) { this.$bvModal.msgBoxConfirm(this.$t('Are_You_Sure'), { @@ -1218,9 +1081,6 @@ export default { }) } }, - categoryName(item) { - return item?.category?.name ?? item.name - }, updateOnlineStatus(e) { const {type} = e this.online = type === "online" @@ -1229,8 +1089,15 @@ export default { window.removeEventListener("online", this.updateOnlineStatus) window.removeEventListener("offline", this.updateOnlineStatus) }, - addRecipeToShopping() { - this.$bvModal.show(`shopping_${this.new_recipe.id}`) + /** + * open standard shopping modal to add selected recipe to shopping list + * @param recipe recipe object to add to shopping + */ + addRecipeToShopping(recipe) { + this.new_recipe = recipe + this.$nextTick(() => { + this.$bvModal.show(`shopping_${this.new_recipe.id}`) + }) }, finishShopping() { this.add_recipe_servings = 1 diff --git a/vue/src/stores/ShoppingListStore.js b/vue/src/stores/ShoppingListStore.js index 13750f90..d5a3f47d 100644 --- a/vue/src/stores/ShoppingListStore.js +++ b/vue/src/stores/ShoppingListStore.js @@ -67,11 +67,13 @@ export const useShoppingListStore = defineStore(_STORE_ID, { return ordered_structure }, - + /** + * list of options available for grouping entry display + * @return {[{id: *, translatable_label: string},{id: *, translatable_label: string},{id: *, translatable_label: string}]} + */ grouping_options: function () { return [{'id': this.GROUP_CATEGORY, 'translatable_label': 'Category'}, {'id': this.GROUP_CREATED_BY, 'translatable_label': 'created_by'}, {'id': this.GROUP_RECIPE, 'translatable_label': 'Recipe'}] - } - + }, }, actions: { // TODO implement shopping list recipes @@ -148,6 +150,21 @@ export const useShoppingListStore = defineStore(_STORE_ID, { StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err) }) }, + /** + * returns a distinct list of recipes associated with unchecked shopping list entries + */ + getAssociatedRecipes: function () { + let recipes = {} + + for (let i in this.entries) { + let e = this.entries[i] + if (e.recipe_mealplan !== null) { + Vue.set(recipes, e.recipe_mealplan.recipe, {'shopping_list_recipe_id': e.list_recipe, 'recipe_id': e.recipe_mealplan.recipe, 'recipe_name': e.recipe_mealplan.recipe_name, 'servings': e.recipe_mealplan.servings}) + } + } + + return recipes + }, // convenience methods /** * function to set entry to its proper place in the data structure to perform grouping