fix: Shopping List Label Dropdown Doesn't Save Correctly (#2361)

* only update items by label on refresh

* made changes more responsive

* fast re-order items when labels are re-ordered
This commit is contained in:
Michael Genson 2023-04-25 12:46:58 -05:00 committed by GitHub
parent fe17922bb8
commit 75698c531a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 29 additions and 14 deletions

View File

@ -138,6 +138,10 @@ export default defineComponent({
}); });
const edit = ref(false); const edit = ref(false);
function toggleEdit(val = !edit.value) { function toggleEdit(val = !edit.value) {
if (edit.value === val) {
return;
}
if (val) { if (val) {
// update local copy of item with the current value // update local copy of item with the current value
localListItem.value = props.value; localListItem.value = props.value;

View File

@ -32,7 +32,7 @@
<div v-for="(value, key, idx) in itemsByLabel" :key="key" class="mb-6"> <div v-for="(value, key, idx) in itemsByLabel" :key="key" class="mb-6">
<div @click="toggleShowChecked()"> <div @click="toggleShowChecked()">
<span v-if="idx || key !== $tc('shopping-list.no-label')"> <span v-if="idx || key !== $tc('shopping-list.no-label')">
<v-icon :color="value[0].label.color"> <v-icon :color="getLabelColor(value[0])">
{{ $globals.icons.tags }} {{ $globals.icons.tags }}
</v-icon> </v-icon>
</span> </span>
@ -206,14 +206,13 @@
<script lang="ts"> <script lang="ts">
import draggable from "vuedraggable"; import draggable from "vuedraggable";
import { defineComponent, useAsync, useRoute, computed, ref, watch, onUnmounted, useContext } from "@nuxtjs/composition-api"; import { defineComponent, useRoute, computed, ref, onUnmounted, useContext } from "@nuxtjs/composition-api";
import { useIdle, useToggle } from "@vueuse/core"; import { useIdle, useToggle } from "@vueuse/core";
import { useCopyList } from "~/composables/use-copy"; import { useCopyList } from "~/composables/use-copy";
import { useUserApi } from "~/composables/api"; import { useUserApi } from "~/composables/api";
import { useAsyncKey } from "~/composables/use-utils";
import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue" import MultiPurposeLabelSection from "~/components/Domain/ShoppingList/MultiPurposeLabelSection.vue"
import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue"; import ShoppingListItem from "~/components/Domain/ShoppingList/ShoppingListItem.vue";
import { ShoppingListItemCreate, ShoppingListItemOut, ShoppingListMultiPurposeLabelOut } from "~/lib/api/types/group"; import { ShoppingListItemCreate, ShoppingListItemOut, ShoppingListMultiPurposeLabelOut, ShoppingListOut } from "~/lib/api/types/group";
import RecipeList from "~/components/Domain/Recipe/RecipeList.vue"; import RecipeList from "~/components/Domain/Recipe/RecipeList.vue";
import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue"; import ShoppingListItemEditor from "~/components/Domain/ShoppingList/ShoppingListItemEditor.vue";
import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store"; import { useFoodStore, useLabelStore, useUnitStore } from "~/composables/store";
@ -253,10 +252,7 @@ export default defineComponent({
// =============================================================== // ===============================================================
// Shopping List Actions // Shopping List Actions
const shoppingList = useAsync(async () => { const shoppingList = ref<ShoppingListOut | null>(null);
return await fetchShoppingList();
}, useAsyncKey());
async function fetchShoppingList() { async function fetchShoppingList() {
const { data } = await userApi.shopping.lists.getOne(id); const { data } = await userApi.shopping.lists.getOne(id);
return data; return data;
@ -270,6 +266,7 @@ export default defineComponent({
// 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; shoppingList.value = newListValue;
updateItemsByLabel();
} }
} }
@ -305,6 +302,7 @@ export default defineComponent({
// start polling // start polling
loadingCounter.value -= 1; loadingCounter.value -= 1;
const pollFrequency = 5000; const pollFrequency = 5000;
pollForChanges(); // populate initial list
let attempts = 0; let attempts = 0;
const maxAttempts = 3; const maxAttempts = 3;
@ -421,6 +419,10 @@ export default defineComponent({
const { units: allUnits } = useUnitStore(); const { units: allUnits } = useUnitStore();
const { foods: allFoods } = useFoodStore(); const { foods: allFoods } = useFoodStore();
function getLabelColor(item: ShoppingListItemOut | null) {
return item?.label?.color;
}
function sortByLabels() { function sortByLabels() {
preferences.value.viewByLabel = !preferences.value.viewByLabel; preferences.value.viewByLabel = !preferences.value.viewByLabel;
} }
@ -441,6 +443,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();
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);
@ -514,10 +517,6 @@ export default defineComponent({
itemsByLabel.value = itemsSorted; itemsByLabel.value = itemsSorted;
} }
watch(shoppingList, () => {
updateItemsByLabel();
}, {deep: true});
async function refreshLabels() { async function refreshLabels() {
const { data } = await userApi.multiPurposeLabels.getAll(); const { data } = await userApi.multiPurposeLabels.getAll();
@ -587,18 +586,29 @@ export default defineComponent({
// make sure the item is at the end of the list with the other checked items // make sure the item is at the end of the list with the other checked items
item.position = shoppingList.value.listItems.length; item.position = shoppingList.value.listItems.length;
// set a temporary updatedAt timestamp so it appears at the top of the checked items in the UI // set a temporary updatedAt timestamp prior to refresh so it appears at the top of the checked items
item.updateAt = new Date().toISOString(); item.updateAt = new Date().toISOString();
item.updateAt = item.updateAt.substring(0, item.updateAt.length-1); item.updateAt = item.updateAt.substring(0, item.updateAt.length-1);
} }
// make updates reflect immediately
if (shoppingList.value.listItems) {
shoppingList.value.listItems.forEach((oldListItem: ShoppingListItemOut, idx: number) => {
if (oldListItem.id === item.id && shoppingList.value?.listItems) {
shoppingList.value.listItems[idx] = item;
}
});
}
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);
loadingCounter.value -= 1; loadingCounter.value -= 1;
if (data) { if (data) {
refresh(); refresh();
} }
} }
async function deleteListItem(item: ShoppingListItemOut) { async function deleteListItem(item: ShoppingListItemOut) {
@ -728,6 +738,7 @@ export default defineComponent({
deleteChecked, deleteChecked,
deleteListItem, deleteListItem,
edit, edit,
getLabelColor,
itemsByLabel, itemsByLabel,
listItems, listItems,
listRecipes, listRecipes,