fix: Improved bulk deletion by reducing refreshs (#6634)

Co-authored-by: David Schinkel <david@zollsoft.de>
Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
davidschinkel
2025-12-13 05:08:04 +01:00
committed by GitHub
parent 2f8625ac44
commit ab4559319e
9 changed files with 85 additions and 27 deletions

View File

@@ -0,0 +1,58 @@
import { describe, expect, test, vi } from "vitest";
import { ref } from "vue";
import { useStoreActions } from "./use-actions-factory";
import type { BaseCRUDAPI } from "~/lib/api/base/base-clients";
describe("useStoreActions", () => {
const mockApi = {
getAll: vi.fn(),
createOne: vi.fn(),
updateOne: vi.fn(),
deleteOne: vi.fn(),
} as unknown as BaseCRUDAPI<unknown, unknown, unknown>;
const mockStore = ref([]);
const mockLoading = ref(false);
test("deleteMany calls deleteOne for each ID and refreshes once", async () => {
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
mockApi.deleteOne = vi.fn().mockResolvedValue({ response: { data: {} } });
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
const ids = ["1", "2", "3"];
await actions.deleteMany(ids);
expect(mockApi.deleteOne).toHaveBeenCalledTimes(3);
expect(mockApi.deleteOne).toHaveBeenCalledWith("1");
expect(mockApi.deleteOne).toHaveBeenCalledWith("2");
expect(mockApi.deleteOne).toHaveBeenCalledWith("3");
expect(mockApi.getAll).toHaveBeenCalledTimes(1);
});
test("deleteMany handles empty array", async () => {
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
mockApi.deleteOne = vi.fn();
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
await actions.deleteMany([]);
expect(mockApi.deleteOne).not.toHaveBeenCalled();
expect(mockApi.getAll).toHaveBeenCalledTimes(1);
});
test("deleteMany sets loading state", async () => {
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
mockApi.deleteOne = vi.fn().mockResolvedValue({});
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
const promise = actions.deleteMany(["1"]);
expect(mockLoading.value).toBe(true);
await promise;
expect(mockLoading.value).toBe(false);
});
});

View File

@@ -12,6 +12,7 @@ interface StoreActions<T extends BoundT> extends ReadOnlyStoreActions<T> {
createOne(createData: T): Promise<T | null>;
updateOne(updateData: T): Promise<T | null>;
deleteOne(id: string | number): Promise<T | null>;
deleteMany(ids: (string | number)[]): Promise<void>;
}
/**
@@ -165,11 +166,23 @@ export function useStoreActions<T extends BoundT>(
return response?.data || null;
}
async function deleteMany(ids: (string | number)[]) {
loading.value = true;
for (const id of ids) {
await api.deleteOne(id);
}
if (allRef?.value) {
await refresh();
}
loading.value = false;
}
return {
getAll,
refresh,
createOne,
updateOne,
deleteOne,
deleteMany,
};
}

View File

@@ -209,12 +209,8 @@ export default defineNuxtComponent({
}
async function deleteSelected() {
for (const item of bulkDeleteTarget.value) {
if (!item.id) {
continue;
}
await categoryStore.actions.deleteOne(item.id);
}
const ids = bulkDeleteTarget.value.map(item => item.id).filter(id => !!id);
await categoryStore.actions.deleteMany(ids);
bulkDeleteTarget.value = [];
}

View File

@@ -528,9 +528,8 @@ export default defineNuxtComponent({
}
async function deleteSelected() {
for (const item of bulkDeleteTarget.value) {
await foodStore.actions.deleteOne(item.id);
}
const ids = bulkDeleteTarget.value.map(item => item.id);
await foodStore.actions.deleteMany(ids);
bulkDeleteTarget.value = [];
}

View File

@@ -261,9 +261,8 @@ export default defineNuxtComponent({
}
async function deleteSelected() {
for (const item of bulkDeleteTarget.value) {
await labelStore.actions.deleteOne(item.id);
}
const ids = bulkDeleteTarget.value.map(item => item.id);
await labelStore.actions.deleteMany(ids);
bulkDeleteTarget.value = [];
}

View File

@@ -249,9 +249,8 @@ export default defineNuxtComponent({
}
async function deleteSelected() {
for (const item of bulkDeleteTarget.value) {
await actionStore.actions.deleteOne(item.id);
}
const ids = bulkDeleteTarget.value.map(item => item.id);
await actionStore.actions.deleteMany(ids);
bulkDeleteTarget.value = [];
}

View File

@@ -211,12 +211,8 @@ export default defineNuxtComponent({
}
async function deleteSelected() {
for (const item of bulkDeleteTarget.value) {
if (!item.id) {
continue;
}
await tagStore.actions.deleteOne(item.id);
}
const ids = bulkDeleteTarget.value.map(item => item.id).filter(id => !!id);
await tagStore.actions.deleteMany(ids);
bulkDeleteTarget.value = [];
}

View File

@@ -263,9 +263,8 @@ export default defineNuxtComponent({
}
async function deleteSelected() {
for (const item of bulkDeleteTarget.value) {
await toolStore.actions.deleteOne(item.id);
}
const ids = bulkDeleteTarget.value.map(item => item.id);
await toolStore.actions.deleteMany(ids);
bulkDeleteTarget.value = [];
}

View File

@@ -465,9 +465,8 @@ export default defineNuxtComponent({
}
async function deleteSelected() {
for (const item of bulkDeleteTarget.value) {
await unitActions.deleteOne(item.id);
}
const ids = bulkDeleteTarget.value.map(item => item.id);
await unitActions.deleteMany(ids);
bulkDeleteTarget.value = [];
}