new datastructure
This commit is contained in:
parent
b0c561661b
commit
d3376b33d8
@ -1,6 +1,7 @@
|
|||||||
<component name="ProjectDictionaryState">
|
<component name="ProjectDictionaryState">
|
||||||
<dictionary name="vaben">
|
<dictionary name="vaben">
|
||||||
<words>
|
<words>
|
||||||
|
<w>mealplan</w>
|
||||||
<w>pinia</w>
|
<w>pinia</w>
|
||||||
<w>selfhosted</w>
|
<w>selfhosted</w>
|
||||||
<w>unapplied</w>
|
<w>unapplied</w>
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
<!-- shopping list table -->
|
<!-- shopping list table -->
|
||||||
|
|
||||||
<b-row v-for="c in shopping_list_store.category_food_entries" v-bind:key="c.id" class="pr-4 pl-4">
|
<b-row v-for="c in shopping_list_store.get_entries_by_group" v-bind:key="c.id" class="pr-4 pl-4">
|
||||||
<b-col cols="12">
|
<b-col cols="12">
|
||||||
<b-button-group class="w-100 mt-1">
|
<b-button-group class="w-100 mt-1">
|
||||||
<b-button variant="light" block class="btn btn-block text-left">
|
<b-button variant="light" block class="btn btn-block text-left">
|
||||||
@ -750,7 +750,7 @@ export default {
|
|||||||
handler() {
|
handler() {
|
||||||
this.$cookies.set(SETTINGS_COOKIE_NAME, {ui: this.ui, settings: {entrymode: this.entrymode}}, "100y")
|
this.$cookies.set(SETTINGS_COOKIE_NAME, {ui: this.ui, settings: {entrymode: this.entrymode}}, "100y")
|
||||||
if (this.entrymode) {
|
if (this.entrymode) {
|
||||||
document.getElementById('shoppinglist').scrollTop = 0
|
//document.getElementById('shoppinglist').scrollTop = 0
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
this.setFocus()
|
this.setFocus()
|
||||||
})
|
})
|
||||||
|
@ -3,21 +3,18 @@
|
|||||||
<div id="app">
|
<div id="app">
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
|
<generic-multiselect
|
||||||
|
:model="Models.SHOPPING_CATEGORY"
|
||||||
<b-button-group class="w-100 mb-1" >
|
:multiple="false"
|
||||||
<b-button variant="dark" block class="btn btn-block btn-sm text-left " >100 g Möhren Test <br/><small class="text-muted">Info</small></b-button>
|
></generic-multiselect>
|
||||||
<b-button variant="success"><i class="fas fa-check"></i></b-button>
|
<generic-multiselect
|
||||||
</b-button-group>
|
:model="Models.SHOPPING_CATEGORY"
|
||||||
<br/>
|
:multiple="false"
|
||||||
<b-button-group class="w-100">
|
></generic-multiselect>
|
||||||
<b-button variant="dark" block class="btn btn-block text-left" >150 ml Heißwassermöhrenbrühe<br/><small class="text-muted">Info</small></b-button>
|
<generic-multiselect
|
||||||
<b-button variant="success"><i class="fas fa-check"></i></b-button>
|
:model="Models.SHOPPING_CATEGORY"
|
||||||
</b-button-group>
|
:multiple="false"
|
||||||
|
></generic-multiselect>
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
----
|
----
|
||||||
|
|
||||||
<markdown-editor-component></markdown-editor-component>
|
<markdown-editor-component></markdown-editor-component>
|
||||||
@ -48,7 +45,7 @@ Vue.use(BootstrapVue)
|
|||||||
export default {
|
export default {
|
||||||
name: "TestView",
|
name: "TestView",
|
||||||
mixins: [ApiMixin],
|
mixins: [ApiMixin],
|
||||||
components: {MarkdownEditorComponent},
|
components: {GenericMultiselect, MarkdownEditorComponent},
|
||||||
computed: {},
|
computed: {},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div id="shopping_line_item">
|
<div id="shopping_line_item">
|
||||||
|
|
||||||
<b-button-group class="w-100" v-if="useShoppingListStore().show_checked_entries || !is_checked">
|
<b-button-group class="w-100" v-if="useShoppingListStore().show_checked_entries || !is_checked">
|
||||||
<b-button :class="{'btn-dark': !is_checked, 'btn-success': is_checked}" block class="btn btn-block text-left" @click="detail_modal_visible = true">
|
<b-button :class="{'btn-dark': (!is_checked && !is_delayed), 'btn-success': is_checked, 'btn-warning': is_delayed}" block class="btn btn-block text-left" @click="detail_modal_visible = true">
|
||||||
<div class="d-flex ">
|
<div class="d-flex ">
|
||||||
<div class="d-flex flex-column pr-2" v-if="Object.keys(amounts).length> 0">
|
<div class="d-flex flex-column pr-2" v-if="Object.keys(amounts).length> 0">
|
||||||
<span v-for="a in amounts" v-bind:key="a.id">{{ a.amount }} {{ a.unit }}<br/></span>
|
<span v-for="a in amounts" v-bind:key="a.id">{{ a.amount }} {{ a.unit }}<br/></span>
|
||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
|
|
||||||
</b-button>
|
</b-button>
|
||||||
<b-button variant="success" @click="useShoppingListStore().setFoodCheckedState(food, !is_checked)" :class="{'btn-success': !is_checked, 'btn-warning': is_checked}">
|
<b-button variant="success" @click="useShoppingListStore().setEntriesCheckedState(entries, !is_checked)" :class="{'btn-success': !is_checked, 'btn-warning': is_checked}">
|
||||||
<i class="fas" :class="{'fa-check': !is_checked, 'fa-times': is_checked}"></i>
|
<i class="fas" :class="{'fa-check': !is_checked, 'fa-times': is_checked}"></i>
|
||||||
</b-button>
|
</b-button>
|
||||||
</b-button-group>
|
</b-button-group>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #default>
|
<template #default>
|
||||||
<h6 class="mt-2">Actions</h6> <!-- TODO localize -->
|
<h6 class="mt-2">{{ $t('Quick actions')}}</h6>
|
||||||
<b-form-select
|
<b-form-select
|
||||||
class="form-control mb-2"
|
class="form-control mb-2"
|
||||||
:options="useShoppingListStore().supermarket_categories"
|
:options="useShoppingListStore().supermarket_categories"
|
||||||
@ -39,11 +39,11 @@
|
|||||||
value-field="id"
|
value-field="id"
|
||||||
v-model="food.supermarket_category"
|
v-model="food.supermarket_category"
|
||||||
@change="detail_modal_visible = false; updateFoodCategory(food)"
|
@change="detail_modal_visible = false; updateFoodCategory(food)"
|
||||||
></b-form-select> <!-- TODO change to lookup input or something else that works with dicts -->
|
></b-form-select>
|
||||||
|
|
||||||
<b-button variant="success" block @click="detail_modal_visible = false;"> {{ $t("Edit_Food") }}</b-button> <!-- TODO implement -->
|
<b-button variant="success" block @click="detail_modal_visible = false;"> {{ $t("Edit_Food") }}</b-button> <!-- TODO implement -->
|
||||||
|
|
||||||
<b-button variant="info" block @click="detail_modal_visible = false;useShoppingListStore().delayFood(food)">{{ $t('Delay') }}</b-button>
|
<b-button variant="info" block @click="detail_modal_visible = false;useShoppingListStore().delayEntries(entries)">{{ $t('Delay') }}</b-button>
|
||||||
|
|
||||||
|
|
||||||
<h6 class="mt-2">{{ $t('Entries') }}</h6>
|
<h6 class="mt-2">{{ $t('Entries') }}</h6>
|
||||||
@ -118,6 +118,14 @@ export default {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
is_delayed: function () {
|
||||||
|
for (let i in this.entries) {
|
||||||
|
if ( Date.parse(this.entries[i].delay_until) > new Date(Date.now())) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
food: function () {
|
food: function () {
|
||||||
return this.entries[Object.keys(this.entries)[0]]['food']
|
return this.entries[Object.keys(this.entries)[0]]['food']
|
||||||
},
|
},
|
||||||
@ -210,7 +218,7 @@ export default {
|
|||||||
|
|
||||||
updateFoodCategory: function (food) {
|
updateFoodCategory: function (food) {
|
||||||
|
|
||||||
if (typeof food.supermarket_category === "number"){
|
if (typeof food.supermarket_category === "number"){ // not the best solution, but as long as generic multiselect does not support caching, I don't want to use a proper model
|
||||||
food.supermarket_category = this.useShoppingListStore().supermarket_categories.filter(sc => sc.id === food.supermarket_category)[0]
|
food.supermarket_category = this.useShoppingListStore().supermarket_categories.filter(sc => sc.id === food.supermarket_category)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,15 +2,18 @@ import {ApiApiFactory} from "@/utils/openapi/api"
|
|||||||
import {StandardToasts} from "@/utils/utils"
|
import {StandardToasts} from "@/utils/utils"
|
||||||
import {defineStore} from "pinia"
|
import {defineStore} from "pinia"
|
||||||
import Vue from "vue"
|
import Vue from "vue"
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
const _STORE_ID = "shopping_list_store"
|
const _STORE_ID = "shopping_list_store"
|
||||||
const _LOCAL_STORAGE_KEY = "SHOPPING_LIST_CLIENT_SETTINGS"
|
const _LOCAL_STORAGE_KEY = "SHOPPING_LIST_CLIENT_SETTINGS"
|
||||||
/*
|
/*
|
||||||
* test store to play around with pinia and see if it can work for my usecases
|
* test store to play around with pinia and see if it can work for my use cases
|
||||||
* dont trust that all shopping list entries are in store as there is no cache validation logic, its just a shared data holder
|
* don't 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, {
|
export const useShoppingListStore = defineStore(_STORE_ID, {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
|
entries: {},
|
||||||
|
|
||||||
category_food_entries: {},
|
category_food_entries: {},
|
||||||
supermarket_categories: [],
|
supermarket_categories: [],
|
||||||
|
|
||||||
@ -18,9 +21,25 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
|||||||
|
|
||||||
currently_updating: false,
|
currently_updating: false,
|
||||||
settings: null,
|
settings: null,
|
||||||
|
|
||||||
|
|
||||||
|
GROUP_CATEGORY: 'food.supermarket_category.name',
|
||||||
|
GROUP_CREATED_BY: 'created_by.display_name',
|
||||||
|
GROUP_RECIPE: 'recipe_mealplan.recipe_name',
|
||||||
|
GROUP_MEALPLAN: 'recipe_mealplan.mealplan', //TODO give this some name from the API
|
||||||
|
|
||||||
|
selected_group: 'food.supermarket_category.name',
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
|
|
||||||
|
get_entries_by_group: function () {
|
||||||
|
let structure = {}
|
||||||
|
for (let i in this.entries) {
|
||||||
|
structure = this.updateEntryInStructure(structure, this.entries[i], this.selected_group)
|
||||||
|
}
|
||||||
|
return structure
|
||||||
|
},
|
||||||
|
|
||||||
client_settings: function () {
|
client_settings: function () {
|
||||||
if (this.settings === null) {
|
if (this.settings === null) {
|
||||||
this.settings = this.loadClientSettings()
|
this.settings = this.loadClientSettings()
|
||||||
@ -41,7 +60,7 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
|||||||
let apiClient = new ApiApiFactory()
|
let apiClient = new ApiApiFactory()
|
||||||
apiClient.listShoppingListEntrys().then((r) => {
|
apiClient.listShoppingListEntrys().then((r) => {
|
||||||
r.data.forEach((e) => {
|
r.data.forEach((e) => {
|
||||||
this.updateEntryInStructure(e)
|
Vue.set(this.entries, e.id, e)
|
||||||
})
|
})
|
||||||
this.currently_updating = false
|
this.currently_updating = false
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
@ -61,7 +80,7 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
|||||||
// TODO shared handled in backend?
|
// TODO shared handled in backend?
|
||||||
|
|
||||||
return apiClient.createShoppingListEntry(object).then((r) => {
|
return apiClient.createShoppingListEntry(object).then((r) => {
|
||||||
this.updateEntryInStructure(r.data)
|
Vue.set(this.entries, r.data.id, r.data)
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||||
})
|
})
|
||||||
@ -69,7 +88,7 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
|||||||
updateObject(object) {
|
updateObject(object) {
|
||||||
let apiClient = new ApiApiFactory()
|
let apiClient = new ApiApiFactory()
|
||||||
return apiClient.updateShoppingListEntry(object.id, object).then((r) => {
|
return apiClient.updateShoppingListEntry(object.id, object).then((r) => {
|
||||||
this.updateEntryInStructure(r.data)
|
Vue.set(this.entries, r.data.id, r.data)
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||||
})
|
})
|
||||||
@ -114,28 +133,41 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
|||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
},
|
},
|
||||||
updateEntryInStructure(entry) {
|
|
||||||
let category = this.getFoodCategory(entry.food)
|
|
||||||
if (!(category in this.category_food_entries)) {
|
|
||||||
Vue.set(this.category_food_entries, category, {'id': category, 'name': entry.food.supermarket_category.name, 'foods': {}})
|
|
||||||
}
|
|
||||||
if (!(entry.food.id in this.category_food_entries[category]['foods'])) {
|
|
||||||
Vue.set(this.category_food_entries[category]['foods'], entry.food.id, {'id': entry.food.id, 'name': entry.food.name, 'entries': {}})
|
|
||||||
}
|
|
||||||
Vue.set(this.category_food_entries[category]['foods'][entry.food.id]['entries'], entry.id, entry)
|
|
||||||
},
|
|
||||||
setFoodCheckedState(food, checked) {
|
|
||||||
/**
|
/**
|
||||||
* function to handle user checking or unchecking a food
|
* function to set entry to its proper place in the data structure to perform grouping
|
||||||
|
* @param {{}} structure datastructure
|
||||||
|
* @param {*} entry entry to place
|
||||||
|
* @param {*} group group to place entry into (must be of ShoppingListStore.GROUP_XXX/dot notation of entry property)
|
||||||
|
* @returns updated datastructure including entry
|
||||||
*/
|
*/
|
||||||
|
updateEntryInStructure(structure, entry, group) {
|
||||||
|
let grouping_key = _.get(entry, group, -1)
|
||||||
|
// todo handele parent
|
||||||
|
if (grouping_key === undefined || grouping_key === null) {
|
||||||
|
grouping_key = -1
|
||||||
|
}
|
||||||
|
|
||||||
let entries = this.category_food_entries[this.getFoodCategory(food)]['foods'][food.id]['entries']
|
if (!(grouping_key in structure)) {
|
||||||
|
Vue.set(structure, grouping_key, {'name': grouping_key, 'foods': {}})
|
||||||
|
}
|
||||||
|
if (!(entry.food.id in structure[grouping_key]['foods'])) {
|
||||||
|
Vue.set(structure[grouping_key]['foods'], entry.food.id, {'id': entry.food.id, 'name': entry.food.name, 'entries': {}})
|
||||||
|
}
|
||||||
|
Vue.set(structure[grouping_key]['foods'][entry.food.id]['entries'], entry.id, entry)
|
||||||
|
return structure
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* function to handle user checking or unchecking a set of entries
|
||||||
|
* @param {{}} entries set of entries
|
||||||
|
* @param checked boolean to set checked state of entry to
|
||||||
|
*/
|
||||||
|
setEntriesCheckedState(entries, checked) {
|
||||||
for (let i in entries) {
|
for (let i in entries) {
|
||||||
entries[i].checked = checked
|
this.entries[i].checked = checked
|
||||||
this.updateObject(entries[i])
|
this.updateObject(this.entries[i])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
delayFood(food) {
|
delayEntries(entries) {
|
||||||
/**
|
/**
|
||||||
* function to handle user "delaying" shopping entry
|
* function to handle user "delaying" shopping entry
|
||||||
* takes a food object as an argument and delays all entries associated with the food
|
* takes a food object as an argument and delays all entries associated with the food
|
||||||
@ -143,12 +175,11 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
|||||||
let delay = 4 //TODO get delay from settings
|
let delay = 4 //TODO get delay from settings
|
||||||
let delay_date = new Date(Date.now() + delay * (60 * 60 * 1000))
|
let delay_date = new Date(Date.now() + delay * (60 * 60 * 1000))
|
||||||
|
|
||||||
let entries = this.category_food_entries[this.getFoodCategory(food)]['foods'][food.id]['entries']
|
|
||||||
for (let i in entries) {
|
for (let i in entries) {
|
||||||
entries[i].delayed_until = delay_date
|
console.log('DELAYING ', i, ' until ', delay_date)
|
||||||
this.updateObject(entries[i])
|
this.entries[i].delay_until = delay_date
|
||||||
|
this.updateObject(this.entries[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
deleteFood(food) {
|
deleteFood(food) {
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user