diff --git a/web/app/dashboard/tags/page.tsx b/web/app/dashboard/tags/page.tsx index 75d0d69..0e96274 100644 --- a/web/app/dashboard/tags/page.tsx +++ b/web/app/dashboard/tags/page.tsx @@ -64,6 +64,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Input } from "@/components/ui/input" +import { updateTagTreeStatus } from "@/lib/tag-tree" import { Table, TableBody, @@ -443,7 +444,10 @@ export default function DashboardTagsPage() { const nextStatus = item.status === 0 ? 1 : 0 await updateTagStatus(item.id, nextStatus) toast.success(t(nextStatus === 0 ? "tag.enabled" : "tag.disabled", { name: item.name })) - await loadData() + setTree((prev) => updateTagTreeStatus(prev, item.id, nextStatus)) + setAllTags((prev) => + prev.map((tag) => (tag.id === item.id ? { ...tag, status: nextStatus } : tag)) + ) } catch (error) { toast.error(error instanceof Error ? error.message : t("tag.statusUpdateFailed")) } finally { diff --git a/web/lib/tag-tree.test.mjs b/web/lib/tag-tree.test.mjs index f82036e..d105d23 100644 --- a/web/lib/tag-tree.test.mjs +++ b/web/lib/tag-tree.test.mjs @@ -102,3 +102,14 @@ test("flattens only visible branches when a parent is collapsed", async () => { JSON.stringify([1, 3]) ) }) + +test("updates a tag status in tree data without mutating the original tree", async () => { + const { updateTagTreeStatus } = await loadModule() + + const nextTags = updateTagTreeStatus(tags, 2, 1) + + assert.equal(tags[0].children[0].status, 0) + assert.equal(nextTags[0].children[0].status, 1) + assert.equal(nextTags[0].status, 0) + assert.equal(nextTags[1], tags[1]) +}) diff --git a/web/lib/tag-tree.ts b/web/lib/tag-tree.ts index ab51607..8a39cf6 100644 --- a/web/lib/tag-tree.ts +++ b/web/lib/tag-tree.ts @@ -87,3 +87,40 @@ export function flattenVisibleTagTree( walk(nodes, 0, "") return result } + +type MutableTagTreeLike = { + id: number + status: number + children: T[] +} + +export function updateTagTreeStatus>( + nodes: T[] | null | undefined, + id: number, + status: number +): T[] { + const safeNodes = Array.isArray(nodes) ? nodes : [] + + function walk(items: T[]): { nodes: T[]; changed: boolean } { + let changed = false + const nextNodes = items.map((item) => { + const nextChildren = walk(item.children) + const statusChanged = item.id === id && item.status !== status + + if (!statusChanged && !nextChildren.changed) { + return item + } + + changed = true + return { + ...item, + status: statusChanged ? status : item.status, + children: nextChildren.nodes, + } + }) + + return { nodes: changed ? nextNodes : items, changed } + } + + return walk(safeNodes).nodes +}