ERP-node/frontend/lib/api/menu.ts

257 lines
7.4 KiB
TypeScript
Raw Normal View History

2025-08-21 09:41:46 +09:00
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;
}
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; // 화면 코드 추가
2025-08-21 09:41:46 +09:00
}
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;
}
feat(pop): PC <-> POP 모드 전환 네비게이션 + POP 기본 화면(Landing) 기능 PC 모드에서 프로필 드롭다운을 통해 POP 화면으로 진입하고, POP에서 PC로 돌아오는 양방향 네비게이션을 구현한다. 기존 메뉴 시스템(menu_info)을 활용하여 POP 화면의 권한 제어와 회사별 관리가 가능하도록 한다. [백엔드: POP 메뉴 조회 API] - AdminService.getPopMenuList: L1 POP 메뉴(menu_desc [POP] 또는 menu_name_kor POP 포함) 하위의 active L2 메뉴 조회 - company_code 필터링 적용 (L1 + L2 모두) - landingMenu 반환: menu_desc에 [POP_LANDING] 태그가 있는 메뉴 - GET /admin/pop-menus 라우트 추가 [프론트: PC -> POP 진입] - AppLayout: handlePopModeClick 함수 추가 - landingMenu 있으면 해당 URL로 바로 이동 - 없으면 childMenus 수에 따라 단일 화면/대시보드/안내 분기 - UserDropdown: onPopModeClick prop + "POP 모드" 메뉴 항목 추가 - 사이드바 하단 + 모바일 헤더 프로필 드롭다운 2곳 모두 적용 [프론트: POP -> PC 복귀] - DashboardHeader: "PC 모드" 버튼 추가 (router.push "/") - POP 개별 화면 page.tsx: 상단 네비게이션 바 추가 (POP 대시보드 / PC 모드 버튼) [프론트: POP 대시보드 동적 메뉴] - PopDashboard: 하드코딩 MENU_ITEMS -> menuApi.getPopMenus() API 조회 - API 실패 시 하드코딩 fallback 유지 [프론트: POP 기본 화면 설정 (MenuFormModal)] - L2 POP 화면 수정 시 "POP 기본 화면으로 설정" 체크박스 추가 - 체크 시 menu_desc에 [POP_LANDING] 태그 자동 추가/제거 - 회사당 1개만 설정 가능 (다른 메뉴에 이미 설정 시 비활성화) [API 타입] - PopMenuItem, PopMenuResponse(landingMenu 포함) 인터페이스 추가 - menuApi.getPopMenus() 함수 추가
2026-03-09 12:16:26 +09:00
export interface PopMenuItem {
objid: string;
menu_name_kor: string;
menu_url: string;
menu_desc: string;
seq: number;
company_code: string;
status: string;
screenId?: number;
}
export interface PopMenuResponse {
parentMenu: PopMenuItem | null;
childMenus: PopMenuItem[];
landingMenu: PopMenuItem | null;
}
2025-08-21 09:41:46 +09:00
export const menuApi = {
// 관리자 메뉴 목록 조회 (좌측 사이드바용 - active만 표시)
2025-08-21 09:41:46 +09:00
getAdminMenus: async (): Promise<ApiResponse<MenuItem[]>> => {
2025-10-13 19:18:01 +09:00
const response = await apiClient.get("/admin/menus", { params: { menuType: "0" } });
2025-08-21 09:41:46 +09:00
if (response.data.success && response.data.data && response.data.data.length > 0) {
}
return response.data;
},
// 사용자 메뉴 목록 조회 (좌측 사이드바용 - active만 표시, 회사별 필터링)
2025-08-21 09:41:46 +09:00
getUserMenus: async (): Promise<ApiResponse<MenuItem[]>> => {
const response = await apiClient.get("/admin/user-menus");
2025-08-21 09:41:46 +09:00
return response.data;
},
feat(pop): PC <-> POP 모드 전환 네비게이션 + POP 기본 화면(Landing) 기능 PC 모드에서 프로필 드롭다운을 통해 POP 화면으로 진입하고, POP에서 PC로 돌아오는 양방향 네비게이션을 구현한다. 기존 메뉴 시스템(menu_info)을 활용하여 POP 화면의 권한 제어와 회사별 관리가 가능하도록 한다. [백엔드: POP 메뉴 조회 API] - AdminService.getPopMenuList: L1 POP 메뉴(menu_desc [POP] 또는 menu_name_kor POP 포함) 하위의 active L2 메뉴 조회 - company_code 필터링 적용 (L1 + L2 모두) - landingMenu 반환: menu_desc에 [POP_LANDING] 태그가 있는 메뉴 - GET /admin/pop-menus 라우트 추가 [프론트: PC -> POP 진입] - AppLayout: handlePopModeClick 함수 추가 - landingMenu 있으면 해당 URL로 바로 이동 - 없으면 childMenus 수에 따라 단일 화면/대시보드/안내 분기 - UserDropdown: onPopModeClick prop + "POP 모드" 메뉴 항목 추가 - 사이드바 하단 + 모바일 헤더 프로필 드롭다운 2곳 모두 적용 [프론트: POP -> PC 복귀] - DashboardHeader: "PC 모드" 버튼 추가 (router.push "/") - POP 개별 화면 page.tsx: 상단 네비게이션 바 추가 (POP 대시보드 / PC 모드 버튼) [프론트: POP 대시보드 동적 메뉴] - PopDashboard: 하드코딩 MENU_ITEMS -> menuApi.getPopMenus() API 조회 - API 실패 시 하드코딩 fallback 유지 [프론트: POP 기본 화면 설정 (MenuFormModal)] - L2 POP 화면 수정 시 "POP 기본 화면으로 설정" 체크박스 추가 - 체크 시 menu_desc에 [POP_LANDING] 태그 자동 추가/제거 - 회사당 1개만 설정 가능 (다른 메뉴에 이미 설정 시 비활성화) [API 타입] - PopMenuItem, PopMenuResponse(landingMenu 포함) 인터페이스 추가 - menuApi.getPopMenus() 함수 추가
2026-03-09 12:16:26 +09:00
// POP 메뉴 목록 조회 ([POP] 태그 L1 하위 active 메뉴)
getPopMenus: async (): Promise<ApiResponse<PopMenuResponse>> => {
const response = await apiClient.get("/admin/pop-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;
2025-08-21 09:41:46 +09:00
},
// 메뉴 정보 조회
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;
},
2025-08-21 09:41:46 +09:00
// 메뉴 삭제
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 백엔드의 실제 라우팅과 일치하도록 수정
2025-08-25 17:22:20 +09:00
const response = await apiClient.get("/multilang/keys", { params });
return response.data;
} catch (error) {
console.error("❌ 다국어 키 목록 조회 실패:", error);
throw error;
}
2025-08-21 09:41:46 +09:00
},
2025-12-18 16:35:55 +09:00
// 메뉴 복사 (타임아웃 5분 - 대량 데이터 처리)
copyMenu: async (
menuObjid: number,
targetCompanyCode: string,
screenNameConfig?: {
removeText?: string;
addPrefix?: string;
2025-12-18 10:55:26 +09:00
},
additionalCopyOptions?: {
copyCodeCategory?: boolean;
copyNumberingRules?: boolean;
copyCategoryMapping?: boolean;
copyTableTypeColumns?: boolean;
2025-12-18 16:35:55 +09:00
copyCascadingRelation?: boolean;
}
): Promise<ApiResponse<MenuCopyResult>> => {
try {
const response = await apiClient.post(
`/admin/menus/${menuObjid}/copy`,
{
targetCompanyCode,
2025-12-18 10:55:26 +09:00
screenNameConfig,
additionalCopyOptions
2025-12-18 16:35:55 +09:00
},
{
timeout: 300000, // 5분 (메뉴 복사는 많은 데이터를 처리하므로 긴 타임아웃 필요)
}
);
return response.data;
} catch (error: any) {
console.error("❌ 메뉴 복사 실패:", error);
2025-12-18 16:35:55 +09:00
// 타임아웃 에러 구분 처리
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",
};
}
},
2025-08-21 09:41:46 +09:00
};
/**
*
*/
export interface MenuCopyResult {
copiedMenus: number;
copiedScreens: number;
copiedFlows: number;
2025-12-18 10:55:26 +09:00
copiedCodeCategories?: number;
copiedCodes?: number;
copiedNumberingRules?: number;
copiedCategoryMappings?: number;
copiedTableTypeColumns?: number;
2025-12-18 16:35:55 +09:00
copiedCascadingRelations?: number;
menuIdMap: Record<number, number>;
screenIdMap: Record<number, number>;
flowIdMap: Record<number, number>;
warnings?: string[];
}