feat: 메뉴 등록 및 수정 작업
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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,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);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user