mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 02:03:35 -04:00 
			
		
		
		
	feat: Group Shopping List Items By Food (#3471)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
		| @@ -279,6 +279,7 @@ export default defineComponent({ | |||||||
|     const edit = ref(false); |     const edit = ref(false); | ||||||
|     const reorderLabelsDialog = ref(false); |     const reorderLabelsDialog = ref(false); | ||||||
|     const settingsDialog = ref(false); |     const settingsDialog = ref(false); | ||||||
|  |     const preserveItemOrder = ref(false); | ||||||
|  |  | ||||||
|     const route = useRoute(); |     const route = useRoute(); | ||||||
|     const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); |     const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); | ||||||
| @@ -299,8 +300,19 @@ export default defineComponent({ | |||||||
|       loadingCounter.value -= 1; |       loadingCounter.value -= 1; | ||||||
|  |  | ||||||
|       // only update the list with the new value if we're not loading, to prevent UI jitter |       // only update the list with the new value if we're not loading, to prevent UI jitter | ||||||
|       if (!loadingCounter.value) { |       if (loadingCounter.value) { | ||||||
|         shoppingList.value = newListValue; |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       shoppingList.value = newListValue; | ||||||
|  |       updateListItemOrder(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function updateListItemOrder() { | ||||||
|  |       if (!preserveItemOrder.value) { | ||||||
|  |         groupAndSortListItemsByFood(); | ||||||
|  |         updateItemsByLabel(); | ||||||
|  |       } else { | ||||||
|         sortListItems(); |         sortListItems(); | ||||||
|         updateItemsByLabel(); |         updateItemsByLabel(); | ||||||
|       } |       } | ||||||
| @@ -515,7 +527,7 @@ export default defineComponent({ | |||||||
|  |  | ||||||
|       // setting this doesn't have any effect on the data since it's refreshed automatically, but it makes the ux feel smoother |       // setting this doesn't have any effect on the data since it's refreshed automatically, but it makes the ux feel smoother | ||||||
|       shoppingList.value.labelSettings = labelSettings; |       shoppingList.value.labelSettings = labelSettings; | ||||||
|       updateItemsByLabel(); |       updateListItemOrder(); | ||||||
|  |  | ||||||
|       loadingCounter.value += 1; |       loadingCounter.value += 1; | ||||||
|       const { data } = await userApi.shopping.lists.updateLabelSettings(shoppingList.value.id, labelSettings); |       const { data } = await userApi.shopping.lists.updateLabelSettings(shoppingList.value.id, labelSettings); | ||||||
| @@ -543,12 +555,62 @@ export default defineComponent({ | |||||||
|  |  | ||||||
|     const itemsByLabel = ref<{ [key: string]: ShoppingListItemOut[] }>({}); |     const itemsByLabel = ref<{ [key: string]: ShoppingListItemOut[] }>({}); | ||||||
|  |  | ||||||
|  |     interface ListItemGroup { | ||||||
|  |       position: number; | ||||||
|  |       createdAt: string; | ||||||
|  |       items: ShoppingListItemOut[]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function groupAndSortListItemsByFood() { | ||||||
|  |       if (!shoppingList.value?.listItems?.length) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       const checkedItemKey = "__checkedItem" | ||||||
|  |       const listItemGroupsMap = new Map<string, ListItemGroup>(); | ||||||
|  |       listItemGroupsMap.set(checkedItemKey, {position: Number.MAX_SAFE_INTEGER, createdAt: "", items: []}); | ||||||
|  |  | ||||||
|  |       // group items by checked status, food, or note | ||||||
|  |       shoppingList.value.listItems.forEach((item) => { | ||||||
|  |         const key = item.checked ? checkedItemKey : item.isFood && item.food?.name | ||||||
|  |           ? item.food.name | ||||||
|  |           : item.note || "" | ||||||
|  |  | ||||||
|  |         const group = listItemGroupsMap.get(key); | ||||||
|  |         if (!group) { | ||||||
|  |           listItemGroupsMap.set(key, {position: item.position || 0, createdAt: item.createdAt || "", items: [item]}); | ||||||
|  |         } else { | ||||||
|  |           group.items.push(item); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       // sort group items by position ascending, then createdAt descending | ||||||
|  |       const listItemGroups = Array.from(listItemGroupsMap.values()); | ||||||
|  |       listItemGroups.sort((a, b) => (a.position > b.position || a.createdAt < b.createdAt ? 1 : -1)); | ||||||
|  |  | ||||||
|  |       // sort group items by position ascending, then createdAt descending, and aggregate them | ||||||
|  |       const sortedItems: ShoppingListItemOut[] = []; | ||||||
|  |       let nextPosition = 0; | ||||||
|  |       listItemGroups.forEach((listItemGroup) => { | ||||||
|  |         // @ts-ignore none of these fields are undefined | ||||||
|  |         listItemGroup.items.sort((a, b) => (a.position > b.position || a.createdAt < b.createdAt ? 1 : -1)); | ||||||
|  |         listItemGroup.items.forEach((item) => { | ||||||
|  |           item.position = nextPosition; | ||||||
|  |           nextPosition += 1; | ||||||
|  |           sortedItems.push(item); | ||||||
|  |         }) | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       shoppingList.value.listItems = sortedItems; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     function sortListItems() { |     function sortListItems() { | ||||||
|       if (!shoppingList.value?.listItems?.length) { |       if (!shoppingList.value?.listItems?.length) { | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       // sort by position ascending, then createdAt descending |       // sort by position ascending, then createdAt descending | ||||||
|  |       // @ts-ignore none of these fields are undefined | ||||||
|       shoppingList.value.listItems.sort((a, b) => (a.position > b.position || a.createdAt < b.createdAt ? 1 : -1)) |       shoppingList.value.listItems.sort((a, b) => (a.position > b.position || a.createdAt < b.createdAt ? 1 : -1)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -682,8 +744,7 @@ export default defineComponent({ | |||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       sortListItems(); |       updateListItemOrder(); | ||||||
|       updateItemsByLabel(); |  | ||||||
|  |  | ||||||
|       loadingCounter.value += 1; |       loadingCounter.value += 1; | ||||||
|       const { data } = await userApi.shopping.items.updateOne(item.id, item); |       const { data } = await userApi.shopping.items.updateOne(item.id, item); | ||||||
| @@ -759,6 +820,9 @@ export default defineComponent({ | |||||||
|         shoppingList.value.listItems = uncheckedItems.concat(listItems.value.checked); |         shoppingList.value.listItems = uncheckedItems.concat(listItems.value.checked); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       // since the user has manually reordered the list, we should preserve this order | ||||||
|  |       preserveItemOrder.value = true; | ||||||
|  |  | ||||||
|       updateListItems(); |       updateListItems(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -776,6 +840,9 @@ export default defineComponent({ | |||||||
|         allUncheckedItems.push(...itemsByLabel.value[labelName]); |         allUncheckedItems.push(...itemsByLabel.value[labelName]); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       // since the user has manually reordered the list, we should preserve this order | ||||||
|  |       preserveItemOrder.value = true; | ||||||
|  |  | ||||||
|       // save changes |       // save changes | ||||||
|       return updateIndexUnchecked(allUncheckedItems); |       return updateIndexUnchecked(allUncheckedItems); | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user