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;
}