From 52d2a88b61a6ef8513560e8810bce6fa0867f0a6 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Wed, 27 Nov 2024 21:00:10 +0100 Subject: [PATCH] fix: moving categories up and down is not working (#1542) --- .../category/actions/move-category.ts | 54 +++++++++++++ .../actions/test/move-category.spec.ts | 76 +++++++++++++++++++ .../sections/category/category-actions.ts | 55 +------------- 3 files changed, 134 insertions(+), 51 deletions(-) create mode 100644 apps/nextjs/src/components/board/sections/category/actions/move-category.ts create mode 100644 apps/nextjs/src/components/board/sections/category/actions/test/move-category.spec.ts diff --git a/apps/nextjs/src/components/board/sections/category/actions/move-category.ts b/apps/nextjs/src/components/board/sections/category/actions/move-category.ts new file mode 100644 index 000000000..f0cdb1d46 --- /dev/null +++ b/apps/nextjs/src/components/board/sections/category/actions/move-category.ts @@ -0,0 +1,54 @@ +import type { RouterOutputs } from "@homarr/api"; + +import type { CategorySection } from "~/app/[locale]/boards/_types"; + +export interface MoveCategoryInput { + id: string; + direction: "up" | "down"; +} + +export const moveCategoryCallback = + (input: MoveCategoryInput) => + (previous: RouterOutputs["board"]["getHomeBoard"]): RouterOutputs["board"]["getHomeBoard"] => { + const currentCategory = previous.sections.find( + (section): section is CategorySection => section.kind === "category" && section.id === input.id, + ); + if (!currentCategory) { + return previous; + } + if (currentCategory.yOffset === 1 && input.direction === "up") { + return previous; + } + if (currentCategory.yOffset === previous.sections.length - 2 && input.direction === "down") { + return previous; + } + + return { + ...previous, + sections: previous.sections.map((section) => { + if (section.kind !== "category" && section.kind !== "empty") { + return section; + } + const offset = input.direction === "up" ? -2 : 2; + // Move category and empty section + if (section.yOffset === currentCategory.yOffset || section.yOffset === currentCategory.yOffset + 1) { + return { + ...section, + yOffset: section.yOffset + offset, + }; + } + + if ( + section.yOffset === currentCategory.yOffset + offset || + section.yOffset === currentCategory.yOffset + offset + 1 + ) { + return { + ...section, + yOffset: section.yOffset - offset, + }; + } + + return section; + }), + }; + }; diff --git a/apps/nextjs/src/components/board/sections/category/actions/test/move-category.spec.ts b/apps/nextjs/src/components/board/sections/category/actions/test/move-category.spec.ts new file mode 100644 index 000000000..bc843065f --- /dev/null +++ b/apps/nextjs/src/components/board/sections/category/actions/test/move-category.spec.ts @@ -0,0 +1,76 @@ +import { describe, expect, test } from "vitest"; + +import type { Section } from "~/app/[locale]/boards/_types"; +import { moveCategoryCallback } from "../move-category"; + +describe("Move Category", () => { + test.each([ + [3, [0, 3, 4, 1, 2, 5, 6]], + [5, [0, 1, 2, 5, 6, 3, 4]], + ])("should move category up", (moveId, expectedOrder) => { + const sections = createSections(3); + + const input = { + id: moveId.toString(), + direction: "up" as const, + }; + + const result = moveCategoryCallback(input)({ sections } as never); + + expect(sortSections(result.sections).map((section) => parseInt(section.id, 10))).toEqual(expectedOrder); + }); + test.each([ + [1, [0, 3, 4, 1, 2, 5, 6]], + [3, [0, 1, 2, 5, 6, 3, 4]], + ])("should move category down", (moveId, expectedOrder) => { + const sections = createSections(3); + + const input = { + id: moveId.toString(), + direction: "down" as const, + }; + + const result = moveCategoryCallback(input)({ sections } as never); + + expect(sortSections(result.sections).map((section) => parseInt(section.id, 10))).toEqual(expectedOrder); + }); + test("should not move category up if it is at the top", () => { + const sections = createSections(3); + + const input = { + id: "1", + direction: "up" as const, + }; + + const result = moveCategoryCallback(input)({ sections } as never); + + expect(sortSections(result.sections).map((section) => parseInt(section.id, 10))).toEqual([0, 1, 2, 3, 4, 5, 6]); + }); + test("should not move category down if it is at the bottom", () => { + const sections = createSections(3); + + const input = { + id: "5", + direction: "down" as const, + }; + + const result = moveCategoryCallback(input)({ sections } as never); + + expect(sortSections(result.sections).map((section) => parseInt(section.id, 10))).toEqual([0, 1, 2, 3, 4, 5, 6]); + }); +}); + +const createSections = (categoryCount: number) => { + return Array.from({ length: categoryCount * 2 + 1 }, (_, index) => ({ + id: index.toString(), + kind: index % 2 === 1 ? ("category" as const) : ("empty" as const), + name: `Category ${index}`, + yOffset: index, + xOffset: 0, + items: [], + })) satisfies Section[]; +}; + +const sortSections = (sections: Section[]) => { + return sections.sort((sectionA, sectionB) => sectionA.yOffset - sectionB.yOffset); +}; diff --git a/apps/nextjs/src/components/board/sections/category/category-actions.ts b/apps/nextjs/src/components/board/sections/category/category-actions.ts index a31d8306e..01e54a2b8 100644 --- a/apps/nextjs/src/components/board/sections/category/category-actions.ts +++ b/apps/nextjs/src/components/board/sections/category/category-actions.ts @@ -4,6 +4,8 @@ import { createId } from "@homarr/db/client"; import type { CategorySection, EmptySection, Section } from "~/app/[locale]/boards/_types"; import { useUpdateBoard } from "~/app/[locale]/boards/(content)/_client"; +import type { MoveCategoryInput } from "./actions/move-category"; +import { moveCategoryCallback } from "./actions/move-category"; interface AddCategory { name: string; @@ -15,11 +17,6 @@ interface RenameCategory { name: string; } -interface MoveCategory { - id: string; - direction: "up" | "down"; -} - interface RemoveCategory { id: string; } @@ -128,52 +125,8 @@ export const useCategoryActions = () => { ); const moveCategory = useCallback( - ({ id, direction }: MoveCategory) => { - updateBoard((previous) => { - const currentCategory = previous.sections.find( - (section): section is CategorySection => section.kind === "category" && section.id === id, - ); - if (!currentCategory) return previous; - if (currentCategory.yOffset === 1 && direction === "up") return previous; - if (currentCategory.yOffset === previous.sections.length - 2 && direction === "down") return previous; - - return { - ...previous, - sections: previous.sections.map((section) => { - if (section.kind !== "category" && section.kind !== "empty") return section; - const offset = direction === "up" ? -2 : 2; - // Move category and empty section - if (section.yOffset === currentCategory.yOffset || section.yOffset - 1 === currentCategory.yOffset) { - return { - ...section, - yOffset: section.yOffset + offset, - }; - } - - if ( - direction === "up" && - (section.yOffset === currentCategory.yOffset - 2 || section.yOffset === currentCategory.yOffset - 1) - ) { - return { - ...section, - position: section.yOffset + 2, - }; - } - - if ( - direction === "down" && - (section.yOffset === currentCategory.yOffset + 2 || section.yOffset === currentCategory.yOffset + 3) - ) { - return { - ...section, - position: section.yOffset - 2, - }; - } - - return section; - }), - }; - }); + (input: MoveCategoryInput) => { + updateBoard(moveCategoryCallback(input)); }, [updateBoard], );