사용자 상태 변경 구현

This commit is contained in:
dohyeons 2025-08-26 09:56:45 +09:00
parent b43a88a045
commit 4f6be8f551
5 changed files with 143 additions and 11 deletions

View File

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

View File

@ -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 중복 체크

View File

@ -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} 상태 토글`}
/>
<span
className={`text-sm font-medium ${user.status === "active" ? "text-blue-600" : "text-gray-500"}`}
>
{user.status === "active" ? "활성" : "비활성"}
</span>
</div>
</TableCell>
<TableCell>

View File

@ -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 || "상태 변경에 실패했습니다.");

View File

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