mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-04 05:39:00 -05:00
several shopping fixes
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user