diff --git a/backend-node/src/controllers/adminController.ts b/backend-node/src/controllers/adminController.ts
index 1858aedb..2ebfcb28 100644
--- a/backend-node/src/controllers/adminController.ts
+++ b/backend-node/src/controllers/adminController.ts
@@ -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),
});
diff --git a/frontend/components/admin/UserToolbar.tsx b/frontend/components/admin/UserToolbar.tsx
index 4e180a84..ff3e455d 100644
--- a/frontend/components/admin/UserToolbar.tsx
+++ b/frontend/components/admin/UserToolbar.tsx
@@ -1,9 +1,8 @@
-import { Search, Plus } from "lucide-react";
+import { Search, Plus, ChevronDown, ChevronUp } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { UserSearchFilter } from "@/types/user";
-import { SEARCH_OPTIONS } from "@/constants/user";
+import { useState } from "react";
interface UserToolbarProps {
searchFilter: UserSearchFilter;
@@ -15,7 +14,7 @@ interface UserToolbarProps {
/**
* 사용자 관리 툴바 컴포넌트
- * 검색, 필터링, 액션 버튼들을 포함
+ * 통합 검색 + 고급 검색 옵션 지원
*/
export function UserToolbar({
searchFilter,
@@ -24,62 +23,197 @@ export function UserToolbar({
onSearchChange,
onCreateClick,
}: UserToolbarProps) {
+ const [showAdvancedSearch, setShowAdvancedSearch] = useState(false);
+
+ // 통합 검색어 변경
+ const handleUnifiedSearchChange = (value: string) => {
+ onSearchChange({
+ searchValue: value,
+ // 통합 검색 시 고급 검색 필드들 클리어
+ searchType: undefined,
+ search_sabun: undefined,
+ search_companyName: undefined,
+ search_deptName: undefined,
+ search_positionName: undefined,
+ search_userId: undefined,
+ search_userName: undefined,
+ search_tel: undefined,
+ search_email: undefined,
+ });
+ };
+
+ // 고급 검색 필드 변경
+ const handleAdvancedSearchChange = (field: string, value: string) => {
+ onSearchChange({
+ [field]: value,
+ // 고급 검색 시 통합 검색어 클리어
+ searchValue: undefined,
+ });
+ };
+
+ // 고급 검색 모드인지 확인
+ const isAdvancedSearchMode = !!(
+ searchFilter.search_sabun ||
+ searchFilter.search_companyName ||
+ searchFilter.search_deptName ||
+ searchFilter.search_positionName ||
+ searchFilter.search_userId ||
+ searchFilter.search_userName ||
+ searchFilter.search_tel ||
+ searchFilter.search_email
+ );
+
return (
- {/* 검색 필터 영역 */}
-
- {/* 검색 대상 선택 */}
-
-
-