diff --git a/backend-node/src/services/adminService.ts b/backend-node/src/services/adminService.ts index c6ab17c6..5ca6b392 100644 --- a/backend-node/src/services/adminService.ts +++ b/backend-node/src/services/adminService.ts @@ -19,15 +19,21 @@ export class AdminService { // menuType에 따른 WHERE 조건 생성 const menuTypeCondition = - menuType !== undefined ? `MENU.MENU_TYPE = ${parseInt(menuType)}` : "1 = 1"; - + menuType !== undefined + ? `MENU.MENU_TYPE = ${parseInt(menuType)}` + : "1 = 1"; + // 메뉴 관리 화면인지 좌측 사이드바인지 구분 // includeInactive가 true거나 menuType이 undefined면 메뉴 관리 화면 const includeInactive = paramMap.includeInactive === true; const isManagementScreen = includeInactive || menuType === undefined; // 메뉴 관리 화면: 모든 상태의 메뉴 표시, 좌측 사이드바: active만 표시 - const statusCondition = isManagementScreen ? "1 = 1" : "MENU.STATUS = 'active'"; - const subStatusCondition = isManagementScreen ? "1 = 1" : "MENU_SUB.STATUS = 'active'"; + const statusCondition = isManagementScreen + ? "1 = 1" + : "MENU.STATUS = 'active'"; + const subStatusCondition = isManagementScreen + ? "1 = 1" + : "MENU_SUB.STATUS = 'active'"; // 1. 권한 그룹 기반 필터링 (좌측 사이드바인 경우만) let authFilter = ""; @@ -35,7 +41,11 @@ export class AdminService { let queryParams: any[] = [userLang]; let paramIndex = 2; - if (menuType !== undefined && userType !== "SUPER_ADMIN" && !isManagementScreen) { + if ( + menuType !== undefined && + userType !== "SUPER_ADMIN" && + !isManagementScreen + ) { // 좌측 사이드바 + SUPER_ADMIN이 아닌 경우만 권한 체크 const userRoleGroups = await query( ` @@ -56,45 +66,45 @@ export class AdminService { ); if (userType === "COMPANY_ADMIN") { - // 회사 관리자: 자기 회사 메뉴는 모두, 공통 메뉴는 권한 있는 것만 + // 회사 관리자: 권한 그룹 기반 필터링 적용 if (userRoleGroups.length > 0) { const roleObjids = userRoleGroups.map((rg: any) => rg.role_objid); - // 루트 메뉴: 회사 코드만 체크 (권한 체크 X) - authFilter = `AND MENU.COMPANY_CODE IN ($${paramIndex}, '*')`; + // 회사 관리자도 권한 그룹 설정에 따라 메뉴 필터링 + authFilter = ` + AND MENU.COMPANY_CODE IN ($${paramIndex}, '*') + AND EXISTS ( + SELECT 1 + FROM rel_menu_auth rma + WHERE rma.menu_objid = MENU.OBJID + AND rma.auth_objid = ANY($${paramIndex + 1}) + AND rma.read_yn = 'Y' + ) + `; queryParams.push(userCompanyCode); - const companyParamIndex = paramIndex; paramIndex++; - // 하위 메뉴: 회사 메뉴는 모두, 공통 메뉴는 권한 체크 + // 하위 메뉴도 권한 체크 unionFilter = ` - AND ( - MENU_SUB.COMPANY_CODE = $${companyParamIndex} - OR ( - MENU_SUB.COMPANY_CODE = '*' - AND EXISTS ( - SELECT 1 - FROM rel_menu_auth rma - WHERE rma.menu_objid = MENU_SUB.OBJID - AND rma.auth_objid = ANY($${paramIndex}) - AND rma.read_yn = 'Y' - ) - ) + AND MENU_SUB.COMPANY_CODE IN ($${paramIndex - 1}, '*') + AND EXISTS ( + SELECT 1 + FROM rel_menu_auth rma + WHERE rma.menu_objid = MENU_SUB.OBJID + AND rma.auth_objid = ANY($${paramIndex}) + AND rma.read_yn = 'Y' ) `; queryParams.push(roleObjids); paramIndex++; logger.info( - `✅ 회사 관리자: 회사 ${userCompanyCode} 메뉴 전체 + 권한 있는 공통 메뉴` + `✅ 회사 관리자: 권한 있는 메뉴만 (${roleObjids.length}개 그룹)` ); } else { - // 권한 그룹이 없는 회사 관리자: 자기 회사 메뉴만 - authFilter = `AND MENU.COMPANY_CODE = $${paramIndex}`; - unionFilter = `AND MENU_SUB.COMPANY_CODE = $${paramIndex}`; - queryParams.push(userCompanyCode); - paramIndex++; - logger.info( - `✅ 회사 관리자 (권한 그룹 없음): 회사 ${userCompanyCode} 메뉴만` + // 권한 그룹이 없는 회사 관리자: 메뉴 없음 + logger.warn( + `⚠️ 회사 관리자 ${userId}는 권한 그룹이 없어 메뉴가 표시되지 않습니다.` ); + return []; } } else { // 일반 사용자: 권한 그룹 필수 @@ -131,7 +141,11 @@ export class AdminService { return []; } } - } else if (menuType !== undefined && userType === "SUPER_ADMIN" && !isManagementScreen) { + } else if ( + menuType !== undefined && + userType === "SUPER_ADMIN" && + !isManagementScreen + ) { // 좌측 사이드바 + SUPER_ADMIN: 권한 그룹 체크 없이 모든 공통 메뉴 표시 logger.info(`✅ 최고 관리자는 권한 그룹 체크 없이 모든 공통 메뉴 표시`); // unionFilter는 비워둠 (하위 메뉴도 공통 메뉴만) @@ -167,7 +181,7 @@ export class AdminService { companyFilter = `AND MENU.COMPANY_CODE = $${paramIndex}`; queryParams.push(userCompanyCode); paramIndex++; - + // 하위 메뉴에도 회사 필터링 적용 (공통 메뉴 제외) if (unionFilter === "") { unionFilter = `AND MENU_SUB.COMPANY_CODE = $${paramIndex - 1}`; diff --git a/frontend/components/admin/RoleDetailManagement.tsx b/frontend/components/admin/RoleDetailManagement.tsx index 8f3d8fbb..27a6c07d 100644 --- a/frontend/components/admin/RoleDetailManagement.tsx +++ b/frontend/components/admin/RoleDetailManagement.tsx @@ -9,6 +9,7 @@ import { useRouter } from "next/navigation"; import { AlertCircle } from "lucide-react"; import { DualListBox } from "@/components/common/DualListBox"; import { MenuPermissionsTable } from "./MenuPermissionsTable"; +import { useMenu } from "@/contexts/MenuContext"; interface RoleDetailManagementProps { roleId: string; @@ -25,6 +26,7 @@ interface RoleDetailManagementProps { export function RoleDetailManagement({ roleId }: RoleDetailManagementProps) { const { user: currentUser } = useAuth(); const router = useRouter(); + const { refreshMenus } = useMenu(); const isSuperAdmin = currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN"; @@ -178,6 +180,9 @@ export function RoleDetailManagement({ roleId }: RoleDetailManagementProps) { if (response.success) { alert("멤버가 성공적으로 저장되었습니다."); loadMembers(); // 새로고침 + + // 사이드바 메뉴 새로고침 (현재 사용자가 영향받을 수 있음) + await refreshMenus(); } else { alert(response.message || "멤버 저장에 실패했습니다."); } @@ -187,7 +192,7 @@ export function RoleDetailManagement({ roleId }: RoleDetailManagementProps) { } finally { setIsSavingMembers(false); } - }, [roleGroup, selectedUsers, loadMembers]); + }, [roleGroup, selectedUsers, loadMembers, refreshMenus]); // 메뉴 권한 저장 핸들러 const handleSavePermissions = useCallback(async () => { @@ -200,6 +205,9 @@ export function RoleDetailManagement({ roleId }: RoleDetailManagementProps) { if (response.success) { alert("메뉴 권한이 성공적으로 저장되었습니다."); loadMenuPermissions(); // 새로고침 + + // 사이드바 메뉴 새로고침 (권한 변경 즉시 반영) + await refreshMenus(); } else { alert(response.message || "메뉴 권한 저장에 실패했습니다."); } @@ -209,7 +217,7 @@ export function RoleDetailManagement({ roleId }: RoleDetailManagementProps) { } finally { setIsSavingPermissions(false); } - }, [roleGroup, menuPermissions, loadMenuPermissions]); + }, [roleGroup, menuPermissions, loadMenuPermissions, refreshMenus]); if (isLoading) { return (