feat: 관리자 테이블 스타일 개선 및 탭 컴포넌트 디자인 수정
- 외부 커넥션 관리 테이블 표준화 (DB 연결, REST API 연결) - 모든 관리자 테이블의 그림자 제거 (테이블 타입 관리 왼쪽 카드 제외) - 테이블 타입 관리 왼쪽 카드 호버 효과 강화 (shadow-lg, bg-muted/20) - 탭 컴포넌트 배경색 밝게 조정 (bg-muted/30) - 탭 트리거 테두리 제거
This commit is contained in:
@@ -25,19 +25,22 @@ export async function getAdminMenus(
|
||||
const userType = req.user?.userType;
|
||||
const userLang = (req.query.userLang as string) || "ko";
|
||||
const menuType = req.query.menuType as string | undefined; // menuType 파라미터 추가
|
||||
const includeInactive = req.query.includeInactive === "true"; // includeInactive 파라미터 추가
|
||||
|
||||
logger.info(`사용자 ID: ${userId}`);
|
||||
logger.info(`사용자 회사 코드: ${userCompanyCode}`);
|
||||
logger.info(`사용자 유형: ${userType}`);
|
||||
logger.info(`사용자 로케일: ${userLang}`);
|
||||
logger.info(`메뉴 타입: ${menuType || "전체"}`);
|
||||
logger.info(`비활성 메뉴 포함: ${includeInactive}`);
|
||||
|
||||
const paramMap = {
|
||||
userId,
|
||||
userCompanyCode,
|
||||
userType,
|
||||
userLang,
|
||||
menuType, // menuType 추가
|
||||
menuType, // includeInactive와 관계없이 menuType 유지 (관리자/사용자 구분)
|
||||
includeInactive, // includeInactive 추가
|
||||
};
|
||||
|
||||
const menuList = await AdminService.getAdminMenuList(paramMap);
|
||||
@@ -1081,9 +1084,41 @@ export async function saveMenu(
|
||||
return;
|
||||
}
|
||||
|
||||
const userCompanyCode = req.user.companyCode;
|
||||
const userType = req.user.userType;
|
||||
let requestCompanyCode = menuData.companyCode || menuData.company_code;
|
||||
|
||||
// "none"이나 빈 값은 undefined로 처리하여 사용자 회사 코드 사용
|
||||
if (requestCompanyCode === "none" || requestCompanyCode === "" || !requestCompanyCode) {
|
||||
requestCompanyCode = undefined;
|
||||
}
|
||||
|
||||
// 공통 메뉴(company_code = '*')는 최고 관리자만 생성 가능
|
||||
if (requestCompanyCode === "*") {
|
||||
if (userCompanyCode !== "*" || userType !== "SUPER_ADMIN") {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "공통 메뉴는 최고 관리자만 생성할 수 있습니다.",
|
||||
error: "Unauthorized to create common menu",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (userCompanyCode !== "*") {
|
||||
// 회사 관리자는 자기 회사 메뉴만 생성 가능
|
||||
// requestCompanyCode가 undefined면 사용자 회사 코드 사용 (권한 체크 통과)
|
||||
if (requestCompanyCode && requestCompanyCode !== userCompanyCode) {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "해당 회사의 메뉴를 생성할 권한이 없습니다.",
|
||||
error: "Unauthorized to create menu for this company",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Raw Query를 사용한 메뉴 저장
|
||||
const objid = Date.now(); // 고유 ID 생성
|
||||
const companyCode = req.user.companyCode;
|
||||
const companyCode = requestCompanyCode || userCompanyCode;
|
||||
|
||||
const [savedMenu] = await query<any>(
|
||||
`INSERT INTO menu_info (
|
||||
@@ -1164,7 +1199,73 @@ export async function updateMenu(
|
||||
return;
|
||||
}
|
||||
|
||||
const companyCode = req.user.companyCode;
|
||||
const userCompanyCode = req.user.companyCode;
|
||||
const userType = req.user.userType;
|
||||
|
||||
// 수정하려는 메뉴 조회
|
||||
const currentMenu = await queryOne<any>(
|
||||
`SELECT objid, company_code FROM menu_info WHERE objid = $1`,
|
||||
[Number(menuId)]
|
||||
);
|
||||
|
||||
if (!currentMenu) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: `메뉴를 찾을 수 없습니다: ${menuId}`,
|
||||
error: "Menu not found",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 공통 메뉴(company_code = '*')는 최고 관리자만 수정 가능
|
||||
if (currentMenu.company_code === "*") {
|
||||
if (userCompanyCode !== "*" || userType !== "SUPER_ADMIN") {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "공통 메뉴는 최고 관리자만 수정할 수 있습니다.",
|
||||
error: "Unauthorized to update common menu",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (userCompanyCode !== "*") {
|
||||
// 회사 관리자는 자기 회사 메뉴만 수정 가능
|
||||
if (currentMenu.company_code !== userCompanyCode) {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "해당 회사의 메뉴를 수정할 권한이 없습니다.",
|
||||
error: "Unauthorized to update menu for this company",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const requestCompanyCode = menuData.companyCode || menuData.company_code || currentMenu.company_code;
|
||||
|
||||
// company_code 변경 시도하는 경우 권한 체크
|
||||
if (requestCompanyCode !== currentMenu.company_code) {
|
||||
// 공통 메뉴로 변경하려는 경우 최고 관리자만 가능
|
||||
if (requestCompanyCode === "*") {
|
||||
if (userCompanyCode !== "*" || userType !== "SUPER_ADMIN") {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "공통 메뉴로 변경할 권한이 없습니다.",
|
||||
error: "Unauthorized to change to common menu",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 회사 관리자는 자기 회사로만 변경 가능
|
||||
else if (userCompanyCode !== "*" && requestCompanyCode !== userCompanyCode) {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "해당 회사로 변경할 권한이 없습니다.",
|
||||
error: "Unauthorized to change company",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const companyCode = requestCompanyCode;
|
||||
|
||||
// Raw Query를 사용한 메뉴 수정
|
||||
const [updatedMenu] = await query<any>(
|
||||
@@ -1239,6 +1340,56 @@ export async function deleteMenu(
|
||||
const { menuId } = req.params;
|
||||
logger.info(`메뉴 삭제 요청: menuId = ${menuId}`, { user: req.user });
|
||||
|
||||
// 사용자의 company_code 확인
|
||||
if (!req.user?.companyCode) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "사용자의 회사 코드를 찾을 수 없습니다.",
|
||||
error: "Missing company_code",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const userCompanyCode = req.user.companyCode;
|
||||
const userType = req.user.userType;
|
||||
|
||||
// 삭제하려는 메뉴 조회
|
||||
const currentMenu = await queryOne<any>(
|
||||
`SELECT objid, company_code FROM menu_info WHERE objid = $1`,
|
||||
[Number(menuId)]
|
||||
);
|
||||
|
||||
if (!currentMenu) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: `메뉴를 찾을 수 없습니다: ${menuId}`,
|
||||
error: "Menu not found",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 공통 메뉴(company_code = '*')는 최고 관리자만 삭제 가능
|
||||
if (currentMenu.company_code === "*") {
|
||||
if (userCompanyCode !== "*" || userType !== "SUPER_ADMIN") {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "공통 메뉴는 최고 관리자만 삭제할 수 있습니다.",
|
||||
error: "Unauthorized to delete common menu",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (userCompanyCode !== "*") {
|
||||
// 회사 관리자는 자기 회사 메뉴만 삭제 가능
|
||||
if (currentMenu.company_code !== userCompanyCode) {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "해당 회사의 메뉴를 삭제할 권한이 없습니다.",
|
||||
error: "Unauthorized to delete menu for this company",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Raw Query를 사용한 메뉴 삭제
|
||||
const [deletedMenu] = await query<any>(
|
||||
`DELETE FROM menu_info WHERE objid = $1 RETURNING *`,
|
||||
@@ -1292,6 +1443,51 @@ export async function deleteMenusBatch(
|
||||
return;
|
||||
}
|
||||
|
||||
// 사용자의 company_code 확인
|
||||
if (!req.user?.companyCode) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "사용자의 회사 코드를 찾을 수 없습니다.",
|
||||
error: "Missing company_code",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const userCompanyCode = req.user.companyCode;
|
||||
const userType = req.user.userType;
|
||||
|
||||
// 삭제하려는 메뉴들의 company_code 확인
|
||||
const menusToDelete = await query<any>(
|
||||
`SELECT objid, company_code FROM menu_info WHERE objid = ANY($1::bigint[])`,
|
||||
[menuIds.map((id) => Number(id))]
|
||||
);
|
||||
|
||||
// 권한 체크: 공통 메뉴 포함 여부 확인
|
||||
const hasCommonMenu = menusToDelete.some((menu: any) => menu.company_code === "*");
|
||||
if (hasCommonMenu && (userCompanyCode !== "*" || userType !== "SUPER_ADMIN")) {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "공통 메뉴는 최고 관리자만 삭제할 수 있습니다.",
|
||||
error: "Unauthorized to delete common menu",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 회사 관리자는 자기 회사 메뉴만 삭제 가능
|
||||
if (userCompanyCode !== "*") {
|
||||
const unauthorizedMenus = menusToDelete.filter(
|
||||
(menu: any) => menu.company_code !== userCompanyCode && menu.company_code !== "*"
|
||||
);
|
||||
if (unauthorizedMenus.length > 0) {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "다른 회사의 메뉴를 삭제할 권한이 없습니다.",
|
||||
error: "Unauthorized to delete menus for other companies",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Raw Query를 사용한 메뉴 일괄 삭제
|
||||
let deletedCount = 0;
|
||||
let failedCount = 0;
|
||||
@@ -1354,6 +1550,103 @@ export async function deleteMenusBatch(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 활성/비활성 토글
|
||||
*/
|
||||
export async function toggleMenuStatus(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { menuId } = req.params;
|
||||
logger.info(`메뉴 상태 토글 요청: menuId = ${menuId}`, { user: req.user });
|
||||
|
||||
// 사용자의 company_code 확인
|
||||
if (!req.user?.companyCode) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "사용자의 회사 코드를 찾을 수 없습니다.",
|
||||
error: "Missing company_code",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const userCompanyCode = req.user.companyCode;
|
||||
const userType = req.user.userType;
|
||||
|
||||
// 현재 상태 및 회사 코드 조회
|
||||
const currentMenu = await queryOne<any>(
|
||||
`SELECT objid, status, company_code FROM menu_info WHERE objid = $1`,
|
||||
[Number(menuId)]
|
||||
);
|
||||
|
||||
if (!currentMenu) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: `메뉴를 찾을 수 없습니다: ${menuId}`,
|
||||
error: "Menu not found",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 공통 메뉴(company_code = '*')는 최고 관리자만 상태 변경 가능
|
||||
if (currentMenu.company_code === "*") {
|
||||
if (userCompanyCode !== "*" || userType !== "SUPER_ADMIN") {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "공통 메뉴는 최고 관리자만 상태를 변경할 수 있습니다.",
|
||||
error: "Unauthorized to toggle common menu status",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (userCompanyCode !== "*") {
|
||||
// 회사 관리자는 자기 회사 메뉴만 상태 변경 가능
|
||||
if (currentMenu.company_code !== userCompanyCode) {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "해당 회사의 메뉴 상태를 변경할 권한이 없습니다.",
|
||||
error: "Unauthorized to toggle menu status for this company",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 상태 토글 (active <-> inactive)
|
||||
const currentStatus = currentMenu.status;
|
||||
const newStatus = currentStatus === "active" ? "inactive" : "active";
|
||||
|
||||
// 상태 업데이트
|
||||
const [updatedMenu] = await query<any>(
|
||||
`UPDATE menu_info SET status = $1 WHERE objid = $2 RETURNING *`,
|
||||
[newStatus, Number(menuId)]
|
||||
);
|
||||
|
||||
logger.info("메뉴 상태 토글 성공", {
|
||||
menuId,
|
||||
oldStatus: currentStatus,
|
||||
newStatus,
|
||||
});
|
||||
|
||||
const result = newStatus === "active" ? "활성화" : "비활성화";
|
||||
|
||||
const response: ApiResponse<string> = {
|
||||
success: true,
|
||||
message: `메뉴가 ${result}되었습니다.`,
|
||||
data: result,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error("메뉴 상태 토글 실패:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "메뉴 상태 변경에 실패하였습니다.",
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
errorCode: "MENU_TOGGLE_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 회사 목록 조회 (실제 데이터베이스에서)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user