From 757fa5e49cf7c6fe39613247abbc531c54ab7259 Mon Sep 17 00:00:00 2001 From: smilerz Date: Sun, 31 Oct 2021 11:18:36 -0500 Subject: [PATCH] edit supermarket categories --- cookbook/serializer.py | 3 + .../ShoppingListView/ShoppingListView.vue | 307 ++++++++++- vue/src/components/GenericHorizontalCard.vue | 504 ++++++++++-------- vue/src/locales/en.json | 5 +- 4 files changed, 564 insertions(+), 255 deletions(-) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 9d6f49f9a..7d6ea556f 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -329,6 +329,9 @@ class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerial class SupermarketCategoryRelationSerializer(WritableNestedModelSerializer): category = SupermarketCategorySerializer() + def get_fields(self, *args, **kwargs): + return super().get_fields(*args, **kwargs) + class Meta: model = SupermarketCategoryRelation fields = ('id', 'category', 'supermarket', 'order') diff --git a/vue/src/apps/ShoppingListView/ShoppingListView.vue b/vue/src/apps/ShoppingListView/ShoppingListView.vue index 3e93d30bc..19a9b58ee 100644 --- a/vue/src/apps/ShoppingListView/ShoppingListView.vue +++ b/vue/src/apps/ShoppingListView/ShoppingListView.vue @@ -14,14 +14,16 @@ - - + +
-
+
+ +
+ +
+
+ + + +
+ + {{ $t("Save") }} +
+
+ + + + {{ s.name }} + + + + + + + + + + +
+
+
+ + + +
+ + {{ $t("Save") }} +
+
+ + Drag categories to change the order categories appear in shopping list. + + + + + {{ categoryName(c) }} + + + +
+ + + + + {{ categoryName(c) }} + + + + +
+
+
+ -
-
+
+
{{ $t("mealplan_autoadd_shopping") }}
@@ -214,14 +356,6 @@
-
- - put the supermarket stuff here
- -add supermarkets
- -add supermarket categories
- -sort supermarket categories
-
-
@@ -324,6 +458,8 @@ import ContextMenu from "@/components/ContextMenu/ContextMenu" import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem" import ShoppingLineItem from "@/components/ShoppingLineItem" import GenericMultiselect from "@/components/GenericMultiselect" +import GenericPill from "@/components/GenericPill" +import draggable from "vuedraggable" import { ApiMixin, getUserPreference } from "@/utils/utils" import { ApiApiFactory } from "@/utils/openapi/api" @@ -334,7 +470,7 @@ Vue.use(BootstrapVue) export default { name: "ShoppingListView", mixins: [ApiMixin], - components: { ContextMenu, ContextMenuItem, ShoppingLineItem, GenericMultiselect }, + components: { ContextMenu, ContextMenuItem, ShoppingLineItem, GenericMultiselect, GenericPill, draggable }, data() { return { @@ -357,10 +493,12 @@ export default { mealplan_autoexclude_onhand: true, filter_to_supermarket: false, }, - + new_supermarket: { entrymode: false, value: undefined, editmode: undefined }, + new_category: { entrymode: false, value: undefined }, autosync_id: undefined, auto_sync_running: false, show_delay: false, + drag: false, show_modal: false, fields: ["checked", "amount", "category", "unit", "food", "recipe", "details"], loading: true, @@ -448,6 +586,25 @@ export default { Recipes() { return [...new Map(this.items.filter((x) => x.list_recipe).map((item) => [item["list_recipe"], item])).values()] }, + supermarketCategory() { + return this.new_supermarket.editmode ? this.new_supermarket.value.category_to_supermarket : this.shopping_categories + }, + + notSupermarketCategory() { + let supercats = this.new_supermarket.value.category_to_supermarket + .map((x) => x.category) + .flat() + .map((x) => x.id) + + return this.shopping_categories + .filter((x) => !supercats.includes(x.id)) + .map((x) => { + return { + id: Math.random(), + category: x, + } + }) + }, }, watch: { selected_supermarket(newVal, oldVal) { @@ -507,9 +664,6 @@ export default { StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE) }) }, - categoryName: function(value) { - return this.shopping_categories.filter((x) => x.id == value)[0]?.name ?? "" - }, resetFilters: function() { this.selected_supermarket = undefined this.supermarket_categories_only = this.settings.filter_to_supermarket @@ -571,6 +725,16 @@ export default { StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE) }) }, + editSupermarket(s) { + if (!s.editmode) { + this.new_supermarket = { entrymode: false, value: undefined, editmode: undefined } + this.supermarkets.map((x) => (x.editmode = false)) + } else { + this.new_supermarket.value = s + this.new_supermarket.editmode = true + this.supermarkets.filter((x) => x.id !== s.id).map((x) => (x.editmode = false)) + } + }, foodName: function(value) { return value?.food?.name ?? value?.[0]?.food?.name ?? "" }, @@ -769,6 +933,108 @@ export default { this.getShoppingList() }) }, + addCategory: function() { + let api = new ApiApiFactory() + api.createSupermarketCategory({ name: this.new_category.value }) + .then((result) => { + StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE) + this.shopping_categories.push(result.data) + this.new_category.value = undefined + }) + .catch((err) => { + console.log(err, Object.keys(err)) + StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE) + }) + }, + addSupermarket: function() { + let api = new ApiApiFactory() + api.createSupermarket({ name: this.new_supermarket.value }) + .then((result) => { + StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE) + this.supermarkets.push(result.data) + this.new_supermarket.value = undefined + }) + .catch((err) => { + console.log(err, Object.keys(err)) + StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE) + }) + }, + saveSupermarketCategoryOrder(e) { + // TODO: all of this complexity should be moved to the backend + let apiClient = new ApiApiFactory() + let supermarket = this.new_supermarket.value + let temp_supermarkets = [...this.supermarkets] + const updateMoved = function(supermarket) { + var promises = [] + supermarket.category_to_supermarket.forEach((x, i) => { + x.order = i + promises.push(apiClient.partialUpdateSupermarketCategoryRelation(x.id, { order: i })) + }) + return Promise.all(promises).then(() => { + return supermarket + }) + } + + if ("removed" in e) { + // store current value in case it needs rolled back + let idx = this.supermarkets.indexOf((x) => x.id === supermarket.id) + Vue.set(this.supermarkets, idx, supermarket) + apiClient + .destroySupermarketCategoryRelation(e.removed.element.id) + .then((result) => { + StandardToasts.makeStandardToast(StandardToasts.SUCCESS_UPDATE) + }) + .catch((err) => { + console.log(err, Object.keys(err)) + StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE) + this.supermarkets = temp_supermarkets + }) + } + + if ("added" in e) { + let apiClient = new ApiApiFactory() + let category = e.added.element.category + + apiClient + .createSupermarketCategoryRelation({ + supermarket: supermarket.id, + category: category, + order: e.added.element.newIndex, + }) + .then((results) => { + this.new_supermarket.value.category_to_supermarket.filter((x) => x.category.id === category.id)[0].id = results.data.id + + return updateMoved(this.new_supermarket.value) + }) + .then((updated_supermarket) => { + let idx = this.supermarkets.indexOf((x) => x.id === updated_supermarket.id) + Vue.set(this.supermarkets, idx, updated_supermarket) + StandardToasts.makeStandardToast(StandardToasts.SUCCESS_UPDATE) + }) + .catch((err) => { + console.log(err, Object.keys(err)) + StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE) + this.supermarkets = temp_supermarkets + }) + } + + if ("moved" in e) { + updateMoved(this.new_supermarket.value) + .then((updated_supermarket) => { + let idx = this.supermarkets.indexOf((x) => x.id === updated_supermarket.id) + Vue.set(this.supermarkets, idx, updated_supermarket) + StandardToasts.makeStandardToast(StandardToasts.SUCCESS_UPDATE) + }) + .catch((err) => { + console.log(err, Object.keys(err)) + StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE) + this.supermarkets = temp_supermarkets + }) + } + }, + categoryName(item) { + return item?.category?.name ?? item.name + }, updateOnlineStatus(e) { const { type } = e this.online = type === "online" @@ -802,4 +1068,9 @@ export default { padding-top: -3em; margin-top: -3em; } + +.ghost { + opacity: 0.5; + background: #c8ebfb; +} diff --git a/vue/src/components/GenericHorizontalCard.vue b/vue/src/components/GenericHorizontalCard.vue index fcfe8094f..c5448b292 100644 --- a/vue/src/components/GenericHorizontalCard.vue +++ b/vue/src/components/GenericHorizontalCard.vue @@ -1,262 +1,294 @@ \ No newline at end of file + diff --git a/vue/src/locales/en.json b/vue/src/locales/en.json index 194fd85d4..36385620b 100644 --- a/vue/src/locales/en.json +++ b/vue/src/locales/en.json @@ -131,6 +131,7 @@ "Root": "Root", "Ignore_Shopping": "Ignore Shopping", "Shopping_Category": "Shopping Category", + "Shopping_Categories": "Shopping Categories", "Edit_Food": "Edit Food", "Move_Food": "Move Food", "New_Food": "New Food", @@ -258,5 +259,7 @@ "err_move_self": "Cannot move item to itself", "nothing": "Nothing to do", "err_merge_self": "Cannot merge item with itself", - "show_sql": "Show SQL" + "show_sql": "Show SQL", + "CategoryName": "Category Name", + "SupermarketName": "Supermarket Name" }