several shopping fixes

This commit is contained in:
vabene1111
2024-11-27 16:52:48 +01:00
parent 2fab51ea9b
commit 110ffe52b7
4 changed files with 69 additions and 47 deletions

View File

@@ -66,11 +66,11 @@
{{ $t('PostponedUntil') }} {{ DateTime.fromJSDate(e.delayUntil).toLocaleString(DateTime.DATETIME_SHORT) }} {{ $t('PostponedUntil') }} {{ DateTime.fromJSDate(e.delayUntil).toLocaleString(DateTime.DATETIME_SHORT) }}
</v-list-item-subtitle> </v-list-item-subtitle>
<template #append> <!-- <template #append>-->
<v-btn size="small" color="delete" icon="$delete" v-if="!e.recipeMealplan"> <!-- <v-btn size="small" color="delete" icon="$delete" v-if="!e.recipeMealplan">-->
<v-icon icon="$delete"></v-icon> <!-- <v-icon icon="$delete"></v-icon>-->
</v-btn> <!-- </v-btn>-->
</template> <!-- </template>-->
<!-- TODO make properly reactive or delete from the food instance in this component as well | ADD functionality once reactive --> <!-- TODO make properly reactive or delete from the food instance in this component as well | ADD functionality once reactive -->
<model-edit-dialog model="ShoppingListEntry" :item="e" @delete="useShoppingStore().entries.delete(e.id!);" v-if="!e.recipeMealplan"></model-edit-dialog> <model-edit-dialog model="ShoppingListEntry" :item="e" @delete="useShoppingStore().entries.delete(e.id!);" v-if="!e.recipeMealplan"></model-edit-dialog>
@@ -126,6 +126,10 @@ const isShoppingLineDelayed = computed(() => {
return isShoppingListFoodDelayed(shoppingListFood.value) return isShoppingListFoodDelayed(shoppingListFood.value)
}) })
/**
* change category of food and update via API
* @param category
*/
function categoryUpdate(category: SupermarketCategory) { function categoryUpdate(category: SupermarketCategory) {
const api = new ApiApi() const api = new ApiApi()
shoppingListFood.value.food.supermarketCategory = category shoppingListFood.value.food.supermarketCategory = category

View File

@@ -15,23 +15,25 @@
<span> <span>
<i class="fas fa-check text-warning" v-if="a.checked && !isChecked"></i> <i class="fas fa-check text-warning" v-if="a.checked && !isChecked"></i>
<i class="fas fa-hourglass-half text-primary" v-if="a.delayed && !a.checked"></i> <b> <i class="fas fa-hourglass-half text-primary" v-if="a.delayed && !a.checked"></i> <b>
{{ a.amount }} <span :class="{'text-decoration-line-through': a.checked}">
{{ a.amount }}
</span>
<span v-if="a.unit">{{ a.unit.name }}</span> <span v-if="a.unit">{{ a.unit.name }}</span>
</b> </b>
</span> </span>
<br/> <br/>
</span> </span>
</div> </div>
<div class="d-flex flex-column flex-grow-1 align-self-center"> <div class="d-flex flex-column flex-grow-1 align-self-center" :class="{'text-decoration-line-through': isChecked}">
{{ food.name }} <br/> {{ shoppingListFood.food.name }} <br/>
<span v-if="info_row"><small class="text-disabled">{{ info_row }}</small></span> <span v-if="infoRow"><small class="text-disabled">{{ infoRow }}</small></span>
</div> </div>
</div> </div>
</div> </div>
<template #append> <template #append>
<v-btn color="success" @click="useShoppingStore().setEntriesCheckedState(entries, !isChecked, true)" <v-btn color="success" @click.native.stop="useShoppingStore().setEntriesCheckedState(entries, !isChecked, true);"
:class="{'btn-success': !isChecked, 'btn-warning': isChecked}" icon="fa-solid fa-check" variant="plain"> :class="{'btn-success': !isChecked, 'btn-warning': isChecked}" :icon="actionButtonIcon" variant="plain">
</v-btn> </v-btn>
<!-- <i class="d-print-none fa-fw fas" :class="{'fa-check': !isChecked , 'fa-cart-plus': isChecked }"></i>--> <!-- <i class="d-print-none fa-fw fas" :class="{'fa-check': !isChecked , 'fa-cart-plus': isChecked }"></i>-->
</template> </template>
@@ -63,6 +65,9 @@ const props = defineProps({
shoppingListFood: {type: {} as PropType<IShoppingListFood>, required: true}, shoppingListFood: {type: {} as PropType<IShoppingListFood>, required: true},
}) })
/**
* ID of outer container, used by swipe system
*/
const itemContainerId = computed(() => { const itemContainerId = computed(() => {
let id = 'id_sli_' let id = 'id_sli_'
for (let i in props.entries) { for (let i in props.entries) {
@@ -71,6 +76,10 @@ const itemContainerId = computed(() => {
return id return id
}) })
/**
* tests if all entries of the given food are checked
*/
const isChecked = computed(() => { const isChecked = computed(() => {
for (let i in props.entries) { for (let i in props.entries) {
if (!props.entries[i].checked) { if (!props.entries[i].checked) {
@@ -80,15 +89,24 @@ const isChecked = computed(() => {
return true return true
}) })
/**
* determine if any entry in a given IShoppingListFood is delayed, if so return true
*/
const isShoppingLineDelayed = computed(() => { const isShoppingLineDelayed = computed(() => {
return isShoppingListFoodDelayed(props.shoppingListFood) return isShoppingListFoodDelayed(props.shoppingListFood)
}) })
/**
const food = computed(() => { * style action button depending on if all items are checked or not
return props.entries[Object.keys(props.entries)[0]]['food'] */
const actionButtonIcon = computed(() => {
if (isChecked.value){
return 'fa-solid fa-plus'
}
return 'fa-solid fa-check'
}) })
/** /**
* calculate the amounts for the given line * calculate the amounts for the given line
* can combine 1 to n entries with the same unit * can combine 1 to n entries with the same unit
@@ -128,7 +146,7 @@ const amounts = computed((): Map<number, ShoppingLineAmount> => {
return unitAmounts return unitAmounts
}) })
const info_row = computed(() => { const infoRow = computed(() => {
let info_row = [] let info_row = []
let authors = [] let authors = []
@@ -170,25 +188,6 @@ const info_row = computed(() => {
return info_row.join(' - ') return info_row.join(' - ')
}) })
// TODO implement
/**
* update the food after the category was changed
* handle changing category to category ID as a workaround
* @param food
*/
function updateFoodCategory(food: Food) {
// if (typeof food.supermarketCategory === "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]
// }
//
// let apiClient = new ApiApiFactory()
// apiClient.updateFood(food.id, food).then(r => {
//
// }).catch((err) => {
// StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
// })
}
/** /**
* set food on_hand status to true and check all associated entries * set food on_hand status to true and check all associated entries
* @param food * @param food

View File

@@ -92,6 +92,28 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-col> </v-col>
<v-col cols="4">
<v-card>
<v-card-title>Sync Queue Debug</v-card-title>
<v-card-text>
Length: {{ useShoppingStore().itemCheckSyncQueue.length }} <br/>
Has Failed Items: {{ useShoppingStore().hasFailedItems()}}
<v-list>
<v-list-item v-for="i in useShoppingStore().itemCheckSyncQueue" :key="i">{{ i }}</v-list-item>
</v-list>
</v-card-text>
</v-card>
</v-col>
<v-col cols="4">
<v-card>
<v-card-title>Undo Debug</v-card-title>
<v-card-text>
<v-list>
<v-list-item v-for="i in useShoppingStore().undoStack" :key="i">{{ i.type }} {{ i.entries.flatMap(e => e.food.name)}}</v-list-item>
</v-list>
</v-card-text>
</v-card>
</v-col>
</v-row> </v-row>
</v-container> </v-container>
</v-window-item> </v-window-item>

View File

@@ -1,5 +1,5 @@
import {acceptHMRUpdate, defineStore} from "pinia" import {acceptHMRUpdate, defineStore} from "pinia"
import {ApiApi, Food, ShoppingListEntry, Supermarket, SupermarketCategory} from "@/openapi"; import {ApiApi, Food, ShoppingListEntry, ShoppingListEntryBulk, Supermarket, SupermarketCategory} from "@/openapi";
import {computed, ref} from "vue"; import {computed, ref} from "vue";
import { import {
IShoppingExportEntry, IShoppingExportEntry,
@@ -346,7 +346,6 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
let entryIdList: number[] = [] let entryIdList: number[] = []
entries.forEach(entry => { entries.forEach(entry => {
entry.checked = checked entry.checked = checked
// TODO used to set updatedAt but does not make sense on client, rethink solution (as above)
entryIdList.push(entry.id!) entryIdList.push(entry.id!)
}) })
@@ -367,34 +366,30 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
function _replaySyncQueue() { function _replaySyncQueue() {
if (navigator.onLine || document.location.href.includes('localhost')) { if (navigator.onLine || document.location.href.includes('localhost')) {
let api = new ApiApi() let api = new ApiApi()
let promises = [] let promises: Promise<void>[] = []
for (let i in itemCheckSyncQueue.value) { itemCheckSyncQueue.value.forEach((entry, index) => {
let entry = itemCheckSyncQueue.value[i]
entry['status'] = ((entry['status'] === 'waiting') ? 'syncing' : 'syncing_failed_before') entry['status'] = ((entry['status'] === 'waiting') ? 'syncing' : 'syncing_failed_before')
itemCheckSyncQueue.value[i] = entry
// TODO set timeout for request (previously was 15000ms) or check that default timeout is similar let p = api.apiShoppingListEntryBulkCreate({shoppingListEntryBulk: entry}, {}).then((r) => {
let p = api.apiShoppingListEntryBulkCreate({shoppingListEntryBulk: entry}, {}).then((r) => {
entry.ids.forEach(id => { entry.ids.forEach(id => {
let e = entries.value.get(id) let e = entries.value.get(id)
e.updatedAt = r.timestamp e.updatedAt = r.timestamp
entries.value.set(id, e) entries.value.set(id, e)
}) })
delete itemCheckSyncQueue.value[i] itemCheckSyncQueue.value.splice(index,1)
}).catch((err) => { }).catch((err) => {
if (err.code === "ERR_NETWORK" || err.code === "ECONNABORTED") { if (err.code === "ERR_NETWORK" || err.code === "ECONNABORTED") {
entry['status'] = 'waiting_failed_before' entry['status'] = 'waiting_failed_before'
itemCheckSyncQueue.value[i] = entry
} else { } else {
delete itemCheckSyncQueue.value[i] itemCheckSyncQueue.value.splice(index,1)
console.error('Failed API call for entry ', entry) console.error('Failed API call for entry ', entry)
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err) useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
} }
}) })
promises.push(p) promises.push(p)
} })
// TODO verify this all settled works
Promise.allSettled(promises).finally(() => { Promise.allSettled(promises).finally(() => {
runSyncQueue(500) runSyncQueue(500)
}) })
@@ -542,6 +537,8 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
currentlyUpdating, currentlyUpdating,
getFlatEntries, getFlatEntries,
hasFailedItems, hasFailedItems,
itemCheckSyncQueue,
undoStack,
refreshFromAPI, refreshFromAPI,
autoSync, autoSync,
createObject, createObject,