feat: Group Shopping List Items By Food (#3471)

Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
Michael Genson 2024-04-19 06:00:40 -05:00 committed by GitHub
parent b38c19ce71
commit 741d37f59e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -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);
} }