feat: 메뉴 등록 및 수정 작업

This commit is contained in:
2025-03-26 17:00:35 +09:00
parent f03b07738e
commit b26e7e3d10
7 changed files with 78 additions and 59 deletions
+11 -3
View File
@@ -5,9 +5,17 @@ import type {
MenuFormData,
MenuService,
TreeMenu,
TreeNode,
} from "../model/menu-interface";
export class MenuApiService implements MenuService {
public async getMenuInfo(menuCd: number): Promise<Menu> {
const response = await client.get<Menu>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
);
return response.data;
}
public async createMenu(data: MenuFormData): Promise<void> {
const response = await client.post<void>(
`${ENDPOINT.CMS_SERVICE.MENUS}`,
@@ -27,14 +35,14 @@ export class MenuApiService implements MenuService {
return response.data;
}
public async getTreeMenuList(siteId: string): Promise<Menu[]> {
const response = await client.get<Menu[]>(
public async getTreeMenuList(siteId: string): Promise<TreeMenu[]> {
const response = await client.get<TreeMenu[]>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
);
return response.data;
}
public async modifyTreeMenu(siteId: string, data: TreeMenu[]): Promise<void> {
public async modifyTreeMenu(siteId: string, data: TreeNode[]): Promise<void> {
const response = await client.put<void>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
data,
+18 -17
View File
@@ -1,9 +1,9 @@
import type { DropOptions, NodeModel } from "@minoru/react-dnd-treeview";
import type { Menu, TreeMenu } from "../model/menu-interface";
import type { TreeMenu, TreeNode } from "../model/menu-interface";
export const generateTreeData = (data: Menu[]): NodeModel<Menu>[] => {
return data.reduce<NodeModel<Menu>[]>((acc, menu) => {
const node: NodeModel<Menu> = {
export const generateTreeData = (data: TreeMenu[]): NodeModel<TreeMenu>[] => {
return data.reduce<NodeModel<TreeMenu>[]>((acc, menu) => {
const node: NodeModel<TreeMenu> = {
id: menu.menuCd ?? 0,
parent: menu.parentId ?? 0,
text: menu.menuKornNm,
@@ -22,9 +22,9 @@ export const generateTreeData = (data: Menu[]): NodeModel<Menu>[] => {
};
export const generateNewTreeData = (
treeData: NodeModel<Menu>[],
options: DropOptions<Menu>,
): TreeMenu | null => {
treeData: NodeModel<TreeMenu>[],
options: DropOptions<TreeMenu>,
): TreeNode | null => {
const { dragSourceId, dragSource, dropTargetId, dropTarget } = options;
if (!dragSource?.data) return null;
@@ -38,7 +38,7 @@ export const generateNewTreeData = (
(node) => node.parent === dragSourceId && node.data,
);
const baseNode = (node: NodeModel<Menu>): TreeMenu => {
const baseNode = (node: NodeModel<TreeMenu>): TreeNode => {
if (!node.data) return {};
return {
menuCd: node.data?.menuCd,
@@ -47,20 +47,20 @@ export const generateNewTreeData = (
};
};
const treeMenu: TreeMenu = {
const treeNode: TreeNode = {
...baseNode(dragSource),
parentId: dropTargetId === 0 ? null : (dropTargetId as number),
sortSeq: newSortSeq,
levelNo: (dropTarget?.data?.levelNo ?? 0) + 1,
};
const result: TreeMenu = {
...treeMenu,
const result: TreeNode = {
...treeNode,
children: childNodes.map((child) => ({
...baseNode(child),
parentId: child.data?.parentId === 0 ? null : (dropTargetId as number),
sortSeq: child.data?.sortSeq,
levelNo: (treeMenu?.levelNo ?? 0) + 1,
levelNo: (treeNode?.levelNo ?? 0) + 1,
})),
};
@@ -68,21 +68,22 @@ export const generateNewTreeData = (
};
export const handleSelectNode =
(callback: (node: NodeModel<Menu>) => void) => (node: NodeModel<Menu>) => {
(callback: (node: NodeModel<TreeMenu>) => void) =>
(node: NodeModel<TreeMenu>) => {
callback(node);
};
export const handleDropNode =
(callback: (newTree: TreeMenu) => void) =>
(newTree: NodeModel<Menu>[], options: DropOptions<Menu>) => {
(callback: (newTree: TreeNode) => void) =>
(newTree: NodeModel<TreeMenu>[], options: DropOptions<TreeMenu>) => {
const newTreeData = generateNewTreeData(newTree, options);
if (!newTreeData) return;
callback(newTreeData);
};
export const handleNodeTextChange =
(treeData: NodeModel<Menu>[]) =>
(callback: (newTree: NodeModel<Menu>[]) => void) =>
(treeData: NodeModel<TreeMenu>[]) =>
(callback: (newTree: NodeModel<TreeMenu>[]) => void) =>
(id: NodeModel["id"], value: string) => {
const newTree = treeData.map((node) => {
if (node.id === id) {
+17 -15
View File
@@ -1,31 +1,31 @@
import { z } from "zod";
export const MENU_TYPE = {
EMPTY: "EMPTY",
CONTENTS: "CONTENTS",
BOARD: "BOARD",
INSIDE: "INSIDE",
OUTSIDE: "OUTSIDE",
BOTTOM: "BOTTOM",
EMPTY: "empty",
CONTENTS: "contents",
BOARD: "board",
INSIDE: "inside",
OUTSIDE: "outside",
BOTTOM: "bottom",
} as const;
export type MenuType = (typeof MENU_TYPE)[keyof typeof MENU_TYPE];
export interface TreeMenu {
export interface TreeNode {
menuCd?: number;
name?: string;
sortSeq?: number;
parentId?: number | null;
levelNo?: number;
icon?: string;
children?: TreeMenu[];
children?: TreeNode[];
}
export interface Menu extends Omit<TreeMenu, "name" | "icon" | "children"> {
export interface TreeMenu extends Omit<TreeNode, "name" | "icon" | "children"> {
menuKornNm: string;
menuEngNm: string;
iconNm: string;
children: Menu[];
children: TreeMenu[];
urlPath: string;
menuLnkgTypeCd: string;
menuLnkgSn: number;
@@ -53,12 +53,12 @@ export const menuFormSchema = z.object({
MENU_TYPE.BOTTOM,
]),
menuLnkgSn: z.number().nullish(),
otptYn: z.string().default("Y").nullish(),
otptYn: z.string().default("Y"),
prntMenuCd: z.number().nullish(),
sortSeq: z.number(),
evlUseYn: z.string().default("Y").nullish(),
evlUseYn: z.string().default("Y"),
qrcd: z.string().nullish(),
useYn: z.string().default("Y").nullish(),
useYn: z.string().default("Y"),
menuLnkgSnNm: z.string().nullish(),
menuPicUserUnqId: z.string().nullish(),
menuPicUserUnqIdNm: z.string().nullish(),
@@ -66,14 +66,16 @@ export const menuFormSchema = z.object({
});
export type MenuFormData = z.infer<typeof menuFormSchema>;
export type Menu = MenuFormData & { menuCd: number };
export interface MenuService {
getMenuInfo: (menuCd: number) => Promise<Menu>;
createMenu: (data: MenuFormData) => Promise<void>;
modifyMenu: (
menuCd: number,
data: Omit<MenuFormData, "siteId">,
) => Promise<void>;
getTreeMenuList: (siteId: string) => Promise<Menu[]>;
modifyTreeMenu: (siteId: string, data: TreeMenu[]) => Promise<void>;
getTreeMenuList: (siteId: string) => Promise<TreeMenu[]>;
modifyTreeMenu: (siteId: string, data: TreeNode[]) => Promise<void>;
modifyMenuName: (menuCd: number, name: string) => Promise<void>;
}
+6 -6
View File
@@ -48,22 +48,22 @@ function MenuForm({
const onSubmit = async (data: MenuFormData) => {
if (isModifyMode) {
onModify(data);
await onModify(data);
} else {
onCreate(data);
await onCreate(data);
}
};
const onCreate = (data: MenuFormData) => {
handleConfirm(async () => {
const onCreate = async (data: MenuFormData) => {
await handleConfirm(async () => {
if (await confirm("저장하시겠습니까?")) {
handleCreate(data);
}
});
};
const onModify = (data: MenuFormData) => {
handleConfirm(async () => {
const onModify = async (data: MenuFormData) => {
await handleConfirm(async () => {
if (await confirm("수정하시겠습니까?")) {
handleModify({ menuCd: menuCd!, data });
}
+3 -3
View File
@@ -5,15 +5,15 @@ import { Button, Input } from "@common/components/ui";
import { cn } from "@common/components/lib";
import { ENDPOINT } from "@/shared/config";
import { MenuApi } from "../api/menu-service";
import type { Menu } from "../model/menu-interface";
import type { TreeMenu } from "../model/menu-interface";
type MenuNodeProps = {
node: NodeModel<Menu>;
node: NodeModel<TreeMenu>;
depth: number;
isOpen: boolean;
isSelected: boolean;
onToggle: (id: NodeModel["id"]) => void;
onSelect: (node: NodeModel<Menu>) => void;
onSelect: (node: NodeModel<TreeMenu>) => void;
};
function MenuNode({
+17 -9
View File
@@ -17,7 +17,7 @@ import { SiteApi } from "@/entities/site";
import { ENDPOINT } from "@/shared/config";
import { PageLayout } from "@/shared/ui/page-layout";
import { useAlertStore } from "@/shared/lib/use-alert-store";
import type { Menu, MenuType } from "../model/menu-interface";
import type { TreeMenu } from "../model/menu-interface";
import { MenuApi } from "../api/menu-service";
import {
MENU_TYPE,
@@ -33,7 +33,13 @@ function MenuPage() {
const { setMessage: alert } = useAlertStore((state) => state);
const [siteId, setSiteId] = useState("");
const [selectedMenu, setSelectedMenu] = useState<Menu | null>(null);
const [selectedMenu, setSelectedMenu] = useState<TreeMenu | null>(null);
const { data: menuInfo } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, selectedMenu?.menuCd],
queryFn: () => MenuApi.getMenuInfo(selectedMenu?.menuCd ?? 0),
enabled: !!selectedMenu?.menuCd,
});
const { data: treeData } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
@@ -91,13 +97,15 @@ function MenuPage() {
menuLnkgTypeCd: MENU_TYPE.EMPTY,
},
values: {
...selectedMenu,
...menuInfo,
siteId,
sortSeq: selectedMenu?.sortSeq ?? 1,
levelNo: selectedMenu?.levelNo ?? 1,
menuKornNm: selectedMenu?.menuKornNm ?? "",
menuLnkgTypeCd:
(selectedMenu?.menuLnkgTypeCd as MenuType) ?? MENU_TYPE.EMPTY,
sortSeq: menuInfo?.sortSeq ?? 1,
levelNo: menuInfo?.levelNo ?? 1,
menuKornNm: menuInfo?.menuKornNm ?? "",
menuLnkgTypeCd: menuInfo?.menuLnkgTypeCd ?? MENU_TYPE.EMPTY,
otptYn: menuInfo?.otptYn ?? "Y",
evlUseYn: menuInfo?.evlUseYn ?? "Y",
useYn: menuInfo?.useYn ?? "Y",
},
});
@@ -106,7 +114,7 @@ function MenuPage() {
form.setValue("siteId", value);
};
const handleNodeSelect = (node: NodeModel<Menu>) => {
const handleNodeSelect = (node: NodeModel<TreeMenu>) => {
if (!node.data) return;
setSelectedMenu(node.data);
};
+6 -6
View File
@@ -10,15 +10,15 @@ import {
} from "@minoru/react-dnd-treeview";
import { ENDPOINT } from "@/shared/config";
import { MenuApi } from "../api/menu-service";
import type { Menu, TreeMenu } from "../model/menu-interface";
import type { TreeMenu, TreeNode } from "../model/menu-interface";
import { handleDropNode, handleSelectNode } from "../lib/utils";
import { MenuNode } from "./menu-node";
type MenuTreeProps = {
treeData: NodeModel<Menu>[];
treeData: NodeModel<TreeMenu>[];
siteId: string;
selectedMenu: Menu | null;
onSelectNode: (node: NodeModel<Menu>) => void;
selectedMenu: TreeMenu | null;
onSelectNode: (node: NodeModel<TreeMenu>) => void;
};
function MenuTree({
@@ -30,7 +30,7 @@ function MenuTree({
const queryClient = useQueryClient();
const { mutate: modifyTreeMenu } = useMutation({
mutationFn: (newTree: TreeMenu) =>
mutationFn: (newTree: TreeNode) =>
MenuApi.modifyTreeMenu(siteId, [newTree]),
onSuccess: () => {
queryClient.invalidateQueries({
@@ -44,7 +44,7 @@ function MenuTree({
<Tree
tree={treeData}
rootId={0}
render={(node: NodeModel<Menu>, { depth, isOpen, onToggle }) => (
render={(node: NodeModel<TreeMenu>, { depth, isOpen, onToggle }) => (
<MenuNode
node={node}
depth={depth}