- Enhanced the menu management functionality by adding a new `menu_icon` field in the database schema, allowing for the storage of menu icons. - Updated the `saveMenu` and `updateMenu` functions in the admin controller to handle the new `menu_icon` field during menu creation and updates. - Modified the `AdminService` to include `MENU_ICON` in various queries, ensuring that the icon data is retrieved and processed correctly. - Integrated the `MenuIconPicker` component in the frontend to allow users to select and display menu icons in the `MenuFormModal`. - Updated the sidebar and layout components to utilize the new icon data, enhancing the visual representation of menus across the application.
237 lines
6.8 KiB
TypeScript
237 lines
6.8 KiB
TypeScript
import { apiClient } from "./client";
|
|
|
|
export interface MenuItem {
|
|
objid?: string;
|
|
OBJID?: string;
|
|
parent_obj_id?: string;
|
|
PARENT_OBJ_ID?: string;
|
|
menu_name_kor?: string;
|
|
MENU_NAME_KOR?: string;
|
|
menu_url?: string;
|
|
MENU_URL?: string;
|
|
menu_desc?: string;
|
|
MENU_DESC?: string;
|
|
seq?: number;
|
|
SEQ?: number;
|
|
menu_type?: string;
|
|
MENU_TYPE?: string;
|
|
status?: string;
|
|
STATUS?: string;
|
|
lev?: number;
|
|
LEV?: number;
|
|
lpad_menu_name_kor?: string;
|
|
LPAD_MENU_NAME_KOR?: string;
|
|
status_title?: string;
|
|
STATUS_TITLE?: string;
|
|
writer?: string;
|
|
WRITER?: string;
|
|
regdate?: string;
|
|
REGDATE?: string;
|
|
company_code?: string;
|
|
COMPANY_CODE?: string;
|
|
company_name?: string;
|
|
COMPANY_NAME?: string;
|
|
// 다국어 관련 필드 추가
|
|
lang_key?: string;
|
|
LANG_KEY?: string;
|
|
lang_key_desc?: string;
|
|
LANG_KEY_DESC?: string;
|
|
translated_name?: string;
|
|
TRANSLATED_NAME?: string;
|
|
translated_desc?: string;
|
|
TRANSLATED_DESC?: string;
|
|
menu_icon?: string;
|
|
MENU_ICON?: string;
|
|
}
|
|
|
|
export interface MenuFormData {
|
|
objid?: string;
|
|
parentObjId: string;
|
|
menuNameKor: string;
|
|
menuUrl: string;
|
|
menuDesc: string;
|
|
seq: number;
|
|
menuType: string;
|
|
status: string;
|
|
companyCode: string;
|
|
langKey?: string;
|
|
screenCode?: string;
|
|
menuIcon?: string;
|
|
}
|
|
|
|
export interface LangKey {
|
|
keyId: number;
|
|
companyCode: string;
|
|
menuName: string;
|
|
langKey: string;
|
|
description: string;
|
|
isActive: string;
|
|
createdDate: string;
|
|
createdBy: string;
|
|
updatedDate: string;
|
|
updatedBy: string;
|
|
}
|
|
|
|
export interface ApiResponse<T> {
|
|
success: boolean;
|
|
data?: T;
|
|
message: string;
|
|
errorCode?: string;
|
|
}
|
|
|
|
export const menuApi = {
|
|
// 관리자 메뉴 목록 조회 (좌측 사이드바용 - active만 표시)
|
|
getAdminMenus: async (): Promise<ApiResponse<MenuItem[]>> => {
|
|
const response = await apiClient.get("/admin/menus", { params: { menuType: "0" } });
|
|
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
|
}
|
|
return response.data;
|
|
},
|
|
|
|
// 사용자 메뉴 목록 조회 (좌측 사이드바용 - active만 표시, 회사별 필터링)
|
|
getUserMenus: async (): Promise<ApiResponse<MenuItem[]>> => {
|
|
const response = await apiClient.get("/admin/user-menus");
|
|
return response.data;
|
|
},
|
|
|
|
// 관리자 메뉴 목록 조회 (메뉴 관리 화면용 - 모든 상태 표시)
|
|
getAdminMenusForManagement: async (): Promise<ApiResponse<MenuItem[]>> => {
|
|
const response = await apiClient.get("/admin/menus", { params: { menuType: "0", includeInactive: "true" } });
|
|
return response.data;
|
|
},
|
|
|
|
// 사용자 메뉴 목록 조회 (메뉴 관리 화면용 - 모든 상태 표시)
|
|
getUserMenusForManagement: async (): Promise<ApiResponse<MenuItem[]>> => {
|
|
const response = await apiClient.get("/admin/menus", { params: { menuType: "1", includeInactive: "true" } });
|
|
return response.data;
|
|
},
|
|
|
|
// 메뉴 정보 조회
|
|
getMenuInfo: async (menuId: string): Promise<ApiResponse<MenuItem>> => {
|
|
const response = await apiClient.get(`/admin/menus/${menuId}`);
|
|
return response.data;
|
|
},
|
|
|
|
// 메뉴 등록/수정
|
|
saveMenu: async (menuData: MenuFormData): Promise<ApiResponse<void>> => {
|
|
const response = await apiClient.post("/admin/menus", menuData);
|
|
return response.data;
|
|
},
|
|
|
|
// 메뉴 수정
|
|
updateMenu: async (menuId: string, menuData: MenuFormData): Promise<ApiResponse<void>> => {
|
|
const response = await apiClient.put(`/admin/menus/${menuId}`, menuData);
|
|
return response.data;
|
|
},
|
|
|
|
// 메뉴 삭제
|
|
deleteMenu: async (menuId: string): Promise<ApiResponse<void>> => {
|
|
const response = await apiClient.delete(`/admin/menus/${menuId}`);
|
|
return response.data;
|
|
},
|
|
|
|
// 메뉴 일괄 삭제
|
|
deleteMenusBatch: async (menuIds: string[]): Promise<ApiResponse<{ deletedCount: number; failedCount: number }>> => {
|
|
const response = await apiClient.delete("/admin/menus/batch", {
|
|
data: menuIds,
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
// 메뉴 활성/비활성 토글
|
|
toggleMenuStatus: async (menuId: string): Promise<ApiResponse<string>> => {
|
|
const response = await apiClient.put(`/admin/menus/${menuId}/toggle`);
|
|
return response.data;
|
|
},
|
|
|
|
// 메뉴 권한 그룹 목록 조회
|
|
getMenuAuthGroups: async (menuId: string): Promise<ApiResponse<any[]>> => {
|
|
const response = await apiClient.get(`/admin/menus/${menuId}/auth-groups`);
|
|
return response.data;
|
|
},
|
|
|
|
// 다국어 키 목록 조회
|
|
getLangKeys: async (params?: {
|
|
companyCode?: string;
|
|
menuCode?: string;
|
|
keyType?: string;
|
|
}): Promise<ApiResponse<LangKey[]>> => {
|
|
try {
|
|
// Node.js 백엔드의 실제 라우팅과 일치하도록 수정
|
|
const response = await apiClient.get("/multilang/keys", { params });
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error("❌ 다국어 키 목록 조회 실패:", error);
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
// 메뉴 복사 (타임아웃 5분 - 대량 데이터 처리)
|
|
copyMenu: async (
|
|
menuObjid: number,
|
|
targetCompanyCode: string,
|
|
screenNameConfig?: {
|
|
removeText?: string;
|
|
addPrefix?: string;
|
|
},
|
|
additionalCopyOptions?: {
|
|
copyCodeCategory?: boolean;
|
|
copyNumberingRules?: boolean;
|
|
copyCategoryMapping?: boolean;
|
|
copyTableTypeColumns?: boolean;
|
|
copyCascadingRelation?: boolean;
|
|
}
|
|
): Promise<ApiResponse<MenuCopyResult>> => {
|
|
try {
|
|
const response = await apiClient.post(
|
|
`/admin/menus/${menuObjid}/copy`,
|
|
{
|
|
targetCompanyCode,
|
|
screenNameConfig,
|
|
additionalCopyOptions
|
|
},
|
|
{
|
|
timeout: 300000, // 5분 (메뉴 복사는 많은 데이터를 처리하므로 긴 타임아웃 필요)
|
|
}
|
|
);
|
|
return response.data;
|
|
} catch (error: any) {
|
|
console.error("❌ 메뉴 복사 실패:", error);
|
|
|
|
// 타임아웃 에러 구분 처리
|
|
if (error.code === "ECONNABORTED" || error.message?.includes("timeout")) {
|
|
return {
|
|
success: false,
|
|
message: "메뉴 복사 요청 시간이 초과되었습니다. 백엔드에서 작업이 완료되었을 수 있으니 잠시 후 확인해주세요.",
|
|
errorCode: "MENU_COPY_TIMEOUT",
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: false,
|
|
message: error.response?.data?.message || "메뉴 복사 중 오류가 발생했습니다",
|
|
errorCode: error.response?.data?.error?.code || "MENU_COPY_ERROR",
|
|
};
|
|
}
|
|
},
|
|
};
|
|
|
|
/**
|
|
* 메뉴 복사 결과
|
|
*/
|
|
export interface MenuCopyResult {
|
|
copiedMenus: number;
|
|
copiedScreens: number;
|
|
copiedFlows: number;
|
|
copiedCodeCategories?: number;
|
|
copiedCodes?: number;
|
|
copiedNumberingRules?: number;
|
|
copiedCategoryMappings?: number;
|
|
copiedTableTypeColumns?: number;
|
|
copiedCascadingRelations?: number;
|
|
menuIdMap: Record<number, number>;
|
|
screenIdMap: Record<number, number>;
|
|
flowIdMap: Record<number, number>;
|
|
warnings?: string[];
|
|
}
|