사용자 검색 기능 구현

This commit is contained in:
dohyeons
2025-08-26 14:23:22 +09:00
parent 6f68fa5639
commit 7267cc52eb
5 changed files with 473 additions and 153 deletions

View File

@@ -174,13 +174,30 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => {
user: req.user,
});
const { page = 1, countPerPage = 20, search, deptCode, status } = req.query;
const {
page = 1,
countPerPage = 20,
// 통합 검색 (전체 필드 대상)
search,
// 고급 검색 (개별 필드별)
searchField,
searchValue,
search_sabun,
search_companyName,
search_deptName,
search_positionName,
search_userId,
search_userName,
search_tel,
search_email,
// 기존 필터
deptCode,
status,
} = req.query;
// PostgreSQL 클라이언트 생성
const client = new Client({
connectionString:
process.env.DATABASE_URL ||
"postgresql://postgres:postgres@localhost:5432/ilshin",
connectionString: config.databaseUrl,
});
await client.connect();
@@ -214,27 +231,109 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => {
const queryParams: any[] = [];
let paramIndex = 1;
let searchType = "none";
// 검색 조건 처리
if (search && typeof search === "string" && search.trim()) {
// 통합 검색 (우선순위: 모든 주요 필드에서 검색)
searchType = "unified";
const searchTerm = search.trim();
// 검색 조건 추가
if (search) {
query += ` AND (
u.user_name ILIKE $${paramIndex} OR
u.user_id ILIKE $${paramIndex} OR
u.sabun ILIKE $${paramIndex} OR
u.email ILIKE $${paramIndex}
UPPER(COALESCE(u.sabun, '')) LIKE UPPER($${paramIndex}) OR
UPPER(COALESCE(u.user_type_name, '')) LIKE UPPER($${paramIndex}) OR
UPPER(COALESCE(u.dept_name, '')) LIKE UPPER($${paramIndex}) OR
UPPER(COALESCE(u.position_name, '')) LIKE UPPER($${paramIndex}) OR
UPPER(COALESCE(u.user_id, '')) LIKE UPPER($${paramIndex}) OR
UPPER(COALESCE(u.user_name, '')) LIKE UPPER($${paramIndex}) OR
UPPER(COALESCE(u.tel, '')) LIKE UPPER($${paramIndex}) OR
UPPER(COALESCE(u.cell_phone, '')) LIKE UPPER($${paramIndex}) OR
UPPER(COALESCE(u.email, '')) LIKE UPPER($${paramIndex})
)`;
queryParams.push(`%${search}%`);
queryParams.push(`%${searchTerm}%`);
paramIndex++;
logger.info("통합 검색 실행", { searchTerm });
} else if (searchField && searchValue) {
// 단일 필드 검색
searchType = "single";
const fieldMap: { [key: string]: string } = {
sabun: "u.sabun",
companyName: "u.user_type_name",
deptName: "u.dept_name",
positionName: "u.position_name",
userId: "u.user_id",
userName: "u.user_name",
tel: "u.tel",
cellPhone: "u.cell_phone",
email: "u.email",
};
if (fieldMap[searchField as string]) {
if (searchField === "tel") {
// 전화번호는 TEL과 CELL_PHONE 모두 검색
query += ` AND (UPPER(u.tel) LIKE UPPER($${paramIndex}) OR UPPER(u.cell_phone) LIKE UPPER($${paramIndex}))`;
} else {
query += ` AND UPPER(${fieldMap[searchField as string]}) LIKE UPPER($${paramIndex})`;
}
queryParams.push(`%${searchValue}%`);
paramIndex++;
logger.info("단일 필드 검색 실행", { searchField, searchValue });
}
} else {
// 고급 검색 (개별 필드별 AND 조건)
const advancedSearchFields = [
{ param: search_sabun, field: "u.sabun" },
{ param: search_companyName, field: "u.user_type_name" },
{ param: search_deptName, field: "u.dept_name" },
{ param: search_positionName, field: "u.position_name" },
{ param: search_userId, field: "u.user_id" },
{ param: search_userName, field: "u.user_name" },
{ param: search_email, field: "u.email" },
];
let hasAdvancedSearch = false;
for (const { param, field } of advancedSearchFields) {
if (param && typeof param === "string" && param.trim()) {
query += ` AND UPPER(${field}) LIKE UPPER($${paramIndex})`;
queryParams.push(`%${param.trim()}%`);
paramIndex++;
hasAdvancedSearch = true;
}
}
// 전화번호 검색 (TEL 또는 CELL_PHONE)
if (search_tel && typeof search_tel === "string" && search_tel.trim()) {
query += ` AND (UPPER(u.tel) LIKE UPPER($${paramIndex}) OR UPPER(u.cell_phone) LIKE UPPER($${paramIndex}))`;
queryParams.push(`%${search_tel.trim()}%`);
paramIndex++;
hasAdvancedSearch = true;
}
if (hasAdvancedSearch) {
searchType = "advanced";
logger.info("고급 검색 실행", {
search_sabun,
search_companyName,
search_deptName,
search_positionName,
search_userId,
search_userName,
search_tel,
search_email,
});
}
}
// 부서 코드 필터
// 기존 필터
if (deptCode) {
query += ` AND u.dept_code = $${paramIndex}`;
queryParams.push(deptCode);
paramIndex++;
}
// 상태 필터
if (status) {
query += ` AND u.status = $${paramIndex}`;
queryParams.push(status);
@@ -244,26 +343,15 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => {
// 정렬
query += ` ORDER BY u.regdate DESC, u.user_name`;
// 총 개수 조회
const countQuery = `
SELECT COUNT(*) as total
FROM user_info u
WHERE 1=1
${
search
? `AND (
u.user_name ILIKE $1 OR
u.user_id ILIKE $1 OR
u.sabun ILIKE $1 OR
u.email ILIKE $1
)`
: ""
}
${deptCode ? `AND u.dept_code = $${search ? 2 : 1}` : ""}
${status ? `AND u.status = $${search ? (deptCode ? 3 : 2) : deptCode ? 2 : 1}` : ""}
`;
// 페이징 파라미터 제외한 카운트용 파라미터
const countParams = [...queryParams];
// 총 개수 조회를 위해 기존 쿼리를 COUNT로 변환
const countQuery = query.replace(
/SELECT[\s\S]*?FROM/i,
"SELECT COUNT(*) as total FROM"
).replace(/ORDER BY.*$/i, "");
const countParams = queryParams.slice(0, -2); // 페이징 파라미터 제외
const countResult = await client.query(countQuery, countParams);
const totalCount = parseInt(countResult.rows[0].total);
@@ -360,14 +448,13 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => {
const response = {
success: true,
data: {
users: processedUsers,
pagination: {
currentPage: Number(page),
countPerPage: Number(countPerPage),
totalCount: totalCount,
totalPages: Math.ceil(totalCount / Number(countPerPage)),
},
data: processedUsers,
total: totalCount,
searchType, // 검색 타입 정보 (unified, single, advanced, none)
pagination: {
page: Number(page),
limit: Number(countPerPage),
totalPages: Math.ceil(totalCount / Number(countPerPage)),
},
message: "사용자 목록 조회 성공",
};
@@ -375,6 +462,7 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => {
logger.info("사용자 목록 조회 성공", {
totalCount,
returnedCount: processedUsers.length,
searchType,
currentPage: Number(page),
countPerPage: Number(countPerPage),
});