[apps/cs] refactor: 메뉴 인터페이스 및 서비스 엔티티로 분리

This commit is contained in:
2025-07-07 13:58:29 +09:00
parent d97de2861e
commit 46c3f50432
14 changed files with 194 additions and 173 deletions
+36 -1
View File
@@ -1,6 +1,11 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type { MenuItem, MenuService } from "../model/menu-interface";
import type {
MenuService,
Menu,
MenuItem,
MenuFormData,
} from "../model/menu-interface";
export class MenuApiService implements MenuService {
async getMenuRoles(siteId: string): Promise<MenuItem[]> {
@@ -9,6 +14,36 @@ export class MenuApiService implements MenuService {
);
return response.data;
}
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}`,
data,
);
return response.data;
}
public async updateMenu(
menuCd: number,
data: Omit<MenuFormData, "siteId">,
): Promise<void> {
const response = await client.put<void>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
data,
);
return response.data;
}
public async deleteMenu(menuCd: number): Promise<void> {
await client.delete(`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`);
}
}
export const MenuApi = new MenuApiService();
+8 -1
View File
@@ -1,2 +1,9 @@
export { MenuApi } from "./api/menu-service";
export {
MENU_TYPE,
menuFormSchema,
type MenuType,
type MenuItem,
type MenuFormData,
} from "./model/menu-interface";
export { MenuWrapper } from "./lib/menu-wrapper";
export type { MenuItem } from "./model/menu-interface";
@@ -1,3 +1,16 @@
import { z } from "zod";
export const MENU_TYPE = {
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 MenuItem {
menuCd: number;
menuKornNm: string;
@@ -18,6 +31,51 @@ export interface MenuItem {
requiredUrlPath: boolean;
}
export const menuFormSchema = z.object({
siteId: z.string(),
menuKornNm: z.string(),
menuSubKornNm: z.string().nullish(),
menuEngNm: z.string().nullish(),
menuSubEngNm: z.string().nullish(),
levelNo: z.number(),
urlPath: z.string().nullish(),
iconNm: z.string().nullish(),
menuLnkgTypeCd: z.enum([
MENU_TYPE.EMPTY,
MENU_TYPE.CONTENTS,
MENU_TYPE.BOARD,
MENU_TYPE.INSIDE,
MENU_TYPE.OUTSIDE,
MENU_TYPE.BOTTOM,
]),
menuLnkgSn: z.number().nullish(),
otptYn: z.string().default("Y"),
prntMenuCd: z.number().nullish(),
sortSeq: z.number(),
evlUseYn: z.string().default("Y"),
qrcd: z.string().nullish(),
useYn: z.string().default("Y"),
newWndwYn: z.string().nullish(),
menuLnkgSnNm: z.string().nullish(),
menuPicUserUnqId: z.string().nullish(),
menuPicUserUnqIdNm: z.string().nullish(),
menuPicDsgnYmd: z.string().nullish(),
});
export type MenuFormData = z.infer<typeof menuFormSchema>;
export type Menu = Omit<MenuFormData, "iconNm"> & {
menuCd: number;
iconName?: string;
};
export interface MenuService {
getMenuRoles(siteId: string): Promise<MenuItem[]>;
getMenuInfo: (menuCd: number) => Promise<Menu>;
createMenu: (data: MenuFormData) => Promise<void>;
updateMenu: (
menuCd: number,
data: Omit<MenuFormData, "siteId">,
) => Promise<void>;
deleteMenu: (menuCd: number) => Promise<void>;
}
@@ -1,65 +0,0 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
Menu,
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}`,
data,
);
return response.data;
}
public async updateMenu(
menuCd: number,
data: Omit<MenuFormData, "siteId">,
): Promise<void> {
const response = await client.put<void>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
data,
);
return response.data;
}
public async deleteMenu(menuCd: number): Promise<void> {
await client.delete(`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`);
}
public async getTreeMenuList(siteId: string): Promise<TreeMenu[]> {
const response = await client.get<TreeMenu[]>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
);
return response.data;
}
public async updateTreeMenu(siteId: string, data: TreeNode[]): Promise<void> {
const response = await client.put<void>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
data,
);
return response.data;
}
public async updateMenuName(menuCd: number, name: string): Promise<void> {
const response = await client.put<void>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}/${name}`,
);
return response.data;
}
}
export const MenuApi = new MenuApiService();
@@ -0,0 +1,33 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
TreeMenuService,
TreeMenu,
TreeNode,
} from "../model/tree-menu-interface";
export class TreeMenuApiService implements TreeMenuService {
public async getTreeMenuList(siteId: string): Promise<TreeMenu[]> {
const response = await client.get<TreeMenu[]>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
);
return response.data;
}
public async updateTreeMenu(siteId: string, data: TreeNode[]): Promise<void> {
const response = await client.put<void>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
data,
);
return response.data;
}
public async updateMenuName(menuCd: number, name: string): Promise<void> {
const response = await client.put<void>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}/${name}`,
);
return response.data;
}
}
export const TreeMenuApi = new TreeMenuApiService();
+1 -1
View File
@@ -3,7 +3,7 @@ import type {
TreeUserOrganization,
TreeUser,
} from "@/entities/user-organization";
import type { TreeMenu, TreeNode } from "../model/menu-interface";
import type { TreeMenu, TreeNode } from "../model/tree-menu-interface";
export const generateMenuTreeData = (
data: TreeMenu[],
@@ -1,86 +0,0 @@
import { z } from "zod";
export const MENU_TYPE = {
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 TreeNode {
menuCd?: number;
name?: string;
sortSeq?: number;
parentId?: number | null;
levelNo?: number;
icon?: string;
children?: TreeNode[];
}
export interface TreeMenu extends Omit<TreeNode, "name" | "icon" | "children"> {
menuKornNm: string;
menuEngNm?: string;
iconNm?: string;
children?: TreeMenu[];
urlPath?: string;
menuLnkgTypeCd: string;
menuLnkgSn?: number;
qrcd?: string;
useYn: string;
otptYn: string;
requiredUrlPath?: boolean;
}
export const menuFormSchema = z.object({
siteId: z.string(),
menuKornNm: z.string(),
menuSubKornNm: z.string().nullish(),
menuEngNm: z.string().nullish(),
menuSubEngNm: z.string().nullish(),
levelNo: z.number(),
urlPath: z.string().nullish(),
iconNm: z.string().nullish(),
menuLnkgTypeCd: z.enum([
MENU_TYPE.EMPTY,
MENU_TYPE.CONTENTS,
MENU_TYPE.BOARD,
MENU_TYPE.INSIDE,
MENU_TYPE.OUTSIDE,
MENU_TYPE.BOTTOM,
]),
menuLnkgSn: z.number().nullish(),
otptYn: z.string().default("Y"),
prntMenuCd: z.number().nullish(),
sortSeq: z.number(),
evlUseYn: z.string().default("Y"),
qrcd: z.string().nullish(),
useYn: z.string().default("Y"),
newWndwYn: z.string().nullish(),
menuLnkgSnNm: z.string().nullish(),
menuPicUserUnqId: z.string().nullish(),
menuPicUserUnqIdNm: z.string().nullish(),
menuPicDsgnYmd: z.string().nullish(),
});
export type MenuFormData = z.infer<typeof menuFormSchema>;
export type Menu = Omit<MenuFormData, "iconNm"> & {
menuCd: number;
iconName?: string;
};
export interface MenuService {
getMenuInfo: (menuCd: number) => Promise<Menu>;
createMenu: (data: MenuFormData) => Promise<void>;
updateMenu: (
menuCd: number,
data: Omit<MenuFormData, "siteId">,
) => Promise<void>;
deleteMenu: (menuCd: number) => Promise<void>;
getTreeMenuList: (siteId: string) => Promise<TreeMenu[]>;
updateTreeMenu: (siteId: string, data: TreeNode[]) => Promise<void>;
updateMenuName: (menuCd: number, name: string) => Promise<void>;
}
@@ -0,0 +1,29 @@
export interface TreeNode {
menuCd?: number;
name?: string;
sortSeq?: number;
parentId?: number | null;
levelNo?: number;
icon?: string;
children?: TreeNode[];
}
export interface TreeMenu extends Omit<TreeNode, "name" | "icon" | "children"> {
menuKornNm: string;
menuEngNm?: string;
iconNm?: string;
children?: TreeMenu[];
urlPath?: string;
menuLnkgTypeCd: string;
menuLnkgSn?: number;
qrcd?: string;
useYn: string;
otptYn: string;
requiredUrlPath?: boolean;
}
export interface TreeMenuService {
getTreeMenuList: (siteId: string) => Promise<TreeMenu[]>;
updateTreeMenu: (siteId: string, data: TreeNode[]) => Promise<void>;
updateMenuName: (menuCd: number, name: string) => Promise<void>;
}
+1 -1
View File
@@ -10,8 +10,8 @@ import {
TableHead,
TableRow,
} from "@common/components";
import { MENU_TYPE, type MenuFormData } from "@/entities/menu";
import { FormItem, FormControl, FormField } from "@/shared/ui/form";
import { MENU_TYPE, type MenuFormData } from "../model/menu-interface";
import { InfoTooltip } from "@/shared/ui/info-tooltip";
interface BaseMenuFormProps {
+11 -3
View File
@@ -1,9 +1,9 @@
import type { UseFormReturn } from "react-hook-form";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button } from "@common/components";
import { Form } from "@/shared/ui/form";
import type { MenuFormData } from "@/entities/menu";
import { ManagementAuthorityDialog } from "@/entities/menu-authority";
import type { MenuFormData } from "../model/menu-interface";
import { Form } from "@/shared/ui/form";
import { BaseMenuForm } from "./base-menu-form";
import { TypeMenuForm } from "./type-menu-form";
@@ -69,7 +69,15 @@ function MenuForm({
<h3 className="text-2xl font-medium text-label">
{isModifyMode ? "메뉴 수정" : "메뉴 등록"}
</h3>
<ManagementAuthorityDialog menuCd={menuCd} />
<div className="flex items-center gap-2">
<Button color="white" disabled={!isModifyMode}>
불러오기
</Button>
<Button color="gray" disabled={!isModifyMode}>
임시저장
</Button>
<ManagementAuthorityDialog menuCd={menuCd} />
</div>
</div>
<div className="card">
<BaseMenuForm form={form} menuCd={menuCd} />
+3 -3
View File
@@ -4,8 +4,8 @@ import type { NodeModel, TreeMethods } from "@minoru/react-dnd-treeview";
import { Button, cn } from "@common/components";
import { useMutationWrapper } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config";
import { MenuApi } from "../api/menu-service";
import type { TreeMenu } from "../model/menu-interface";
import { TreeMenuApi } from "../api/tree-menu-service";
import type { TreeMenu } from "../model/tree-menu-interface";
import { EditableNode } from "./editable-node";
type MenuNodeProps = {
@@ -37,7 +37,7 @@ function MenuNode({
const { mutateAsync: updateMenuName } = useMutationWrapper({
mutationFn: (nodeName: string) => {
const encodedName = encodeURIComponent(nodeName);
return MenuApi.updateMenuName(id as number, encodedName);
return TreeMenuApi.updateMenuName(id as number, encodedName);
},
onSuccess: () => {
queryClient.invalidateQueries({
+9 -8
View File
@@ -14,19 +14,20 @@ import {
SelectTrigger,
SelectValue,
} from "@common/components";
import { SiteApi } from "@/entities/site";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import type { TreeMenu } from "../model/menu-interface";
import { MenuApi } from "../api/menu-service";
import {
MenuApi,
MENU_TYPE,
menuFormSchema,
type MenuFormData,
} from "../model/menu-interface";
} from "@/entities/menu";
import { SiteApi } from "@/entities/site";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import { generateMenuTreeData } from "../lib/utils";
import { TreeMenuApi } from "../api/tree-menu-service";
import type { TreeMenu } from "../model/tree-menu-interface";
import { MenuTree } from "./menu-tree";
import { MenuForm } from "./menu-form";
import { generateMenuTreeData } from "../lib/utils";
function MenuPage() {
const queryClient = useQueryClient();
@@ -46,7 +47,7 @@ function MenuPage() {
const { data: menuTreeData } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
queryFn: () => MenuApi.getTreeMenuList(siteId),
queryFn: () => TreeMenuApi.getTreeMenuList(siteId),
select: (data) => {
const treeData = generateMenuTreeData(data);
const defaultSortSeq =
+4 -3
View File
@@ -11,8 +11,9 @@ import {
import { Button } from "@common/components";
import { ENDPOINT } from "@/shared/config";
import { useMutationWrapper } from "@/shared/lib";
import { MenuApi } from "../api/menu-service";
import type { MenuFormData, TreeMenu, TreeNode } from "../model/menu-interface";
import type { MenuFormData } from "@/entities/menu";
import { TreeMenuApi } from "../api/tree-menu-service";
import type { TreeMenu, TreeNode } from "../model/tree-menu-interface";
import { handleDropNode, handleSelectNode } from "../lib/utils";
import { MenuNode } from "./menu-node";
import { EditableNode } from "./editable-node";
@@ -41,7 +42,7 @@ function MenuTree({
const { mutate: updateTreeMenu } = useMutationWrapper({
mutationFn: (newTree: TreeNode) =>
MenuApi.updateTreeMenu(siteId, [newTree]),
TreeMenuApi.updateTreeMenu(siteId, [newTree]),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS],
+1 -1
View File
@@ -16,8 +16,8 @@ import {
import type { BoardPopup } from "@/entities/board";
import type { Contents } from "@/entities/content";
import type { TreeUser } from "@/entities/user-organization";
import type { MenuFormData, MenuType } from "@/entities/menu";
import { FormItem, FormControl, FormField } from "@/shared/ui/form";
import type { MenuFormData, MenuType } from "../model/menu-interface";
import { OrganizationSearchDialog } from "./organization-search-dialog";
import { ContentSearchDialog } from "./content-search-dialog";
import { BoardSearchDialog } from "./board-search-dialog";