diff --git a/apps/cs/src/pages/menu/api/menu-service.ts b/apps/cs/src/pages/menu/api/menu-service.ts index 6e983a197..bb5dd45f4 100644 --- a/apps/cs/src/pages/menu/api/menu-service.ts +++ b/apps/cs/src/pages/menu/api/menu-service.ts @@ -1,6 +1,6 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; -import type { Menu, MenuFormData, MenuService } from "../model/menu-interface"; +import type { Menu, MenuService, TreeMenu } from "../model/menu-interface"; export class MenuApiService implements MenuService { public async getMenuList(siteId: string): Promise { @@ -10,9 +10,9 @@ export class MenuApiService implements MenuService { return response.data; } - public async modifyMenu(menuCd: number, data: MenuFormData): Promise { + public async modifyMenu(siteId: string, data: TreeMenu[]): Promise { const response = await client.put( - `${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`, + `${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`, data, ); return response.data; diff --git a/apps/cs/src/pages/menu/lib/utils.ts b/apps/cs/src/pages/menu/lib/utils.ts new file mode 100644 index 000000000..304feac48 --- /dev/null +++ b/apps/cs/src/pages/menu/lib/utils.ts @@ -0,0 +1,61 @@ +import type { DropOptions, NodeModel } from "@minoru/react-dnd-treeview"; +import type { Menu, TreeMenu } from "../model/menu-interface"; + +export const generateTreeData = (data: Menu[]): NodeModel[] => { + return data.reduce[]>((acc, menu) => { + const node: NodeModel = { + id: menu.menuCd ?? 0, + parent: menu.parentId ?? 0, + text: menu.menuKornNm, + droppable: true, + data: menu, + }; + + acc.push(node); + + if (menu.children?.length) { + acc.push(...generateTreeData(menu.children)); + } + + return acc; + }, []); +}; + +export const generateNewTreeData = ( + treeData: NodeModel[], + options: DropOptions, +): TreeMenu | null => { + const { dragSourceId, dragSource, dropTargetId, dropTarget } = options; + if (!dragSource?.data) return null; + + const childNodes = treeData.filter( + (node) => node.parent === dragSourceId && node.data, + ); + + const baseNode = (node: NodeModel): TreeMenu => { + if (!node.data) return {}; + return { + menuCd: node.data?.menuCd, + name: node.data?.menuKornNm, + sortSeq: node.data?.sortSeq, + icon: node.data?.iconNm, + }; + }; + + const treeMenu: TreeMenu = { + ...baseNode(dragSource), + parentId: dropTargetId === 0 ? null : (dropTargetId as number), + levelNo: (dropTarget?.data?.levelNo ?? 0) + 1, + }; + + const result: TreeMenu = { + ...treeMenu, + children: childNodes.map((child) => ({ + ...baseNode(child), + parentId: child.data?.parentId === 0 ? null : (dropTargetId as number), + levelNo: (treeMenu?.levelNo ?? 0) + 1, + })), + }; + + return result; +}; diff --git a/apps/cs/src/pages/menu/model/menu-interface.ts b/apps/cs/src/pages/menu/model/menu-interface.ts index 966cc7520..09e473d07 100644 --- a/apps/cs/src/pages/menu/model/menu-interface.ts +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -11,15 +11,21 @@ export const MENU_TYPE = { export type MenuType = (typeof MENU_TYPE)[keyof typeof MENU_TYPE]; -export interface Menu { - menuCd: number; +export interface TreeMenu { + menuCd?: number; + name?: string; + sortSeq?: number; + parentId?: number | null; + levelNo?: number; + icon?: string; + children?: TreeMenu[]; +} + +export interface Menu extends Omit { menuKornNm: string; menuEngNm: string; - parentId: number; - sortSeq: number; iconNm: string; children: Menu[]; - levelNo: number; urlPath: string; menuLnkgTypeCd: string; menuLnkgSn: number; @@ -59,5 +65,5 @@ export type MenuFormData = z.infer; export interface MenuService { getMenuList: (siteId: string) => Promise; - modifyMenu: (menuCd: number, data: MenuFormData) => Promise; + modifyMenu: (siteId: string, data: TreeMenu[]) => Promise; } diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index e677281f2..760f86c42 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -23,6 +23,7 @@ import { } from "../model/menu-interface"; import { MenuTree } from "./menu-tree"; import { MenuForm } from "./menu-form"; +import { generateTreeData } from "../lib/utils"; function MenuPage() { const [siteId, setSiteId] = useState(""); @@ -30,14 +31,7 @@ function MenuPage() { const { data: treeData } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], queryFn: () => MenuApi.getMenuList(siteId), - select: (data) => - data.map((menu) => ({ - id: menu.menuCd, - parent: menu.parentId ?? 0, - text: menu.menuKornNm, - droppable: true, - data: menu, - })), + select: (data) => generateTreeData(data), enabled: !!siteId, }); @@ -91,7 +85,7 @@ function MenuPage() {
- +
)} diff --git a/apps/cs/src/pages/menu/ui/menu-tree.tsx b/apps/cs/src/pages/menu/ui/menu-tree.tsx index 1ff88fdb3..cab0bafcc 100644 --- a/apps/cs/src/pages/menu/ui/menu-tree.tsx +++ b/apps/cs/src/pages/menu/ui/menu-tree.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { DndProvider } from "react-dnd"; -import { useMutation } from "@tanstack/react-query"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { Tree, MultiBackend, @@ -12,16 +12,26 @@ import { } from "@minoru/react-dnd-treeview"; import { cn } from "@common/components/lib"; import { MenuApi } from "../api/menu-service"; -import { - menuFormSchema, - type Menu, - type MenuFormData, -} from "../model/menu-interface"; +import type { Menu, TreeMenu } from "../model/menu-interface"; +import { generateNewTreeData } from "../lib/utils"; +import { ENDPOINT } from "@/shared/config"; + +type MenuTreeProps = { + treeData: NodeModel[]; + siteId: string; +}; + +function MenuTree({ treeData, siteId }: MenuTreeProps) { + const queryClient = useQueryClient(); -function MenuTree({ treeData }: { treeData: NodeModel[] }) { const { mutate } = useMutation({ - mutationFn: ({ menuCd, data }: { menuCd: number; data: MenuFormData }) => - MenuApi.modifyMenu(menuCd, data), + mutationFn: ({ data }: { data: TreeMenu[] }) => + MenuApi.modifyMenu(siteId, data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.CMS_SERVICE.MENUS], + }); + }, }); const [selectedNode, setSelectedNode] = useState | null>( @@ -34,20 +44,9 @@ function MenuTree({ treeData }: { treeData: NodeModel[] }) { newTree: NodeModel[], options: DropOptions, ) => { - let saveData: MenuFormData | null = null; - const dragSourceNode = findNode(options.dragSourceId) ?? options.dragSource; - const dropTargetNode = findNode(options.dropTargetId) ?? options.dropTarget; - - if (!dropTargetNode) return; - - saveData = menuFormSchema.parse({ - ...dragSourceNode?.data, - }); - console.log(saveData); - }; - - const findNode = (id?: NodeModel["id"]) => { - return id ? treeData.find((node) => node.id === id) : null; + const newTreeData = generateNewTreeData(newTree, options); + if (!newTreeData) return; + mutate({ data: [newTreeData] }); }; return ( @@ -67,7 +66,7 @@ function MenuTree({ treeData }: { treeData: NodeModel[] }) { )} onDrop={handleDrop} classes={{ - root: "", + root: "p-4", container: "flex flex-col", listItem: "", dropTarget: "!bg-[rgba(32,148,250,0.5)] rounded-sm", @@ -96,8 +95,8 @@ function MenuNode({ onToggle, onSelect, }: MenuNodeProps) { - const { droppable, id, text } = node; - const indent = depth * 24 + 5; + const { id, text, data } = node; + const indent = depth * 10 + 5; const handleToggle = () => onToggle(id); const handleSelect = () => onSelect(node); @@ -112,10 +111,10 @@ function MenuNode({ onClick={handleSelect} >
- {droppable && ( + {data?.children?.length && ( {