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>
<!-- add alert at top if offline -->
<!-- get autosync time from preferences and put fetching checked items on timer -->
<!-- allow reordering or items -->
<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="offset-md-10 col-md-2 no-gutter text-right">
<b-button variant="link" class="px-0">
@ -19,9 +19,7 @@
<div class="container">
<div class="row">
<div class="col col-md-12">
<!-- TODO add spinner -->
<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="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">
@ -74,12 +72,7 @@
<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 -->
<div v-for="(entries, x) in Object.entries(s)" :key="x">
<ShoppingLineItem
:entries="entries[1]"
:groupby="group_by"
@open-context-menu="openContextMenu"
@update-checkbox="updateChecked"
></ShoppingLineItem>
<ShoppingLineItem :entries="entries[1]" :groupby="group_by" @open-context-menu="openContextMenu" @update-checkbox="updateChecked" />
</div>
</div>
</div>
@ -93,7 +86,6 @@
<table class="table w-75">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">{{ $t("Meal_Plan") }}</th>
<th scope="col">{{ $t("Recipe") }}</th>
<th scope="col">{{ $t("Servings") }}</th>
@ -101,7 +93,6 @@
</tr>
</thead>
<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.recipe_name }}</td>
<td class="block-inline">
@ -230,8 +221,6 @@ import { StandardToasts, makeToast } from "@/utils/utils"
Vue.use(BootstrapVue)
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",
mixins: [ApiMixin],
components: { ContextMenu, ContextMenuItem, ShoppingLineItem, GenericMultiselect },
@ -240,7 +229,7 @@ export default {
return {
// this.Models and this.Actions inherited from ApiMixin
items: [],
group_by: "category", //TODO create setting to switch group_by
group_by: "category",
group_by_choices: ["created_by", "category", "recipe"],
supermarkets: [],
shopping_categories: [],
@ -248,13 +237,17 @@ export default {
show_undefined_categories: true,
supermarket_categories_only: false,
shopcat: null,
delay: 0, // user default
delay: 0,
autosync: 0,
autosync_id: undefined,
auto_sync_running: false,
show_delay: false,
show_modal: false,
fields: ["checked", "amount", "category", "unit", "food", "recipe", "details"],
loading: true,
entrymode: false,
new_item: { amount: 1, unit: undefined, food: undefined },
online: true,
}
},
computed: {
@ -341,22 +334,44 @@ export default {
selected_supermarket(newVal, oldVal) {
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() {
// value is passed from lists.py
this.getShoppingList()
this.getSupermarkets()
this.getShoppingCategories()
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: {
// this.genericAPI inherited from ApiMixin
addItem() {
console.log(this.new_item)
let api = new ApiApiFactory()
api.createShoppingListEntry(this.new_item)
.then((results) => {
console.log(results)
if (results?.data) {
this.items.push(results.data)
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
@ -436,21 +451,28 @@ export default {
this.shopping_categories = result.data
})
},
getShoppingList: function(params = {}) {
this.loading = true
params = {
supermarket: this.selected_supermarket,
}
getShoppingList: function(autosync = false) {
let params = {}
params.supermarket = this.selected_supermarket
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)
.then((results) => {
console.log(results.data)
if (results.data?.length) {
this.items = results.data
if (!autosync) {
if (results.data?.length) {
this.items = results.data
} else {
console.log("no data returned")
}
this.loading = false
} else {
console.log("no data returned")
this.mergeShoppingList(results.data)
}
this.loading = false
})
.catch((err) => {
console.log(err)
@ -461,15 +483,11 @@ export default {
let api = new ApiApiFactory()
api.listSupermarkets().then((result) => {
this.supermarkets = result.data
console.log(this.supermarkets)
})
},
getThis: function(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) {
let food = {
id: item?.[0]?.food.id ?? item.food.id,
@ -477,6 +495,18 @@ export default {
}
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) {
if (!e) {
makeToast(this.$t("Warning"), this.$t("NoCategory"), "warning")
@ -536,7 +566,6 @@ export default {
})
.catch((err) => {
console.log(err, err.response)
StandardToasts.makeStandardToast(StandardToasts.FAIL_UPDATE)
})
}
@ -546,15 +575,17 @@ export default {
},
updateChecked: function(update) {
// when checking a sub item don't refresh the screen until all entries complete but change class to cross out
let promises = []
update.entries.forEach((x) => {
console.log({ id: x, checked: update.checked })
promises.push(this.saveThis({ id: x, checked: update.checked }, false))
let item = this.items.filter((entry) => entry.id == x)[0]
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) => {
console.log(err, err.response)
@ -597,6 +628,14 @@ export default {
this.getShoppingList()
})
},
updateOnlineStatus(e) {
const { type } = e
this.online = type === "online"
},
beforeDestroy() {
window.removeEventListener("online", this.updateOnlineStatus)
window.removeEventListener("offline", this.updateOnlineStatus)
},
},
}
</script>
@ -618,4 +657,8 @@ export default {
padding-bottom: -3em;
margin-bottom: -3em;
}
.float-up {
padding-top: -3em;
margin-top: -3em;
}
</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" />
</div>
</div>
<div class="col col-md-1">{{ formatAmount }}</div>
<div class="col col-md-1">{{ formatUnit }}</div>
<div class="col-sm-3">
<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">
{{ formatFood }} <span class="small text-muted">{{ formatHint }}</span>
@ -41,7 +43,7 @@
aria-haspopup="true"
aria-expanded="false"
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;"
@click.stop="openRecipeCard($event, e)"
@mouseover="openRecipeCard($event, e)"
@ -74,6 +76,7 @@
</div>
<div class="col-sm-1">{{ formatOneAmount(e) }}</div>
<div class="col-sm-2">{{ formatOneUnit(e) }}</div>
<div class="col-sm-3">{{ formatOneFood(e) }}</div>
<div class="col-sm-4">
@ -136,7 +139,18 @@ export default {
},
computed: {
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() {
return this.formatOneCategory(this.entries[0]) || this.$t("Undefined")