+
-
-
-
-
+
+
+
+
-
+
+
{{ book.name }}
-
+
{{ book.description }}
+
+
+ ↑
+ ↓
+
+
+
+ swap
+
+
+
@@ -94,6 +112,13 @@ export default {
current_book: undefined,
loading: false,
search: "",
+ dropdown_text: "Sort by: oldest to newest",
+ showOtN: false,
+ showNtO: true,
+ showAlp: true,
+ showMan: true,
+ md: 12,
+ inputValue: ""
}
},
computed: {
@@ -168,7 +193,76 @@ export default {
}
})
},
- },
+ sortAlphabetical: function(){
+ this.dropdown_text = "Sort by: alphabetical order"
+ this.cookbooks = this.cookbooks.sort((a, b) => a.name.localeCompare(b.name))
+ this.showAlp= false
+ this.showNtO = true
+ this.showOtN = true
+ this.showMan = true
+ this.md = 12
+
+ },
+ sortNewest: function(){
+ this.dropdown_text = "Sort by: newest to oldest"
+ this.cookbooks = this.cookbooks.sort((a, b) => b.id - a.id);
+ this.showNtO = false
+ this.showAlp= true
+ this.showOtN = true
+ this.showMan = true
+ this.md = 12
+ },
+ sortOldest: function(){
+ this.dropdown_text = "Sort by: oldest to newest"
+ this.cookbooks = this.cookbooks.sort((a, b) => a.id - b.id)
+ this.showOtN= false
+ this.showAlp= true
+ this.showNtO = true
+ this.showMan = true
+ this.md = 12
+ },
+ enableSortManually: function(){
+ this.showOtN= true
+ this.showAlp= true
+ this.showNtO = true
+ this.showMan = false
+ this.md = 8
+ this.dropdown_text = "Sort by: manually"
+
+ },
+ swapUpBooks: function(index){
+ const tempArray = this.cookbooks
+ const temp = tempArray[index - 1]
+ tempArray[index-1] = tempArray[index]
+ tempArray[index] = temp
+ this.cookbooks = []
+ this.cookbooks = tempArray
+ },
+ swapDownBooks: function(index){
+ const tempArray = this.cookbooks
+ const temp = tempArray[index + 1]
+ tempArray[index+1] = tempArray[index]
+ tempArray[index] = temp
+ this.cookbooks = []
+ this.cookbooks = tempArray
+ },
+ swapWithPos: function(index){
+ const position = parseInt(this.inputValue)
+ this.inputValue = ""
+ if (!(/^\d+$/.test(position)) || position >= this.cookbooks.length || position < 0){
+ console.log("catch")
+ this.inputValue = ""
+ } else {
+ const tempArray = this.cookbooks
+ const temp = tempArray[position]
+ tempArray[position] = tempArray[index]
+ tempArray[index] = temp
+ this.cookbooks = []
+ this.cookbooks = tempArray
+ }
+
+ }
+ },
directives: {
hover: {
inserted: function (el) {
From e423fc1df450385cbbfedf877337fecb46a565cf Mon Sep 17 00:00:00 2001
From: Mahmoud <“aljouhar@rptu.de”>
Date: Fri, 15 Dec 2023 02:23:08 +0100
Subject: [PATCH 002/157] last changes
---
vue/src/apps/CookbookView/CookbookView.vue | 59 ++++++++++++++++++----
1 file changed, 48 insertions(+), 11 deletions(-)
diff --git a/vue/src/apps/CookbookView/CookbookView.vue b/vue/src/apps/CookbookView/CookbookView.vue
index 737300564f..bb63db823d 100644
--- a/vue/src/apps/CookbookView/CookbookView.vue
+++ b/vue/src/apps/CookbookView/CookbookView.vue
@@ -18,6 +18,9 @@
manually
+
+ {{submitText}}
+
@@ -33,10 +36,10 @@
-
+
{{ book.name }}
-
+
{{ book.description }}
@@ -44,14 +47,13 @@
- ↑
- ↓
+ ↑
+ ↓
-
- swap
+
+ swap
-
@@ -118,7 +120,9 @@ export default {
showAlp: true,
showMan: true,
md: 12,
- inputValue: ""
+ inputValue: "",
+ submitManual: false,
+ submitText: "Edit"
}
},
computed: {
@@ -200,6 +204,7 @@ export default {
this.showNtO = true
this.showOtN = true
this.showMan = true
+ this.submitManual = false
this.md = 12
},
@@ -210,6 +215,7 @@ export default {
this.showAlp= true
this.showOtN = true
this.showMan = true
+ this.submitManual = false
this.md = 12
},
sortOldest: function(){
@@ -219,16 +225,22 @@ export default {
this.showAlp= true
this.showNtO = true
this.showMan = true
+ this.submitManual = false
this.md = 12
},
enableSortManually: function(){
+ console.log(1)
+ this.synchroniseLocalToDatabase();
+ console.log(2)
+ if (localStorage.getItem('cookbooks') ){
+ this.cookbooks = JSON.parse(localStorage.getItem('cookbooks'))
+ }
this.showOtN= true
this.showAlp= true
this.showNtO = true
this.showMan = false
- this.md = 8
this.dropdown_text = "Sort by: manually"
-
+
},
swapUpBooks: function(index){
const tempArray = this.cookbooks
@@ -250,7 +262,6 @@ export default {
const position = parseInt(this.inputValue)
this.inputValue = ""
if (!(/^\d+$/.test(position)) || position >= this.cookbooks.length || position < 0){
- console.log("catch")
this.inputValue = ""
} else {
const tempArray = this.cookbooks
@@ -261,6 +272,32 @@ export default {
this.cookbooks = tempArray
}
+ }, submitManualChanging: function(){
+ if (!this.submitManual){
+ this.submitText = "Submit"
+ this.submitManual = true
+ this.md = 8
+ } else {
+ localStorage.setItem('cookbooks',JSON.stringify(this.cookbooks))
+ this.submitText = "Edit"
+ this.submitManual = false
+ this.md = 12
+ }
+ }, synchroniseLocalToDatabase: function(){
+ const localStorageData = localStorage.getItem('cookbooks');
+ const localStorageArray = JSON.parse(localStorageData) || [];
+ const updatedLocalStorageArray = localStorageArray.filter(localStorageElement => {
+ // Assuming there's a unique identifier in your objects, replace 'id' with the actual property
+ const isElementInTargetArray = this.cookbooks.some(targetElement => targetElement.id === localStorageElement.id);
+ return isElementInTargetArray;
+ });
+ this.cookbooks.forEach(targetElement => {
+ const isNewElement = !updatedLocalStorageArray.some(localStorageElement => localStorageElement.id === targetElement.id);
+ if (isNewElement) {
+ updatedLocalStorageArray.push(targetElement);
+ }
+ });
+ localStorage.setItem('cookbooks', JSON.stringify(updatedLocalStorageArray));
}
},
directives: {
From 45c14f6a12b04c2afa8819adef4571be7fe5c3cf Mon Sep 17 00:00:00 2001
From: Mahmoud <“aljouhar@rptu.de”>
Date: Sun, 17 Dec 2023 16:35:46 +0100
Subject: [PATCH 003/157] automatic ordering through api
---
cookbook/views/api.py | 12 ++++-
vue/src/apps/CookbookView/CookbookView.vue | 63 ++++++++--------------
vue/src/utils/openapi/api.ts | 7 +++
3 files changed, 39 insertions(+), 43 deletions(-)
diff --git a/cookbook/views/api.py b/cookbook/views/api.py
index d97d5f739c..440a899ca4 100644
--- a/cookbook/views/api.py
+++ b/cookbook/views/api.py
@@ -651,15 +651,23 @@ def destroy(self, *args, **kwargs):
content = {'error': True, 'msg': e.args[0]}
return Response(content, status=status.HTTP_403_FORBIDDEN)
-
+import json
class RecipeBookViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = RecipeBook.objects
serializer_class = RecipeBookSerializer
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
def get_queryset(self):
+ order_field = self.request.GET.get('order_field')
+ order_direction = self.request.GET.get('order_direction')
+
+ if not order_field:
+ order_field = 'id'
+
+ ordering = f"{'' if order_direction == 'asc' else '-'}{order_field}"
+
self.queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(
- space=self.request.space).distinct()
+ space=self.request.space).distinct().order_by(ordering)
return super().get_queryset()
diff --git a/vue/src/apps/CookbookView/CookbookView.vue b/vue/src/apps/CookbookView/CookbookView.vue
index bb63db823d..1b2560dc06 100644
--- a/vue/src/apps/CookbookView/CookbookView.vue
+++ b/vue/src/apps/CookbookView/CookbookView.vue
@@ -11,11 +11,11 @@
-
- oldest to newest
- newest to oldest
- alphabetical order
- manually
+
+ oldest to newest
+ newest to oldest
+ alphabetical order
+ manually
@@ -114,10 +114,8 @@ export default {
current_book: undefined,
loading: false,
search: "",
- dropdown_text: "Sort by: oldest to newest",
- showOtN: false,
- showNtO: true,
- showAlp: true,
+ activeSortField : 'id',
+ activeSortDirection: 'asc',
showMan: true,
md: 12,
inputValue: "",
@@ -139,7 +137,7 @@ export default {
methods: {
refreshData: function () {
let apiClient = new ApiApiFactory()
-
+
apiClient.listRecipeBooks().then((result) => {
this.cookbooks = result.data
})
@@ -197,41 +195,24 @@ export default {
}
})
},
- sortAlphabetical: function(){
- this.dropdown_text = "Sort by: alphabetical order"
- this.cookbooks = this.cookbooks.sort((a, b) => a.name.localeCompare(b.name))
- this.showAlp= false
- this.showNtO = true
- this.showOtN = true
- this.showMan = true
- this.submitManual = false
- this.md = 12
-
- },
- sortNewest: function(){
- this.dropdown_text = "Sort by: newest to oldest"
- this.cookbooks = this.cookbooks.sort((a, b) => b.id - a.id);
- this.showNtO = false
- this.showAlp= true
- this.showOtN = true
- this.showMan = true
- this.submitManual = false
- this.md = 12
+ orderBy: function(order_field,order_direction){
+ let apiClient = new ApiApiFactory()
+ const options = {
+ order_field: order_field,
+ order_direction: order_direction
+ }
+ this.activeSortField = order_field
+ this.activeSortDirection = order_direction
+ apiClient.listRecipeBooks(options).then((result) => {
+ this.cookbooks = result.data
+ })
},
- sortOldest: function(){
- this.dropdown_text = "Sort by: oldest to newest"
- this.cookbooks = this.cookbooks.sort((a, b) => a.id - b.id)
- this.showOtN= false
- this.showAlp= true
- this.showNtO = true
- this.showMan = true
- this.submitManual = false
- this.md = 12
+ isActiveSort: function(field, direction) {
+ // Check if the current item is the active sorting option
+ return this.activeSortField === field && this.activeSortDirection === direction;
},
enableSortManually: function(){
- console.log(1)
this.synchroniseLocalToDatabase();
- console.log(2)
if (localStorage.getItem('cookbooks') ){
this.cookbooks = JSON.parse(localStorage.getItem('cookbooks'))
}
diff --git a/vue/src/utils/openapi/api.ts b/vue/src/utils/openapi/api.ts
index 064297c498..b08ea25105 100644
--- a/vue/src/utils/openapi/api.ts
+++ b/vue/src/utils/openapi/api.ts
@@ -8985,6 +8985,13 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
const localVarQueryParameter = {} as any;
+ if (options.order_field !== undefined) {
+ localVarQueryParameter['order_field'] = options.order_field;
+ }
+
+ if (options.order_direction!== undefined) {
+ localVarQueryParameter['order_direction'] = options.order_direction;
+ }
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
From f65597c3915959200d354b45c3a37bee28f272c0 Mon Sep 17 00:00:00 2001
From: vabene1111
Date: Fri, 22 Dec 2023 11:40:17 +0100
Subject: [PATCH 004/157] basic nocer ui, nothing really working
---
cookbook/serializer.py | 4 +-
.../ShoppingListView/ShoppingListView.vue | 19 +-
vue/src/apps/TestView/TestView.vue | 18 ++
vue/src/components/ShoppingLineItem.vue | 205 +++++++++---------
4 files changed, 138 insertions(+), 108 deletions(-)
diff --git a/cookbook/serializer.py b/cookbook/serializer.py
index ff4e2dfc8c..fd42bd961d 100644
--- a/cookbook/serializer.py
+++ b/cookbook/serializer.py
@@ -1031,6 +1031,8 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField('get_name') # should this be done at the front end?
recipe_name = serializers.ReadOnlyField(source='recipe.name')
mealplan_note = serializers.ReadOnlyField(source='mealplan.note')
+ mealplan_from_date = serializers.ReadOnlyField(source='mealplan.from_date')
+ mealplan_type = serializers.ReadOnlyField(source='mealplan.meal_type.name')
servings = CustomDecimalField()
def get_name(self, obj):
@@ -1054,7 +1056,7 @@ def update(self, instance, validated_data):
class Meta:
model = ShoppingListRecipe
- fields = ('id', 'recipe_name', 'name', 'recipe', 'mealplan', 'servings', 'mealplan_note')
+ fields = ('id', 'recipe_name', 'name', 'recipe', 'mealplan', 'servings', 'mealplan_note','mealplan_from_date', 'mealplan_type')
read_only_fields = ('id',)
diff --git a/vue/src/apps/ShoppingListView/ShoppingListView.vue b/vue/src/apps/ShoppingListView/ShoppingListView.vue
index 6686722721..b0f8742c2f 100644
--- a/vue/src/apps/ShoppingListView/ShoppingListView.vue
+++ b/vue/src/apps/ShoppingListView/ShoppingListView.vue
@@ -155,14 +155,17 @@
v-for="(entries, index) in Object.entries(foods_group)"
:key="index">
-
+
+
+
+
diff --git a/vue/src/apps/TestView/TestView.vue b/vue/src/apps/TestView/TestView.vue
index 5aa6efff05..24520e7f7c 100644
--- a/vue/src/apps/TestView/TestView.vue
+++ b/vue/src/apps/TestView/TestView.vue
@@ -2,6 +2,24 @@
+
+
+
+
+ 100 g Möhren Test
Info
+
+
+
+
+ 150 ml Heißwassermöhrenbrühe
Info
+
+
+
+
+
+
+ ----
+
diff --git a/vue/src/components/ShoppingLineItem.vue b/vue/src/components/ShoppingLineItem.vue
index b1e52f7f63..e9796e22a1 100644
--- a/vue/src/components/ShoppingLineItem.vue
+++ b/vue/src/components/ShoppingLineItem.vue
@@ -1,83 +1,20 @@
@@ -180,7 +141,7 @@ import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css"
import ContextMenu from "@/components/ContextMenu/ContextMenu"
import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem"
-import {ApiMixin} from "@/utils/utils"
+import {ApiMixin, resolveDjangoUrl} from "@/utils/utils"
import RecipeCard from "./RecipeCard.vue"
import Vue2TouchEvents from "vue2-touch-events"
@@ -192,7 +153,7 @@ export default {
// or i'm capturing it incorrectly
name: "ShoppingLineItem",
mixins: [ApiMixin],
- components: {RecipeCard, ContextMenu, ContextMenuItem},
+ components: {},
props: {
entries: {
type: Array,
@@ -206,7 +167,8 @@ export default {
recipe: undefined,
servings: 1,
dragStartX: 0,
- distance_left: 0
+ distance_left: 0,
+ detail_modal_visible: false,
}
},
computed: {
@@ -266,12 +228,57 @@ export default {
}
return ""
},
+
+ food_row: function () {
+ let item = this.entries[0]
+ return this.formatOneAmount(item) + " " + this.formatOneUnit(item) + " " + this.formatOneFood(item)
+ },
+ info_row: function () {
+ // TODO add setting
+
+ // author
+ if (this.entries.length === 123) {
+ let authors = []
+ this.entries.forEach(e => {
+ if (authors.indexOf(e.created_by.display_name) === -1) {
+ authors.push(e.created_by.display_name)
+ }
+ })
+ return authors.join(', ')
+ }
+
+ if (this.entries.length === 1123) {
+ let recipes = []
+ this.entries.forEach(e => {
+ if (e.recipe_mealplan !== null) {
+ let recipe_name = e.recipe_mealplan.recipe_name
+ if (recipes.indexOf(recipe_name) === -1) {
+ recipes.push(recipe_name)
+ }
+ }
+ })
+ if (recipes.length > 1) {
+ let short_recipes = []
+ recipes.forEach(r => {
+ short_recipes.push(r.substring(0, 14) + (r.length > 14 ? '..' : ''))
+ })
+ }
+ return recipes.join(', ')
+ }
+
+ if (this.entries.length === 123) {
+ return "Abendessen 31.12" // TODO implement mealplan or manual
+ }
+
+ return this.entries.length
+ }
},
watch: {},
mounted() {
this.servings = this.entries?.[0]?.recipe_mealplan?.servings ?? 0
},
methods: {
+ resolveDjangoUrl,
// this.genericAPI inherited from ApiMixin
formatDate: function (datetime) {
From ddf9ef11a0ad7d35273cea0bc902784ec29e99c2 Mon Sep 17 00:00:00 2001
From: vabene1111
Date: Fri, 22 Dec 2023 13:12:28 +0100
Subject: [PATCH 005/157] basic working shopping list store
---
.../ShoppingListView/ShoppingListView.vue | 4 +
vue/src/stores/ShoppingListStore.js | 123 ++++++++++++++++++
2 files changed, 127 insertions(+)
create mode 100644 vue/src/stores/ShoppingListStore.js
diff --git a/vue/src/apps/ShoppingListView/ShoppingListView.vue b/vue/src/apps/ShoppingListView/ShoppingListView.vue
index b0f8742c2f..c48bc217c6 100644
--- a/vue/src/apps/ShoppingListView/ShoppingListView.vue
+++ b/vue/src/apps/ShoppingListView/ShoppingListView.vue
@@ -631,6 +631,7 @@ Vue.use(VueCookies)
let SETTINGS_COOKIE_NAME = "shopping_settings"
import {Workbox} from 'workbox-window';
import BottomNavigationBar from "@/components/BottomNavigationBar.vue";
+import {useShoppingListStore} from "@/stores/ShoppingListStore";
export default {
name: "ShoppingListView",
@@ -920,6 +921,9 @@ export default {
}
})
this.$i18n.locale = window.CUSTOM_LOCALE
+
+ let store = useShoppingListStore()
+ store.refreshFromAPI()
},
methods: {
/**
diff --git a/vue/src/stores/ShoppingListStore.js b/vue/src/stores/ShoppingListStore.js
new file mode 100644
index 0000000000..22e3b19b09
--- /dev/null
+++ b/vue/src/stores/ShoppingListStore.js
@@ -0,0 +1,123 @@
+import {ApiApiFactory} from "@/utils/openapi/api"
+import {StandardToasts} from "@/utils/utils"
+import {defineStore} from "pinia"
+import Vue from "vue"
+
+const _STORE_ID = "shopping_list_store"
+const _LOCAL_STORAGE_KEY = "SHOPPING_LIST_CLIENT_SETTINGS"
+/*
+ * test store to play around with pinia and see if it can work for my usecases
+ * dont trust that all shopping list entries are in store as there is no cache validation logic, its just a shared data holder
+ * */
+export const useShoppingListStore = defineStore(_STORE_ID, {
+ state: () => ({
+ category_food_entries: {},
+
+ currently_updating: false,
+ settings: null,
+ }),
+ getters: {
+
+ client_settings: function () {
+ if (this.settings === null) {
+ this.settings = this.loadClientSettings()
+ }
+ return this.settings
+ },
+ },
+ actions: {
+ refreshFromAPI() {
+ /**
+ * Retrieves all shopping list entries from the API and parses them into a structured object category > food > entry
+ */
+ this.category_food_entries = {}
+ Vue.set(this.category_food_entries, -1, {'id': -1, 'name': '', foods: {}}) //TODO use localization to get name for undefined category
+
+ if (!this.currently_updating) {
+ this.currently_updating = true
+ let apiClient = new ApiApiFactory()
+ apiClient.listShoppingListEntrys().then((r) => {
+ r.data.forEach((e) => {
+ let category = this.getFoodCategory(e.food)
+ if (!(category in this.category_food_entries)) {
+ Vue.set(this.category_food_entries, category, {'id': category, 'name': e.food.supermarket_category.name, foods: {}})
+ }
+ if (!(e.food.id in this.category_food_entries[category]['foods'])) {
+ Vue.set(this.category_food_entries[category]['foods'], e.food.id, {'id': e.food.id, 'name': e.food.name, 'entries': {}})
+ }
+ Vue.set(this.category_food_entries[category]['foods'][e.food.id]['entries'], e.id, e)
+ })
+ this.currently_updating = false
+ })
+ }
+ },
+ createObject(object) {
+ let apiClient = new ApiApiFactory()
+
+ // TODO shared handled in backend?
+
+ return apiClient.createShoppingListEntry(object).then((r) => {
+ Vue.set(this.category_food_entries[this.getFoodCategory(r.food)]['foods'][r.food.id][r.id], r.id, r)
+ }).catch((err) => {
+ StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
+ })
+ },
+ updateObject(object) {
+ let apiClient = new ApiApiFactory()
+ return apiClient.updateShoppingListEntry(object.id, object).then((r) => {
+ Vue.set(this.category_food_entries[this.getFoodCategory(r.food)]['foods'][r.food.id][r.id], r.id, r)
+ }).catch((err) => {
+ StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
+ })
+ },
+ deleteObject(object) {
+ let apiClient = new ApiApiFactory()
+ return apiClient.destroyShoppingListEntry(object.id).then((r) => {
+ Vue.delete(this.category_food_entries[this.getFoodCategory(object.food)]['foods'][object.food.id], object.id)
+ }).catch((err) => {
+ StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err)
+ })
+ },
+ updateClientSettings(settings) {
+ // this.settings = settings
+ // localStorage.setItem(_LOCAL_STORAGE_KEY, JSON.stringify(this.settings))
+ },
+ loadClientSettings() {
+ // let s = localStorage.getItem(_LOCAL_STORAGE_KEY)
+ // if (s === null || s === {}) {
+ // return {
+ // displayPeriodUom: "week",
+ // displayPeriodCount: 3,
+ // startingDayOfWeek: 1,
+ // displayWeekNumbers: true,
+ // }
+ // } else {
+ // return JSON.parse(s)
+ // }
+ },
+
+ // concenience methods
+ getFoodCategory(food) {
+ /**
+ * Get category id from food or return -1 if food has no category
+ */
+ if (food.supermarket_category !== null) {
+ return food.supermarket_category.id
+ }
+ return -1
+ },
+ delayFood(food) {
+ /**
+ * function to handle user "delaying" shopping entry
+ * takes a food object as an argument and delays all entries associated with the food
+ */
+ let delay = 4 //TODO get delay from settings
+ let delay_date = new Date(Date.now() + delay * (60 * 60 * 1000))
+
+ this.category_food_entries[this.getFoodCategory(food)]['foods'][food.id]['entries'].forEach(e => {
+ e.delayed_until = delay_date
+ this.updateObject(e)
+ })
+ },
+ },
+})
From a70ebd5130d3eef9504377cd96ced3fa02d146ca Mon Sep 17 00:00:00 2001
From: vabene1111
Date: Fri, 22 Dec 2023 14:35:17 +0100
Subject: [PATCH 006/157] somewhat working list
---
.../ShoppingListView/ShoppingListView.vue | 79 +---
vue/src/components/ShoppingLineItem.vue | 408 +++---------------
vue/src/stores/ShoppingListStore.js | 10 +
3 files changed, 93 insertions(+), 404 deletions(-)
diff --git a/vue/src/apps/ShoppingListView/ShoppingListView.vue b/vue/src/apps/ShoppingListView/ShoppingListView.vue
index c48bc217c6..712b5c1ed2 100644
--- a/vue/src/apps/ShoppingListView/ShoppingListView.vue
+++ b/vue/src/apps/ShoppingListView/ShoppingListView.vue
@@ -113,66 +113,22 @@
-
-
-
-
-
-
-
-
-
-
- {{ category_key }}
-
-
-
-
-
-
+
+
+
+
+ {{$t('Undefined')}}
+ {{ c.name}}
+
+
+
+
+
+
+
+
+