From 81491924554bca6502d9227f0d6026893d5b180d Mon Sep 17 00:00:00 2001 From: Kaibu Date: Sat, 23 Apr 2022 14:47:10 +0200 Subject: [PATCH] shopping list and import view ux --- .../ImportResponseView/ImportResponseView.vue | 334 ++++++++++++++---- vue/src/apps/ImportView/ImportView.vue | 43 +-- .../ShoppingListView/ShoppingListView.vue | 106 ++++-- vue/src/components/ShoppingLineItem.vue | 18 +- vue/src/locales/en.json | 6 +- 5 files changed, 375 insertions(+), 132 deletions(-) diff --git a/vue/src/apps/ImportResponseView/ImportResponseView.vue b/vue/src/apps/ImportResponseView/ImportResponseView.vue index c01399852..9102b7f2f 100644 --- a/vue/src/apps/ImportResponseView/ImportResponseView.vue +++ b/vue/src/apps/ImportResponseView/ImportResponseView.vue @@ -1,44 +1,158 @@ @@ -49,7 +163,7 @@ import {BootstrapVue} from 'bootstrap-vue' import 'bootstrap-vue/dist/bootstrap-vue.css' -import {ResolveUrlMixin, ToastMixin} from "@/utils/utils"; +import {ResolveUrlMixin, ToastMixin, RandomIconMixin} from "@/utils/utils"; import LoadingSpinner from "@/components/LoadingSpinner"; @@ -58,47 +172,131 @@ import {ApiApiFactory} from "@/utils/openapi/api.ts"; Vue.use(BootstrapVue) export default { - name: 'ImportResponseView', - mixins: [ - ResolveUrlMixin, - ToastMixin, - ], - components: { - LoadingSpinner - }, - data() { - return { - import_id: window.IMPORT_ID, - import_info: undefined, - } - }, - mounted() { - this.refreshData() - this.$i18n.locale = window.CUSTOM_LOCALE - setInterval(() => { - if ((this.import_id !== null) && window.navigator.onLine && this.import_info.running) { + name: 'ImportResponseView', + mixins: [ + ResolveUrlMixin, + ToastMixin, + RandomIconMixin + ], + components: { + LoadingSpinner + }, + computed: { + imported_recipes: function () { + let msg = this.import_info.msg.split(/\r?\n/) + let out = {recipes: [], imported_total: 0, duplicates_total: 0, info: '', error: false} + if (this.import_info.msg.includes("--------------------")) { + out.error = true + } + msg.forEach((cur, i) => { + let line = cur.trim() + if (line === '') { + return + } + if (this.isNumber(line.charAt(0))) { + let recipe = line.split(/-(.*)/s) + out.recipes.push({ + id: recipe[0].trim(), + recipe_name: recipe[1].trim(), + icon: this.getRandomFoodIcon() + }) + } else { + if (i === msg.length - 4) { + out.info = line + } + if (i === msg.length - 2) { + out.imported_total = parseInt(line.match(/\d+/)[0]) + } + } + if (out.info !== '') { + let items = out.info.split(/:(.*)/s)[1] + items = items.split(",") + out.duplicates_total = items.length + out.recipes.forEach((recipe) => { + recipe.imported = true + items.forEach((item) => { + if (recipe.recipe_name === item.trim()) { + recipe.imported = false + } + }) + }) + } else { + if (out.imported_total > 0) { + out.recipes.forEach((recipe) => { + recipe.imported = true + }) + } + } + }) + return out + } + }, + data() { + return { + import_id: window.IMPORT_ID, + import_info: undefined, + } + }, + mounted() { this.refreshData() - let el = this.$refs.output_text - el.scrollTop = el.scrollHeight; - } - }, 5000) + this.$i18n.locale = window.CUSTOM_LOCALE + setInterval(() => { + if ((this.import_id !== null) && window.navigator.onLine && this.import_info.running) { + this.refreshData() + let el = this.$refs.output_text + el.scrollTop = el.scrollHeight; + } + }, 5000) - }, - methods: { - refreshData: function () { - let apiClient = new ApiApiFactory() + }, + methods: { + refreshData: function () { + let apiClient = new ApiApiFactory() - apiClient.retrieveImportLog(this.import_id).then(result => { - this.import_info = result.data + apiClient.retrieveImportLog(this.import_id).then(result => { + this.import_info = result.data + }) + }, + isNumber: function (char) { + if (typeof char !== 'string') { + return false; + } - }) - } - } + if (char.trim() === '') { + return false; + } + + return !isNaN(char); + } + }, directives: { + hover: { + inserted: function (el) { + el.addEventListener("mouseenter", () => { + el.classList.add("shadow") + }) + el.addEventListener("mouseleave", () => { + el.classList.remove("shadow") + }) + }, + }, + }, } diff --git a/vue/src/apps/ImportView/ImportView.vue b/vue/src/apps/ImportView/ImportView.vue index 2c143741f..28993fae2 100644 --- a/vue/src/apps/ImportView/ImportView.vue +++ b/vue/src/apps/ImportView/ImportView.vue @@ -1,12 +1,6 @@ -
+
@@ -52,6 +52,7 @@ :description="$t('Amount')" v-model="new_item.amount" style="font-size: 16px; border-radius: 5px !important; border: 1px solid #e8e8e8 !important" + ref="amount_input_complex" > @@ -67,10 +68,11 @@ + @keyup.enter="addItem" + ref="amount_input_simple"> - + @@ -861,7 +863,7 @@ export default { delay: 0, clear: Math.random(), ui: { - entry_mode_simple: false, + entry_mode_simple: true, selected_supermarket: undefined, }, settings: { @@ -1019,14 +1021,23 @@ export default { watch: { ui: { handler() { - this.$cookies.set(SETTINGS_COOKIE_NAME, this.ui) + this.$cookies.set(SETTINGS_COOKIE_NAME, {ui: this.ui, settings: {entrymode: this.entrymode}}) + if (this.entrymode) { + this.$nextTick(function () { + this.setFocus() + }) + } }, deep: true, }, entrymode: { handler() { + this.$cookies.set(SETTINGS_COOKIE_NAME, {ui: this.ui, settings: {entrymode: this.entrymode}}) if (this.entrymode) { document.getElementById('shoppinglist').scrollTop = 0 + this.$nextTick(function () { + this.setFocus() + }) } } }, @@ -1078,13 +1089,23 @@ export default { } this.$nextTick(function () { if (this.$cookies.isKey(SETTINGS_COOKIE_NAME)) { - this.ui = Object.assign({}, this.ui, this.$cookies.get(SETTINGS_COOKIE_NAME)) + this.ui = Object.assign({}, this.ui, this.$cookies.get(SETTINGS_COOKIE_NAME).ui) + this.entrymode = this.$cookies.get(SETTINGS_COOKIE_NAME).settings.entrymode } }) this.$i18n.locale = window.CUSTOM_LOCALE console.log(window.CUSTOM_LOCALE) }, methods: { + setFocus() { + if (this.ui.entry_mode_simple) { + this.$refs['amount_input_simple'].focus() + } else { + if (this.$refs['amount_input_complex']) { + this.$refs['amount_input_complex'].focus() + } + } + }, // this.genericAPI inherited from ApiMixin addItem: function () { if (this.ui.entry_mode_simple) { @@ -1106,6 +1127,7 @@ export default { } else { this.addEntry() } + this.setFocus() }, addEntry: function (x) { let api = new ApiApiFactory() @@ -1113,7 +1135,7 @@ export default { .then((results) => { if (results?.data) { this.items.push(results.data) - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_CREATE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE) } else { console.log("no data returned") } @@ -1121,7 +1143,7 @@ export default { this.clear += 1 }) .catch((err) => { - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_CREATE, err) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE, err) }) }, deleteSupermarket: function (s) { @@ -1129,10 +1151,10 @@ export default { api.destroySupermarket(s.id) .then(() => { this.getSupermarkets() - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_DELETE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_DELETE) }) .catch((err) => { - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_DELETE, err) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err) }) }, deleteCategory: function (c) { @@ -1144,10 +1166,10 @@ export default { this.getSupermarkets() this.getShoppingCategories() this.new_supermarket.value.category_to_supermarket = this.new_supermarket.value.category_to_supermarket.filter((x) => x.category.id != c_id) - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_DELETE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_DELETE) }) .catch((err) => { - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_DELETE, err) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err) }) }, resetFilters: function () { @@ -1176,7 +1198,7 @@ export default { promises.push(this.saveThis({id: entry, delay_until: delay_date}, false)) }) Promise.all(promises).then(() => { - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_UPDATE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) this.items = this.items.filter((x) => !entries.includes(x.id)) this.delay = this.defaultDelay }) @@ -1186,10 +1208,10 @@ export default { api.destroyShoppingListRecipe(recipe) .then((x) => { this.items = this.items.filter((x) => x.list_recipe !== recipe) - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_DELETE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_DELETE) }) .catch((err) => { - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_DELETE, err) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err) }) }, deleteThis: function (item) { @@ -1205,14 +1227,14 @@ export default { entries.forEach((x) => { promises.push( api.destroyShoppingListEntry(x).catch((err) => { - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_DELETE, 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) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_DELETE) }) }, editSupermarket(s) { @@ -1261,7 +1283,7 @@ export default { }) .catch((err) => { if (!autosync) { - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_FETCH, err) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err) } }) }, @@ -1347,10 +1369,10 @@ export default { let api = ApiApiFactory() api.partialUpdateUserPreference(this.settings.user, this.settings) .then((result) => { - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_UPDATE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) }) .catch((err) => { - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE, err) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) }) }, saveThis: function (thisItem, toast = true) { @@ -1361,22 +1383,22 @@ export default { .createShoppingListEntry(thisItem) .then((result) => { if (toast) { - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_CREATE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE) } }) .catch((err) => { - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_CREATE, 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) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) } }) .catch((err) => { - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE, err) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) }) } }, @@ -1406,7 +1428,7 @@ export default { }) .catch((err) => { this.auto_sync_blocked = false - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE, err) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) }) }, updateFood: function (food, field) { @@ -1419,13 +1441,13 @@ export default { return api .partialUpdateFood(food.id, food) .then((result) => { - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_UPDATE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) if (food?.numchild > 0) { this.getShoppingList() // if food has children, just get the whole list. probably could be more efficient } }) .catch((err) => { - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE, err) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) }) }, updateServings(e, plan) { @@ -1439,26 +1461,26 @@ export default { let api = new ApiApiFactory() api.createSupermarketCategory({name: this.new_category.value}) .then((result) => { - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_CREATE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE) this.shopping_categories.push(result.data) this.new_category.value = undefined }) .catch((err) => { console.log(err, Object.keys(err)) - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_CREATE) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE) }) }, addSupermarket: function () { let api = new ApiApiFactory() api.createSupermarket({name: this.new_supermarket.value}) .then((result) => { - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_CREATE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE) this.supermarkets.push(result.data) this.new_supermarket.value = undefined }) .catch((err) => { console.log(err, Object.keys(err)) - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_CREATE) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE) }) }, saveSupermarketCategoryOrder(e) { @@ -1484,11 +1506,11 @@ export default { apiClient .destroySupermarketCategoryRelation(e.removed.element.id) .then((result) => { - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_UPDATE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) }) .catch((err) => { console.log(err, Object.keys(err)) - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE) this.supermarkets = temp_supermarkets }) } @@ -1511,11 +1533,11 @@ export default { .then((updated_supermarket) => { let idx = this.supermarkets.indexOf((x) => x.id === updated_supermarket.id) Vue.set(this.supermarkets, idx, updated_supermarket) - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_UPDATE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) }) .catch((err) => { console.log(err, Object.keys(err)) - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE) this.supermarkets = temp_supermarkets }) } @@ -1525,11 +1547,11 @@ export default { .then((updated_supermarket) => { let idx = this.supermarkets.indexOf((x) => x.id === updated_supermarket.id) Vue.set(this.supermarkets, idx, updated_supermarket) - StandardToasts.makeStandardToast(this,StandardToasts.SUCCESS_UPDATE) + StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) }) .catch((err) => { console.log(err, Object.keys(err)) - StandardToasts.makeStandardToast(this,StandardToasts.FAIL_UPDATE) + StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE) this.supermarkets = temp_supermarkets }) } @@ -1646,7 +1668,13 @@ export default { padding-left: 5px; } -@media (min-width: 768px) { +@media (max-width: 991.9px) { + #shoppinglist { + max-width: none; + } +} + +@media (min-width: 992px) { #shoppinglist { overflow-y: auto; overflow-x: auto; diff --git a/vue/src/components/ShoppingLineItem.vue b/vue/src/components/ShoppingLineItem.vue index aa5e77593..cfdabd143 100644 --- a/vue/src/components/ShoppingLineItem.vue +++ b/vue/src/components/ShoppingLineItem.vue @@ -20,7 +20,7 @@ aria-expanded="false" type="button" :class="settings.left_handed ? 'dropdown-spacing' : ''" - class="btn dropdown-toggle btn-link text-decoration-none text-body pr-0 pl-1 dropdown-toggle-no-caret"> + class="btn dropdown-toggle btn-link text-decoration-none text-body pr-0 pl-1 pr-md-3 pl-md-3 dropdown-toggle-no-caret">
@@ -53,7 +53,7 @@ class="p-0 mr-0 mr-md-2 p-md-2 text-decoration-none" variant="link">
- {{ $t("Details") }} + {{ $t("Details") }}
@@ -112,7 +112,7 @@ aria-expanded="false" type="button" :class="settings.left_handed ? 'dropdown-spacing' : ''" - class="btn dropdown-toggle btn-link text-decoration-none text-body pr-1 dropdown-toggle-no-caret" + class="btn dropdown-toggle btn-link text-decoration-none text-body pr-1 pr-md-3 pl-md-3 dropdown-toggle-no-caret" > @@ -453,4 +453,16 @@ export default { .invis-border { border: 1px solid transparent; } + +@media (min-width: 992px) { + .fa-ellipsis-v { + font-size: 20px; + } +} + +@media (min-width: 576px) { + .fa-ellipsis-v { + font-size: 16px; + } +} diff --git a/vue/src/locales/en.json b/vue/src/locales/en.json index 6a3f1810e..7f19bc360 100644 --- a/vue/src/locales/en.json +++ b/vue/src/locales/en.json @@ -400,5 +400,9 @@ "Import_Supported": "Import supported", "Export_Supported": "Export supported", "Import_Not_Yet_Supported": "Import not yet supported", - "Export_Not_Yet_Supported": "Export not yet supported" + "Export_Not_Yet_Supported": "Export not yet supported", + "Import_Result_Info": "{imported} of {total} recipes were imported", + "Recipes_In_Import": "Recipes in your import file", + "Toggle": "Toggle", + "Import_Error": "An Error occurred during your import. Please expand the Details at the bottom of the page to view it." }