From 92f67ce1a3f20aea8d3ff004691b34c2fd321b9a Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 25 Mar 2025 14:23:16 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=8A=B8=EB=A6=AC=20=EB=A9=94=EB=89=B4?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/eslint.config.mjs | 5 + apps/cs/src/pages/menu/api/menu-service.ts | 11 +- apps/cs/src/pages/menu/lib/utils.ts | 36 +++++- .../cs/src/pages/menu/model/menu-interface.ts | 9 +- apps/cs/src/pages/menu/ui/menu-node.tsx | 116 ++++++++++++++++++ apps/cs/src/pages/menu/ui/menu-page.tsx | 6 +- apps/cs/src/pages/menu/ui/menu-tree.tsx | 79 ++---------- 7 files changed, 179 insertions(+), 83 deletions(-) create mode 100644 apps/cs/src/pages/menu/ui/menu-node.tsx diff --git a/apps/cs/eslint.config.mjs b/apps/cs/eslint.config.mjs index c85fb67c4..9109635e9 100644 --- a/apps/cs/eslint.config.mjs +++ b/apps/cs/eslint.config.mjs @@ -11,6 +11,11 @@ const compat = new FlatCompat({ const eslintConfig = [ ...compat.extends("next/core-web-vitals", "next/typescript"), + { + rules: { + "react-hooks/exhaustive-deps": "off", + }, + }, ]; export default eslintConfig; diff --git a/apps/cs/src/pages/menu/api/menu-service.ts b/apps/cs/src/pages/menu/api/menu-service.ts index bb5dd45f4..4fe6c845a 100644 --- a/apps/cs/src/pages/menu/api/menu-service.ts +++ b/apps/cs/src/pages/menu/api/menu-service.ts @@ -3,20 +3,27 @@ import { ENDPOINT } from "@/shared/config"; import type { Menu, MenuService, TreeMenu } from "../model/menu-interface"; export class MenuApiService implements MenuService { - public async getMenuList(siteId: string): Promise { + public async getTreeMenuList(siteId: string): Promise { const response = await client.get( `${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`, ); return response.data; } - public async modifyMenu(siteId: string, data: TreeMenu[]): Promise { + public async modifyTreeMenu(siteId: string, data: TreeMenu[]): Promise { const response = await client.put( `${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`, data, ); return response.data; } + + public async modifyMenuName(menuCd: number, name: string): Promise { + const response = await client.put( + `${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}/${name}`, + ); + return response.data; + } } export const MenuApi = new MenuApiService(); diff --git a/apps/cs/src/pages/menu/lib/utils.ts b/apps/cs/src/pages/menu/lib/utils.ts index 723023d8c..a7bc514a3 100644 --- a/apps/cs/src/pages/menu/lib/utils.ts +++ b/apps/cs/src/pages/menu/lib/utils.ts @@ -44,8 +44,8 @@ export const generateNewTreeData = ( const treeMenu: TreeMenu = { ...baseNode(dragSource), parentId: dropTargetId === 0 ? null : (dropTargetId as number), - sortSeq: dropTarget?.data?.children?.length ?? 0 + 1, - levelNo: dropTarget?.data?.levelNo ?? 0 + 1, + sortSeq: (dropTarget?.data?.children?.length ?? 0) + 1, + levelNo: (dropTarget?.data?.levelNo ?? 0) + 1, }; const result: TreeMenu = { @@ -54,9 +54,39 @@ export const generateNewTreeData = ( ...baseNode(child), parentId: child.data?.parentId === 0 ? null : (dropTargetId as number), sortSeq: child.data?.sortSeq, - levelNo: treeMenu?.levelNo ?? 0 + 1, + levelNo: (treeMenu?.levelNo ?? 0) + 1, })), }; return result; }; + +export const handleSelectNode = + (callback: (node: NodeModel) => void) => (node: NodeModel) => { + callback(node); + }; + +export const handleDropNode = + (callback: (newTree: TreeMenu) => void) => + (newTree: NodeModel[], options: DropOptions) => { + const newTreeData = generateNewTreeData(newTree, options); + if (!newTreeData) return; + callback(newTreeData); + }; + +export const handleNodeTextChange = + (treeData: NodeModel[]) => + (callback: (newTree: NodeModel[]) => void) => + (id: NodeModel["id"], value: string) => { + const newTree = treeData.map((node) => { + if (node.id === id) { + return { + ...node, + text: value, + }; + } + + return node; + }); + callback(newTree); + }; diff --git a/apps/cs/src/pages/menu/model/menu-interface.ts b/apps/cs/src/pages/menu/model/menu-interface.ts index 09e473d07..fc22c462c 100644 --- a/apps/cs/src/pages/menu/model/menu-interface.ts +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -53,17 +53,18 @@ export const menuFormSchema = z.object({ MENU_TYPE.BOTTOM, ]), menuLnkgSn: z.number().nullish(), - otptYn: z.enum(["Y", "N"]).default("Y"), + otptYn: z.enum(["Y", "N"]).default("Y").nullish(), prntMenuCd: z.number().nullish(), sortSeq: z.number(), evlUseYn: z.enum(["Y", "N"]).nullish(), qrcd: z.string().nullish(), - useYn: z.enum(["Y", "N"]).default("Y"), + useYn: z.enum(["Y", "N"]).default("Y").nullish(), }); export type MenuFormData = z.infer; export interface MenuService { - getMenuList: (siteId: string) => Promise; - modifyMenu: (siteId: string, data: TreeMenu[]) => Promise; + getTreeMenuList: (siteId: string) => Promise; + modifyTreeMenu: (siteId: string, data: TreeMenu[]) => Promise; + modifyMenuName: (menuCd: number, name: string) => Promise; } diff --git a/apps/cs/src/pages/menu/ui/menu-node.tsx b/apps/cs/src/pages/menu/ui/menu-node.tsx new file mode 100644 index 000000000..f8a150633 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/menu-node.tsx @@ -0,0 +1,116 @@ +import { useState } from "react"; +import { useMutation } from "@tanstack/react-query"; +import type { NodeModel } from "@minoru/react-dnd-treeview"; +import { cn } from "@common/components/lib"; +import { Button, Input } from "@common/components/ui"; +import type { Menu } from "../model/menu-interface"; +import { MenuApi } from "../api/menu-service"; + +type MenuNodeProps = { + node: NodeModel; + depth: number; + isOpen: boolean; + isSelected: boolean; + onToggle: (id: NodeModel["id"]) => void; + onSelect: (node: NodeModel) => void; +}; + +function MenuNode({ + node, + depth, + isOpen, + isSelected, + onToggle, + onSelect, +}: MenuNodeProps) { + const { id, text, data } = node; + const indent = depth * 10 + 5; + + const { mutateAsync: modifyMenuName } = useMutation({ + mutationFn: (nodeName: string) => { + const encodedName = encodeURIComponent(nodeName); + return MenuApi.modifyMenuName(id as number, encodedName); + }, + }); + + const [editMode, setEditMode] = useState(false); + const [nodeName, setNodeName] = useState(text); + + const handleToggle = () => onToggle(id); + const handleSelect = () => onSelect(node); + + const handleSave = async () => { + await modifyMenuName(nodeName); + setEditMode(false); + }; + + return ( +
+
+ {editMode ? ( + <> + setNodeName(e.target.value)} + className="h-[2.5rem] !text-[1.3rem] text-foreground" + /> + + ) : ( + <> + {data?.children?.length && ( + + )} + {text} + + )} +
+ {editMode ? ( +
+ + +
+ ) : ( + + )} +
+ ); +} + +export { MenuNode }; diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 760f86c42..5cc63b627 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -30,7 +30,7 @@ function MenuPage() { const { data: treeData } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], - queryFn: () => MenuApi.getMenuList(siteId), + queryFn: () => MenuApi.getTreeMenuList(siteId), select: (data) => generateTreeData(data), enabled: !!siteId, }); @@ -61,9 +61,9 @@ function MenuPage() { return (
-
+