This commit is contained in:
smilerz
2021-10-26 21:58:06 -05:00
parent b84d77be15
commit 4c506750de
2 changed files with 97 additions and 40 deletions

View File

@@ -1,8 +1,8 @@
<template> <template>
<!-- add alert at top if offline --> <!-- add alert at top if offline -->
<!-- get autosync time from preferences and put fetching checked items on timer --> <!-- get autosync time from preferences and put fetching checked items on timer -->
<!-- allow reordering or items -->
<div id="app" style="margin-bottom: 4vh"> <div id="app" style="margin-bottom: 4vh">
<b-alert :show="online" dismissible class="small float-up" variant="warning">{{ $t("OfflineAlert") }}</b-alert>
<div class="row float-top"> <div class="row float-top">
<div class="offset-md-10 col-md-2 no-gutter text-right"> <div class="offset-md-10 col-md-2 no-gutter text-right">
<b-button variant="link" class="px-0"> <b-button variant="link" class="px-0">
@@ -19,9 +19,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col col-md-12"> <div class="col col-md-12">
<!-- TODO add spinner -->
<div role="tablist" v-if="items && items.length > 0"> <div role="tablist" v-if="items && items.length > 0">
<!-- WARNING: all data in the table should be considered read-only, don't change any data through table bindings -->
<div class="row justify-content-md-center w-75" v-if="entrymode"> <div class="row justify-content-md-center w-75" v-if="entrymode">
<div class="col col-md-2 "><b-form-input min="1" type="number" :description="$t('Amount')" v-model="new_item.amount"></b-form-input></div> <div class="col col-md-2 "><b-form-input min="1" type="number" :description="$t('Amount')" v-model="new_item.amount"></b-form-input></div>
<div class="col col-md-3"> <div class="col col-md-3">
@@ -74,12 +72,7 @@
<div class="collapse" :id="'section-' + sectionID(x, i)" visible role="tabpanel" :class="{ show: x == 'false' }"> <div class="collapse" :id="'section-' + sectionID(x, i)" visible role="tabpanel" :class="{ show: x == 'false' }">
<!-- passing an array of values to the table grouped by Food --> <!-- passing an array of values to the table grouped by Food -->
<div v-for="(entries, x) in Object.entries(s)" :key="x"> <div v-for="(entries, x) in Object.entries(s)" :key="x">
<ShoppingLineItem <ShoppingLineItem :entries="entries[1]" :groupby="group_by" @open-context-menu="openContextMenu" @update-checkbox="updateChecked" />
:entries="entries[1]"
:groupby="group_by"
@open-context-menu="openContextMenu"
@update-checkbox="updateChecked"
></ShoppingLineItem>
</div> </div>
</div> </div>
</div> </div>
@@ -93,7 +86,6 @@
<table class="table w-75"> <table class="table w-75">
<thead> <thead>
<tr> <tr>
<th scope="col">ID</th>
<th scope="col">{{ $t("Meal_Plan") }}</th> <th scope="col">{{ $t("Meal_Plan") }}</th>
<th scope="col">{{ $t("Recipe") }}</th> <th scope="col">{{ $t("Recipe") }}</th>
<th scope="col">{{ $t("Servings") }}</th> <th scope="col">{{ $t("Servings") }}</th>
@@ -101,7 +93,6 @@
</tr> </tr>
</thead> </thead>
<tr v-for="r in Recipes" :key="r.list_recipe"> <tr v-for="r in Recipes" :key="r.list_recipe">
<td>{{ r.list_recipe }}</td>
<td>{{ r.recipe_mealplan.name }}</td> <td>{{ r.recipe_mealplan.name }}</td>
<td>{{ r.recipe_mealplan.recipe_name }}</td> <td>{{ r.recipe_mealplan.recipe_name }}</td>
<td class="block-inline"> <td class="block-inline">
@@ -230,8 +221,6 @@ import { StandardToasts, makeToast } from "@/utils/utils"
Vue.use(BootstrapVue) Vue.use(BootstrapVue)
export default { export default {
// TODO ApiGenerator doesn't capture and share error information - would be nice to share error details when available
// or i'm capturing it incorrectly
name: "ShoppingListView", name: "ShoppingListView",
mixins: [ApiMixin], mixins: [ApiMixin],
components: { ContextMenu, ContextMenuItem, ShoppingLineItem, GenericMultiselect }, components: { ContextMenu, ContextMenuItem, ShoppingLineItem, GenericMultiselect },
@@ -240,7 +229,7 @@ export default {
return { return {
// this.Models and this.Actions inherited from ApiMixin // this.Models and this.Actions inherited from ApiMixin
items: [], items: [],
group_by: "category", //TODO create setting to switch group_by group_by: "category",
group_by_choices: ["created_by", "category", "recipe"], group_by_choices: ["created_by", "category", "recipe"],
supermarkets: [], supermarkets: [],
shopping_categories: [], shopping_categories: [],
@@ -248,13 +237,17 @@ export default {
show_undefined_categories: true, show_undefined_categories: true,
supermarket_categories_only: false, supermarket_categories_only: false,
shopcat: null, shopcat: null,
delay: 0, // user default delay: 0,
autosync: 0,
autosync_id: undefined,
auto_sync_running: false,
show_delay: false, show_delay: false,
show_modal: false, show_modal: false,
fields: ["checked", "amount", "category", "unit", "food", "recipe", "details"], fields: ["checked", "amount", "category", "unit", "food", "recipe", "details"],
loading: true, loading: true,
entrymode: false, entrymode: false,
new_item: { amount: 1, unit: undefined, food: undefined }, new_item: { amount: 1, unit: undefined, food: undefined },
online: true,
} }
}, },
computed: { computed: {
@@ -341,22 +334,44 @@ export default {
selected_supermarket(newVal, oldVal) { selected_supermarket(newVal, oldVal) {
this.getShoppingList() this.getShoppingList()
}, },
autosync(newVal, oldVal) {
clearInterval(this.autosync_id)
this.autosync_id = undefined
if (!newVal) {
window.removeEventListener("online", this.updateOnlineStatus)
window.removeEventListener("offline", this.updateOnlineStatus)
return
} else if (oldVal === 0 && newVal > 0) {
console.log("adding listener")
window.addEventListener("online", this.updateOnlineStatus)
window.addEventListener("offline", this.updateOnlineStatus)
}
this.autosync_id = setInterval(() => {
if (this.online && !this.auto_sync_running) {
console.log("interval", this.online && !this.auto_sync_running)
this.auto_sync_running = true
this.getShoppingList(true)
}
}, this.autosync * 1000)
},
}, },
mounted() { mounted() {
// value is passed from lists.py
this.getShoppingList() this.getShoppingList()
this.getSupermarkets() this.getSupermarkets()
this.getShoppingCategories() this.getShoppingCategories()
this.delay = getUserPreference("default_delay") || 4 this.delay = getUserPreference("default_delay") || 4
this.autosync = getUserPreference("shopping_auto_sync")
if (this.autosync) {
window.addEventListener("online", this.updateOnlineStatus)
window.addEventListener("offline", this.updateOnlineStatus)
}
}, },
methods: { methods: {
// this.genericAPI inherited from ApiMixin // this.genericAPI inherited from ApiMixin
addItem() { addItem() {
console.log(this.new_item)
let api = new ApiApiFactory() let api = new ApiApiFactory()
api.createShoppingListEntry(this.new_item) api.createShoppingListEntry(this.new_item)
.then((results) => { .then((results) => {
console.log(results)
if (results?.data) { if (results?.data) {
this.items.push(results.data) this.items.push(results.data)
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE) StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
@@ -436,21 +451,28 @@ export default {
this.shopping_categories = result.data this.shopping_categories = result.data
}) })
}, },
getShoppingList: function(params = {}) { getShoppingList: function(autosync = false) {
this.loading = true let params = {}
params = { params.supermarket = this.selected_supermarket
supermarket: this.selected_supermarket,
}
params.options = { query: { recent: 1 } } params.options = { query: { recent: 1 } }
if (autosync) {
params.options.query["autosync"] = 1
} else {
this.loading = true
}
this.genericAPI(this.Models.SHOPPING_LIST, this.Actions.LIST, params) this.genericAPI(this.Models.SHOPPING_LIST, this.Actions.LIST, params)
.then((results) => { .then((results) => {
console.log(results.data) if (!autosync) {
if (results.data?.length) { if (results.data?.length) {
this.items = results.data this.items = results.data
} else {
console.log("no data returned")
}
this.loading = false
} else { } else {
console.log("no data returned") this.mergeShoppingList(results.data)
} }
this.loading = false
}) })
.catch((err) => { .catch((err) => {
console.log(err) console.log(err)
@@ -461,15 +483,11 @@ export default {
let api = new ApiApiFactory() let api = new ApiApiFactory()
api.listSupermarkets().then((result) => { api.listSupermarkets().then((result) => {
this.supermarkets = result.data this.supermarkets = result.data
console.log(this.supermarkets)
}) })
}, },
getThis: function(id) { getThis: function(id) {
return this.genericAPI(this.Models.SHOPPING_CATEGORY, this.Actions.FETCH, { id: id }) return this.genericAPI(this.Models.SHOPPING_CATEGORY, this.Actions.FETCH, { id: id })
}, },
getRecipe: function(item) {
// change to get pop up card? maybe same for unit and food?
},
ignoreThis: function(item) { ignoreThis: function(item) {
let food = { let food = {
id: item?.[0]?.food.id ?? item.food.id, id: item?.[0]?.food.id ?? item.food.id,
@@ -477,6 +495,18 @@ export default {
} }
this.updateFood(food, "ignore_shopping") this.updateFood(food, "ignore_shopping")
}, },
mergeShoppingList: function(data) {
console.log(data)
this.items.map((x) =>
data.map((y) => {
if (y.id === x.id) {
x.checked = y.checked
return x
}
})
)
this.auto_sync_running = false
},
moveEntry: function(e, item) { moveEntry: function(e, item) {
if (!e) { if (!e) {
makeToast(this.$t("Warning"), this.$t("NoCategory"), "warning") makeToast(this.$t("Warning"), this.$t("NoCategory"), "warning")
@@ -536,7 +566,6 @@ export default {
}) })
.catch((err) => { .catch((err) => {
console.log(err, err.response) console.log(err, err.response)
StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE) StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE)
}) })
} }
@@ -546,15 +575,17 @@ export default {
}, },
updateChecked: function(update) { updateChecked: function(update) {
// when checking a sub item don't refresh the screen until all entries complete but change class to cross out // when checking a sub item don't refresh the screen until all entries complete but change class to cross out
let promises = [] let promises = []
update.entries.forEach((x) => { update.entries.forEach((x) => {
console.log({ id: x, checked: update.checked })
promises.push(this.saveThis({ id: x, checked: update.checked }, false)) promises.push(this.saveThis({ id: x, checked: update.checked }, false))
let item = this.items.filter((entry) => entry.id == x)[0] let item = this.items.filter((entry) => entry.id == x)[0]
Vue.set(item, "checked", update.checked) Vue.set(item, "checked", update.checked)
Vue.set(item, "completed_at", new Date().toISOString()) if (update.checked) {
Vue.set(item, "completed_at", new Date().toISOString())
} else {
Vue.set(item, "completed_at", undefined)
}
}) })
Promise.all(promises).catch((err) => { Promise.all(promises).catch((err) => {
console.log(err, err.response) console.log(err, err.response)
@@ -597,6 +628,14 @@ export default {
this.getShoppingList() this.getShoppingList()
}) })
}, },
updateOnlineStatus(e) {
const { type } = e
this.online = type === "online"
},
beforeDestroy() {
window.removeEventListener("online", this.updateOnlineStatus)
window.removeEventListener("offline", this.updateOnlineStatus)
},
}, },
} }
</script> </script>
@@ -618,4 +657,8 @@ export default {
padding-bottom: -3em; padding-bottom: -3em;
margin-bottom: -3em; margin-bottom: -3em;
} }
.float-up {
padding-top: -3em;
margin-top: -3em;
}
</style> </style>

View File

@@ -21,8 +21,10 @@
<input type="checkbox" class="text-right mx-3 mt-2" :checked="formatChecked" @change="updateChecked" :key="entries[0].id" /> <input type="checkbox" class="text-right mx-3 mt-2" :checked="formatChecked" @change="updateChecked" :key="entries[0].id" />
</div> </div>
</div> </div>
<div class="col col-md-1">{{ formatAmount }}</div> <div class="col-sm-3">
<div class="col col-md-1">{{ formatUnit }}</div> <div v-if="Object.entries(formatAmount).length == 1">{{ Object.entries(formatAmount)[0][1] }} &ensp; {{ Object.entries(formatAmount)[0][0] }}</div>
<div class="small" v-else v-for="(x, i) in Object.entries(formatAmount)" :key="i">{{ x[1] }} &ensp; {{ x[0] }}</div>
</div>
<div class="col col-md-6"> <div class="col col-md-6">
{{ formatFood }} <span class="small text-muted">{{ formatHint }}</span> {{ formatFood }} <span class="small text-muted">{{ formatHint }}</span>
@@ -41,7 +43,7 @@
aria-haspopup="true" aria-haspopup="true"
aria-expanded="false" aria-expanded="false"
type="button" type="button"
class="btn btn-link stn-sm m-0 p-0" class="btn btn-link btn-sm m-0 p-0"
style="text-overflow: ellipsis;" style="text-overflow: ellipsis;"
@click.stop="openRecipeCard($event, e)" @click.stop="openRecipeCard($event, e)"
@mouseover="openRecipeCard($event, e)" @mouseover="openRecipeCard($event, e)"
@@ -74,6 +76,7 @@
</div> </div>
<div class="col-sm-1">{{ formatOneAmount(e) }}</div> <div class="col-sm-1">{{ formatOneAmount(e) }}</div>
<div class="col-sm-2">{{ formatOneUnit(e) }}</div> <div class="col-sm-2">{{ formatOneUnit(e) }}</div>
<div class="col-sm-3">{{ formatOneFood(e) }}</div> <div class="col-sm-3">{{ formatOneFood(e) }}</div>
<div class="col-sm-4"> <div class="col-sm-4">
@@ -136,7 +139,18 @@ export default {
}, },
computed: { computed: {
formatAmount: function() { formatAmount: function() {
return this.formatOneAmount(this.entries[0]) let amount = {}
this.entries.forEach((entry) => {
let unit = entry?.unit?.name ?? "----"
if (entry.amount) {
if (amount[unit]) {
amount[unit] += entry.amount
} else {
amount[unit] = entry.amount
}
}
})
return amount
}, },
formatCategory: function() { formatCategory: function() {
return this.formatOneCategory(this.entries[0]) || this.$t("Undefined") return this.formatOneCategory(this.entries[0]) || this.$t("Undefined")