mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-23 18:29:23 -05:00
update shopping performance
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<v-list-item class="swipe-container border-t-sm mt-0 mb-0 pt-0 pb-0 pe-0 pa-0 shopping-border" :id="itemContainerId" @touchend="handleSwipe()" @click="dialog = true;"
|
||||
v-if="isShoppingListFoodVisible(props.shoppingListFood, useUserPreferenceStore().deviceSettings)"
|
||||
<v-list-item class="swipe-container border-t-sm mt-0 mb-0 pt-0 pb-0 pe-0 pa-0 shopping-border" :id="itemContainerId" @touchend="handleSwipe()" @click="dialog = true;"
|
||||
|
||||
>
|
||||
<!-- <div class="swipe-action" :class="{'bg-success': !isChecked , 'bg-warning': isChecked }">-->
|
||||
<!-- <i class="swipe-icon fa-fw fas" :class="{'fa-check': !isChecked , 'fa-cart-plus': isChecked }"></i>-->
|
||||
@@ -10,8 +10,8 @@
|
||||
<span :style="{background: sl.color}" v-for="sl in shoppingList"></span>
|
||||
</div>
|
||||
|
||||
<div class="flex-grow-1 p-2" >
|
||||
<div class="d-flex" >
|
||||
<div class="flex-grow-1 p-2">
|
||||
<div class="d-flex">
|
||||
<div class="d-flex flex-column pr-2 pl-4">
|
||||
<span v-for="a in amounts" v-bind:key="a.key">
|
||||
<span>
|
||||
@@ -59,7 +59,7 @@ import {computed, PropType, ref} from "vue";
|
||||
import {DateTime} from "luxon";
|
||||
import {useShoppingStore} from "@/stores/ShoppingStore.js";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.js";
|
||||
import {ApiApi, Food, ShoppingListEntry} from '@/openapi'
|
||||
import {ApiApi, Food, ShoppingList, ShoppingListEntry} from '@/openapi'
|
||||
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
import {IShoppingListFood, ShoppingLineAmount} from "@/types/Shopping";
|
||||
import {isDelayed, isEntryVisible, isShoppingListFoodDelayed, isShoppingListFoodVisible} from "@/utils/logic_utils";
|
||||
@@ -86,9 +86,7 @@ const entries = computed(() => {
|
||||
*/
|
||||
const itemContainerId = computed(() => {
|
||||
let id = 'id_sli_'
|
||||
for (let i in entries.value) {
|
||||
id += i + '_'
|
||||
}
|
||||
entries.value.forEach(e => id += e.id + '_')
|
||||
return id
|
||||
})
|
||||
|
||||
@@ -117,13 +115,18 @@ const actionButtonIcon = computed(() => {
|
||||
|
||||
|
||||
const shoppingList = computed(() => {
|
||||
const lists = new Set()
|
||||
for (let entry of entries.value) {
|
||||
if (entry.shoppingLists) {
|
||||
entry.shoppingLists.forEach(l => lists.add(l))
|
||||
const lists = [] as ShoppingList[]
|
||||
entries.value.forEach(e => {
|
||||
if (e.shoppingLists) {
|
||||
e.shoppingLists.forEach(l => {
|
||||
if (lists.findIndex(sl => sl.id == l.id) == -1) {
|
||||
lists.push(l)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return Array.from(lists)
|
||||
})
|
||||
|
||||
return lists
|
||||
})
|
||||
|
||||
|
||||
@@ -138,34 +141,34 @@ const amounts = computed((): ShoppingLineAmount[] => {
|
||||
for (let i in entries.value) {
|
||||
let e = entries.value[i]
|
||||
|
||||
if (isEntryVisible(e, useUserPreferenceStore().deviceSettings)) {
|
||||
let unit = -1
|
||||
if (e.unit !== undefined && e.unit !== null) {
|
||||
unit = e.unit.id!
|
||||
}
|
||||
|
||||
if (e.amount > 0) {
|
||||
let unit = -1
|
||||
if (e.unit !== undefined && e.unit !== null) {
|
||||
unit = e.unit.id!
|
||||
}
|
||||
|
||||
let uaMerged = false
|
||||
unitAmounts.forEach(ua => {
|
||||
if (((ua.unit == null && e.unit == null) || (ua.unit != null && ua.unit.id! == unit)) && ua.checked == e.checked && ua.delayed == isDelayed(e)) {
|
||||
ua.amount += e.amount
|
||||
uaMerged = true
|
||||
}
|
||||
})
|
||||
if (e.amount > 0) {
|
||||
|
||||
if (!uaMerged) {
|
||||
unitAmounts.push({
|
||||
key: `${unit}_${e.checked}_${isDelayed(e)}`,
|
||||
amount: e.amount,
|
||||
unit: e.unit,
|
||||
checked: e.checked,
|
||||
delayed: isDelayed(e)
|
||||
} as ShoppingLineAmount)
|
||||
let uaMerged = false
|
||||
unitAmounts.forEach(ua => {
|
||||
if (((ua.unit == null && e.unit == null) || (ua.unit != null && ua.unit.id! == unit)) && ua.checked == e.checked && ua.delayed == isDelayed(e)) {
|
||||
ua.amount += e.amount
|
||||
uaMerged = true
|
||||
}
|
||||
})
|
||||
|
||||
if (!uaMerged) {
|
||||
unitAmounts.push({
|
||||
key: `${unit}_${e.checked}_${isDelayed(e)}`,
|
||||
amount: e.amount,
|
||||
unit: e.unit,
|
||||
checked: e.checked,
|
||||
delayed: isDelayed(e)
|
||||
} as ShoppingLineAmount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unitAmounts
|
||||
})
|
||||
|
||||
@@ -186,29 +189,28 @@ const infoRow = computed(() => {
|
||||
for (let i in entries.value) {
|
||||
let e = entries.value[i]
|
||||
|
||||
if (isEntryVisible(e, useUserPreferenceStore().deviceSettings)) {
|
||||
|
||||
if (authors.indexOf(e.createdBy.displayName) === -1) {
|
||||
authors.push(e.createdBy.displayName)
|
||||
}
|
||||
|
||||
if (e.listRecipe != null) {
|
||||
if (e.listRecipeData.recipe != null) {
|
||||
let recipe_name = e.listRecipeData.recipeData.name
|
||||
if (recipes.indexOf(recipe_name) === -1) {
|
||||
recipes.push(recipe_name.substring(0, 14) + (recipe_name.length > 14 ? '..' : ''))
|
||||
}
|
||||
}
|
||||
|
||||
if (e.listRecipeData.mealplan != null) {
|
||||
let meal_plan_entry = (e.listRecipeData.mealPlanData.mealType.name.substring(0, 8) || '') + (e.listRecipeData.mealPlanData.mealType.name.length > 8 ? '..' : '') + ' (' + DateTime.fromJSDate(e.listRecipeData.mealPlanData.fromDate).toLocaleString(DateTime.DATE_SHORT) + ')'
|
||||
if (meal_pans.indexOf(meal_plan_entry) === -1) {
|
||||
meal_pans.push(meal_plan_entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (authors.indexOf(e.createdBy.displayName) === -1) {
|
||||
authors.push(e.createdBy.displayName)
|
||||
}
|
||||
|
||||
if (e.listRecipe != null) {
|
||||
if (e.listRecipeData.recipe != null) {
|
||||
let recipe_name = e.listRecipeData.recipeData.name
|
||||
if (recipes.indexOf(recipe_name) === -1) {
|
||||
recipes.push(recipe_name.substring(0, 14) + (recipe_name.length > 14 ? '..' : ''))
|
||||
}
|
||||
}
|
||||
|
||||
if (e.listRecipeData.mealplan != null) {
|
||||
let meal_plan_entry = (e.listRecipeData.mealPlanData.mealType.name.substring(0, 8) || '') + (e.listRecipeData.mealPlanData.mealType.name.length > 8 ? '..' : '') + ' (' + DateTime.fromJSDate(e.listRecipeData.mealPlanData.fromDate).toLocaleString(DateTime.DATE_SHORT) + ')'
|
||||
if (meal_pans.indexOf(meal_plan_entry) === -1) {
|
||||
meal_pans.push(meal_plan_entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (useUserPreferenceStore().deviceSettings.shopping_item_info_created_by && authors.length > 0) {
|
||||
@@ -266,18 +268,18 @@ function handleSwipe() {
|
||||
|
||||
/* 2. Container to wrap the color bars and place them to the far left */
|
||||
.color-marker-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 3px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 3px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.color-marker-container span {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<v-tabs v-model="currentTab">
|
||||
<v-tab value="shopping"><i class="fas fa-fw"
|
||||
:class="{'fa-circle-notch fa-spin':useShoppingStore().currentlyUpdating, 'fa-shopping-cart ': !useShoppingStore().currentlyUpdating}"></i> <span
|
||||
class="d-none d-md-block ms-1">{{ $t('Shopping_list') }} ({{ useShoppingStore().stats.countUnchecked }})</span></v-tab>
|
||||
class="d-none d-md-block ms-1">{{ $t('Shopping_list') }} ({{ useShoppingStore().totalFoods }})</span></v-tab>
|
||||
<v-tab value="recipes"><i class="fas fa-book fa-fw"></i> <span class="d-none d-md-block ms-1">{{
|
||||
$t('Recipes')
|
||||
}} ({{ useShoppingStore().getAssociatedRecipes().length }})</span></v-tab>
|
||||
@@ -153,7 +153,7 @@
|
||||
</v-list>
|
||||
<v-list class="mt-3" density="compact" v-else>
|
||||
<template v-for="category in useShoppingStore().getEntriesByGroup" :key="category.name">
|
||||
<template v-if="isShoppingCategoryVisible(category)">
|
||||
|
||||
|
||||
<v-list-subheader v-if="category.name === useShoppingStore().UNDEFINED_CATEGORY"><i>{{ $t('NoCategory') }}</i></v-list-subheader>
|
||||
<v-list-subheader v-else>{{ category.name }}</v-list-subheader>
|
||||
@@ -163,7 +163,6 @@
|
||||
<shopping-line-item :shopping-list-food="value"></shopping-line-item>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</template>
|
||||
</v-list>
|
||||
|
||||
|
||||
@@ -25,14 +25,6 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
|
||||
let supermarketCategories = ref([] as SupermarketCategory[])
|
||||
let supermarkets = ref([] as Supermarket[])
|
||||
|
||||
let stats = ref({
|
||||
countChecked: 0,
|
||||
countUnchecked: 0,
|
||||
countCheckedFood: 0,
|
||||
countUncheckedFood: 0,
|
||||
countUncheckedDelayed: 0,
|
||||
} as ShoppingListStats)
|
||||
|
||||
// internal
|
||||
let currentlyUpdating = ref(false)
|
||||
let initialized = ref(false)
|
||||
@@ -44,26 +36,21 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
|
||||
let undoStack = ref([] as ShoppingOperationHistoryEntry[])
|
||||
let queueTimeoutId = ref(-1)
|
||||
let itemCheckSyncQueue = ref([] as IShoppingSyncQueueEntry[])
|
||||
let syncQueueRunning = ref(false)
|
||||
|
||||
/**
|
||||
* build a multi-level data structure ready for display from shopping list entries
|
||||
* group by selected grouping key
|
||||
*/
|
||||
const getEntriesByGroup = computed(() => {
|
||||
console.log("--> getEntriesByGroup called")
|
||||
stats.value = {
|
||||
countChecked: 0,
|
||||
countUnchecked: 0,
|
||||
countCheckedFood: 0,
|
||||
countUncheckedFood: 0,
|
||||
countUncheckedDelayed: 0,
|
||||
} as ShoppingListStats
|
||||
|
||||
console.log('-> getEntriesByGroup called')
|
||||
let structure = {} as IShoppingList
|
||||
structure.categories = new Map<string, IShoppingListCategory>
|
||||
|
||||
if (useUserPreferenceStore().deviceSettings.shopping_selected_grouping === ShoppingGroupingOptions.CATEGORY && useUserPreferenceStore().deviceSettings.shopping_selected_supermarket != null) {
|
||||
useUserPreferenceStore().deviceSettings.shopping_selected_supermarket.categoryToSupermarket.forEach(cTS => {
|
||||
const deviceSettings = useUserPreferenceStore().deviceSettings
|
||||
|
||||
if (deviceSettings.shopping_selected_grouping === ShoppingGroupingOptions.CATEGORY && deviceSettings.shopping_selected_supermarket != null) {
|
||||
deviceSettings.shopping_selected_supermarket.categoryToSupermarket.forEach(cTS => {
|
||||
structure.categories.set(cTS.category.name, {'name': cTS.category.name, 'foods': new Map<number, IShoppingListFood>} as IShoppingListCategory)
|
||||
})
|
||||
}
|
||||
@@ -72,60 +59,50 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
|
||||
|
||||
// build structure
|
||||
entries.value.forEach(shoppingListEntry => {
|
||||
structure = updateEntryInStructure(structure, shoppingListEntry)
|
||||
})
|
||||
|
||||
// statistics for UI conditions and display
|
||||
structure.categories.forEach(category => {
|
||||
let categoryStats = {
|
||||
countChecked: 0,
|
||||
countUnchecked: 0,
|
||||
countCheckedFood: 0,
|
||||
countUncheckedFood: 0,
|
||||
countUncheckedDelayed: 0,
|
||||
} as ShoppingListStats
|
||||
|
||||
category.foods.forEach(food => {
|
||||
let food_checked = true
|
||||
|
||||
food.entries.forEach(entry => {
|
||||
if (entry.checked) {
|
||||
categoryStats.countChecked++
|
||||
} else {
|
||||
if (isDelayed(entry)) {
|
||||
categoryStats.countUncheckedDelayed++
|
||||
} else {
|
||||
categoryStats.countUnchecked++
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (food_checked) {
|
||||
categoryStats.countCheckedFood++
|
||||
} else {
|
||||
categoryStats.countUncheckedFood++
|
||||
}
|
||||
})
|
||||
category.stats = categoryStats
|
||||
|
||||
stats.value.countChecked += categoryStats.countChecked
|
||||
stats.value.countUnchecked += categoryStats.countUnchecked
|
||||
stats.value.countCheckedFood += categoryStats.countCheckedFood
|
||||
stats.value.countUncheckedFood += categoryStats.countUncheckedFood
|
||||
if (isEntryVisible(shoppingListEntry, deviceSettings)) {
|
||||
structure = updateEntryInStructure(structure, shoppingListEntry)
|
||||
}
|
||||
})
|
||||
|
||||
// ordering
|
||||
let undefinedCategoryGroup = structure.categories.get(UNDEFINED_CATEGORY)
|
||||
if (undefinedCategoryGroup != null) {
|
||||
totalFoods.value += undefinedCategoryGroup.foods.size
|
||||
orderedStructure.push(undefinedCategoryGroup)
|
||||
structure.categories.delete(UNDEFINED_CATEGORY)
|
||||
}
|
||||
|
||||
structure.categories.forEach(category => {
|
||||
orderedStructure.push(category)
|
||||
if (category.foods.size > 0) {
|
||||
orderedStructure.push(category)
|
||||
}
|
||||
})
|
||||
|
||||
return orderedStructure
|
||||
}, {
|
||||
onTrack(e) {
|
||||
// triggered when count.value is tracked as a dependency
|
||||
|
||||
},
|
||||
onTrigger(e) {
|
||||
// triggered when count.value is mutated
|
||||
console.log('TRIGGER', e)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* get the total number of foods in the shopping list
|
||||
* since entries are always grouped by food, it makes no sense to display the entry count anywhere
|
||||
*/
|
||||
let totalFoods = computed(() => {
|
||||
let count = 0
|
||||
if (initialized.value){
|
||||
getEntriesByGroup.value.forEach(category => {
|
||||
count += category.foods.size
|
||||
})
|
||||
}
|
||||
return count
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -176,7 +153,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
|
||||
function hasFailedItems() {
|
||||
for (let i in itemCheckSyncQueue.value) {
|
||||
if (itemCheckSyncQueue.value[i]['status'] === 'syncing_failed_before' || itemCheckSyncQueue.value[i]['status'] === 'waiting_failed_before') {
|
||||
return true
|
||||
return !syncQueueRunning.value
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -198,6 +175,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
|
||||
} else {
|
||||
// only clear local entries when not given a meal plan to not accidentally filter the shopping list
|
||||
entries.value = new Map<number, ShoppingListEntry>
|
||||
initialized.value = false
|
||||
}
|
||||
|
||||
recLoadShoppingListEntries(requestParameters)
|
||||
@@ -223,19 +201,28 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
|
||||
function recLoadShoppingListEntries(requestParameters: ApiShoppingListEntryListRequest) {
|
||||
let api = new ApiApi()
|
||||
return api.apiShoppingListEntryList(requestParameters).then((r) => {
|
||||
let promises = [] as Promise<any>[]
|
||||
let newMap = new Map<number, ShoppingListEntry>()
|
||||
r.results.forEach((e) => {
|
||||
entries.value.set(e.id!, e)
|
||||
newMap.set(e.id!, e)
|
||||
})
|
||||
// bulk assign to avoid unnecessary reactivity updates
|
||||
entries.value = new Map([...entries.value, ...newMap])
|
||||
|
||||
if (requestParameters.page == 1 && r.next) {
|
||||
while (Math.ceil(r.count / requestParameters.pageSize) > requestParameters.page) {
|
||||
requestParameters.page = requestParameters.page + 1
|
||||
recLoadShoppingListEntries(requestParameters)
|
||||
if (requestParameters.page == 1) {
|
||||
if (r.next) {
|
||||
while (Math.ceil(r.count / requestParameters.pageSize) > requestParameters.page) {
|
||||
requestParameters.page = requestParameters.page + 1
|
||||
promises.push(recLoadShoppingListEntries(requestParameters))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentlyUpdating.value = false
|
||||
initialized.value = true
|
||||
|
||||
Promise.allSettled(promises).then(() => {
|
||||
currentlyUpdating.value = false
|
||||
initialized.value = true
|
||||
})
|
||||
}
|
||||
|
||||
}).catch((err) => {
|
||||
currentlyUpdating.value = false
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
@@ -413,15 +400,18 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
|
||||
let api = new ApiApi()
|
||||
let promises: Promise<void>[] = []
|
||||
|
||||
itemCheckSyncQueue.value.forEach((entry, index) => {
|
||||
entry['status'] = ((entry['status'] === 'waiting') ? 'syncing' : 'syncing_failed_before')
|
||||
let updatedEntries = new Map<number, ShoppingListEntry>()
|
||||
|
||||
itemCheckSyncQueue.value.forEach((entry, index) => {
|
||||
entry['status'] = ((entry['status'] === 'waiting_failed_before') ? 'syncing_failed_before' : 'syncing')
|
||||
syncQueueRunning.value = true
|
||||
let p = api.apiShoppingListEntryBulkCreate({shoppingListEntryBulk: entry}, {}).then((r) => {
|
||||
entry.ids.forEach(id => {
|
||||
let e = entries.value.get(id)
|
||||
e.updatedAt = r.timestamp
|
||||
e.checked = r.checked
|
||||
entries.value.set(id, e)
|
||||
if (e) {
|
||||
e.updatedAt = r.timestamp
|
||||
updatedEntries.set(id, e)
|
||||
}
|
||||
})
|
||||
itemCheckSyncQueue.value.splice(index, 1)
|
||||
}).catch((err) => {
|
||||
@@ -436,6 +426,8 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
|
||||
})
|
||||
|
||||
Promise.allSettled(promises).finally(() => {
|
||||
entries.value = new Map([...entries.value, ...updatedEntries])
|
||||
syncQueueRunning.value = false
|
||||
if (itemCheckSyncQueue.value.length > 0) {
|
||||
runSyncQueue(500)
|
||||
}
|
||||
@@ -593,7 +585,7 @@ export const useShoppingStore = defineStore(_STORE_ID, () => {
|
||||
hasFailedItems,
|
||||
itemCheckSyncQueue,
|
||||
undoStack,
|
||||
stats,
|
||||
totalFoods,
|
||||
refreshFromAPI,
|
||||
autoSync,
|
||||
createObject,
|
||||
|
||||
Reference in New Issue
Block a user