diff --git a/backend-node/src/controllers/adminController.ts b/backend-node/src/controllers/adminController.ts index a91e8a31..03b58c26 100644 --- a/backend-node/src/controllers/adminController.ts +++ b/backend-node/src/controllers/adminController.ts @@ -3,6 +3,7 @@ import { logger } from "../utils/logger"; import { AuthenticatedRequest } from "../types/auth"; import { ApiResponse } from "../types/common"; import { Client } from "pg"; +import config from "../config/environment"; import { AdminService } from "../services/adminService"; import { EncryptUtil } from "../utils/encryptUtil"; @@ -1984,6 +1985,140 @@ export const getUserHistory = async ( } }; +/** + * PATCH /api/admin/users/:userId/status + * 사용자 상태 변경 API (부분 수정) + * 기존 Java AdminController.changeUserStatus() 포팅 + */ +export const changeUserStatus = async ( + req: AuthenticatedRequest, + res: Response +) => { + try { + const { userId } = req.params; + const { status } = req.body; + + logger.info("사용자 상태 변경 요청", { userId, status, user: req.user }); + + // 필수 파라미터 검증 + if (!userId || !status) { + res.status(400).json({ + result: false, + msg: "사용자 ID와 상태는 필수입니다.", + }); + return; + } + + // 상태 값 검증 + if (!["active", "inactive"].includes(status)) { + res.status(400).json({ + result: false, + msg: "유효하지 않은 상태값입니다. (active, inactive만 허용)", + }); + return; + } + + const client = new Client({ + connectionString: config.databaseUrl, + }); + + try { + await client.connect(); + + // 1. 사용자 존재 여부 확인 + const userCheckResult = await client.query( + "SELECT user_id, user_name, status FROM user_info WHERE user_id = $1", + [userId] + ); + + if (userCheckResult.rows.length === 0) { + res.status(404).json({ + result: false, + msg: "사용자를 찾을 수 없습니다.", + }); + return; + } + + const currentUser = userCheckResult.rows[0]; + + // 2. 상태 변경 쿼리 실행 + let updateQuery = ` + UPDATE user_info + SET status = $1 + `; + + const queryParams = [status]; + + // active/inactive에 따른 END_DATE 처리 + if (status === "inactive") { + updateQuery += `, end_date = NOW()`; + } else if (status === "active") { + updateQuery += `, end_date = NULL`; + } + + updateQuery += ` WHERE user_id = $2`; + queryParams.push(userId); + + const updateResult = await client.query(updateQuery, queryParams); + + if (updateResult.rowCount && updateResult.rowCount > 0) { + // 3. 사용자 이력 저장 (선택적) + try { + await client.query( + ` + INSERT INTO user_info_history + (user_id, user_name, dept_code, dept_name, user_type_name, history_type, writer, reg_date, status, sabun) + VALUES ($1, $2, '', '', '', '사용자 상태 변경', $3, NOW(), $4, '') + `, + [ + userId, + currentUser.user_name || userId, + req.user?.userId || "system", + status, + ] + ); + } catch (historyError) { + logger.warn("사용자 이력 저장 실패", { + error: historyError, + userId, + status, + }); + // 이력 저장 실패는 치명적이지 않으므로 계속 진행 + } + + logger.info("사용자 상태 변경 성공", { + userId, + oldStatus: currentUser.status, + newStatus: status, + updatedBy: req.user?.userId, + }); + + res.json({ + result: true, + msg: `사용자 상태가 ${status === "active" ? "활성" : "비활성"}으로 변경되었습니다.`, + }); + } else { + res.status(400).json({ + result: false, + msg: "사용자 상태 변경에 실패했습니다.", + }); + } + } finally { + await client.end(); + } + } catch (error) { + logger.error("사용자 상태 변경 중 오류 발생", { + error, + userId: req.params.userId, + status: req.body.status, + }); + res.status(500).json({ + result: false, + msg: "시스템 오류가 발생했습니다.", + }); + } +}; + export const saveUser = async (req: AuthenticatedRequest, res: Response) => { try { const userData = req.body; diff --git a/backend-node/src/routes/adminRoutes.ts b/backend-node/src/routes/adminRoutes.ts index c3e79cd6..460e81d9 100644 --- a/backend-node/src/routes/adminRoutes.ts +++ b/backend-node/src/routes/adminRoutes.ts @@ -10,6 +10,7 @@ import { getUserList, getUserInfo, // 사용자 상세 조회 getUserHistory, // 사용자 변경이력 조회 + changeUserStatus, // 사용자 상태 변경 getDepartmentList, // 부서 목록 조회 checkDuplicateUserId, // 사용자 ID 중복 체크 saveUser, // 사용자 등록/수정 @@ -41,6 +42,7 @@ router.delete("/menus/:menuId", deleteMenu); // 메뉴 삭제 router.get("/users", getUserList); router.get("/users/:userId", getUserInfo); // 사용자 상세 조회 router.get("/users/:userId/history", getUserHistory); // 사용자 변경이력 조회 +router.patch("/users/:userId/status", changeUserStatus); // 사용자 상태 변경 router.post("/users", saveUser); // 사용자 등록/수정 router.post("/users/check-duplicate", checkDuplicateUserId); // 사용자 ID 중복 체크 diff --git a/frontend/components/admin/UserTable.tsx b/frontend/components/admin/UserTable.tsx index a4b55731..86b7f519 100644 --- a/frontend/components/admin/UserTable.tsx +++ b/frontend/components/admin/UserTable.tsx @@ -1,4 +1,4 @@ -import { Key, FileText, History } from "lucide-react"; +import { Key, History } from "lucide-react"; import { useState } from "react"; import { User } from "@/types/user"; import { USER_TABLE_COLUMNS } from "@/constants/user"; @@ -198,11 +198,6 @@ export function UserTable({ users, isLoading, paginationInfo, onStatusToggle, on onCheckedChange={(checked) => handleStatusToggle(user, checked)} aria-label={`${user.userName} 상태 토글`} /> - - {user.status === "active" ? "활성" : "비활성"} - diff --git a/frontend/hooks/useUserManagement.ts b/frontend/hooks/useUserManagement.ts index ae53607e..3492f4fb 100644 --- a/frontend/hooks/useUserManagement.ts +++ b/frontend/hooks/useUserManagement.ts @@ -181,10 +181,10 @@ export const useUserManagement = () => { // 사용자 상태 토글 핸들러 const handleStatusToggle = useCallback(async (user: User, newStatus: string) => { try { - console.log(`🎛️ 상태 변경: ${user.user_name} (${user.user_id}) → ${newStatus}`); + console.log(`🎛️ 상태 변경: ${user.userName} (${user.userId}) → ${newStatus}`); // 백엔드 API 호출 - const response = await userAPI.updateStatus(user.user_id, newStatus); + const response = await userAPI.updateStatus(user.userId, newStatus); // 백엔드 응답 구조: { result: boolean, msg: string } if (response && typeof response === "object" && "result" in response) { @@ -194,7 +194,7 @@ export const useUserManagement = () => { console.log("✅ 상태 변경 성공:", apiResponse.msg); // 전체 목록 새로고침 대신 개별 사용자 상태만 업데이트 - setUsers((prevUsers) => prevUsers.map((u) => (u.user_id === user.user_id ? { ...u, status: newStatus } : u))); + setUsers((prevUsers) => prevUsers.map((u) => (u.userId === user.userId ? { ...u, status: newStatus } : u))); } else { console.error("❌ 상태 변경 실패:", apiResponse.msg); alert(apiResponse.msg || "상태 변경에 실패했습니다."); diff --git a/frontend/lib/api/user.ts b/frontend/lib/api/user.ts index d4f8efea..b34eb71c 100644 --- a/frontend/lib/api/user.ts +++ b/frontend/lib/api/user.ts @@ -78,10 +78,10 @@ export async function createUser(userData: any) { // 사용자 수정 기능 제거됨 /** - * 사용자 상태 변경 + * 사용자 상태 변경 (부분 수정) */ export async function updateUserStatus(userId: string, status: string) { - const response = await apiClient.put(`/admin/users/${userId}/status`, { status }); + const response = await apiClient.patch(`/admin/users/${userId}/status`, { status }); return response.data; }