사용자 검색 기능 구현
This commit is contained in:
@@ -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 (
|
||||
<div className="space-y-4">
|
||||
{/* 검색 필터 영역 */}
|
||||
<div className="bg-muted/30 flex flex-wrap gap-4 rounded-lg p-4">
|
||||
{/* 검색 대상 선택 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium">검색 대상</label>
|
||||
<Select
|
||||
value={searchFilter.searchType || "all"}
|
||||
onValueChange={(value) =>
|
||||
onSearchChange({
|
||||
searchType: value as (typeof SEARCH_OPTIONS)[number]["value"],
|
||||
searchValue: "", // 옵션 변경 시 항상 검색어 초기화
|
||||
})
|
||||
}
|
||||
{/* 메인 검색 영역 */}
|
||||
<div className="bg-muted/30 rounded-lg p-4">
|
||||
{/* 통합 검색 */}
|
||||
<div className="mb-4 flex items-center gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="relative">
|
||||
<Search
|
||||
className={`absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform ${
|
||||
isSearching ? "animate-pulse text-blue-500" : "text-muted-foreground"
|
||||
}`}
|
||||
/>
|
||||
<Input
|
||||
placeholder="통합 검색..."
|
||||
value={searchFilter.searchValue || ""}
|
||||
onChange={(e) => handleUnifiedSearchChange(e.target.value)}
|
||||
disabled={isAdvancedSearchMode}
|
||||
className={`pl-10 ${isSearching ? "border-blue-300 ring-1 ring-blue-200" : ""} ${
|
||||
isAdvancedSearchMode ? "bg-muted text-muted-foreground cursor-not-allowed" : ""
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
{isSearching && <p className="mt-1 text-xs text-blue-500">검색 중...</p>}
|
||||
{isAdvancedSearchMode && (
|
||||
<p className="mt-1 text-xs text-amber-600">
|
||||
고급 검색 모드가 활성화되어 있습니다. 통합 검색을 사용하려면 고급 검색 조건을 초기화하세요.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 고급 검색 토글 버튼 */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowAdvancedSearch(!showAdvancedSearch)}
|
||||
className="gap-2"
|
||||
>
|
||||
<SelectTrigger className="w-[140px]">
|
||||
<SelectValue placeholder="전체" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{SEARCH_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
🔍 고급 검색
|
||||
{showAdvancedSearch ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 검색어 입력 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium">
|
||||
검색어
|
||||
{isSearching && <span className="ml-1 text-xs text-blue-500">(검색 중...)</span>}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Search
|
||||
className={`absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform ${
|
||||
isSearching ? "animate-pulse text-blue-500" : "text-muted-foreground"
|
||||
}`}
|
||||
/>
|
||||
<Input
|
||||
placeholder={
|
||||
(searchFilter.searchType || "all") === "all"
|
||||
? "전체 목록을 조회합니다"
|
||||
: `${SEARCH_OPTIONS.find((opt) => opt.value === (searchFilter.searchType || "all"))?.label || "전체"}을 입력하세요`
|
||||
}
|
||||
value={searchFilter.searchValue || ""}
|
||||
onChange={(e) => onSearchChange({ searchValue: e.target.value })}
|
||||
disabled={(searchFilter.searchType || "all") === "all"}
|
||||
className={`w-[300px] pl-10 ${isSearching ? "border-blue-300 ring-1 ring-blue-200" : ""} ${
|
||||
(searchFilter.searchType || "all") === "all" ? "bg-muted text-muted-foreground cursor-not-allowed" : ""
|
||||
}`}
|
||||
/>
|
||||
{/* 고급 검색 옵션 */}
|
||||
{showAdvancedSearch && (
|
||||
<div className="border-t pt-4">
|
||||
<div className="mb-3">
|
||||
<h4 className="text-sm font-medium">고급 검색 옵션</h4>
|
||||
<span className="text-muted-foreground text-xs">(각 필드별로 개별 검색 조건을 설정할 수 있습니다)</span>
|
||||
</div>
|
||||
|
||||
{/* 고급 검색 필드들 */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-xs font-medium">사번</label>
|
||||
<Input
|
||||
placeholder="사번 검색"
|
||||
value={searchFilter.search_sabun || ""}
|
||||
onChange={(e) => handleAdvancedSearchChange("search_sabun", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-xs font-medium">회사명</label>
|
||||
<Input
|
||||
placeholder="회사명 검색"
|
||||
value={searchFilter.search_companyName || ""}
|
||||
onChange={(e) => handleAdvancedSearchChange("search_companyName", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-xs font-medium">부서명</label>
|
||||
<Input
|
||||
placeholder="부서명 검색"
|
||||
value={searchFilter.search_deptName || ""}
|
||||
onChange={(e) => handleAdvancedSearchChange("search_deptName", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-xs font-medium">직책</label>
|
||||
<Input
|
||||
placeholder="직책 검색"
|
||||
value={searchFilter.search_positionName || ""}
|
||||
onChange={(e) => handleAdvancedSearchChange("search_positionName", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-xs font-medium">사용자 ID</label>
|
||||
<Input
|
||||
placeholder="사용자 ID 검색"
|
||||
value={searchFilter.search_userId || ""}
|
||||
onChange={(e) => handleAdvancedSearchChange("search_userId", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-xs font-medium">사용자명</label>
|
||||
<Input
|
||||
placeholder="사용자명 검색"
|
||||
value={searchFilter.search_userName || ""}
|
||||
onChange={(e) => handleAdvancedSearchChange("search_userName", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-xs font-medium">전화번호</label>
|
||||
<Input
|
||||
placeholder="전화번호/휴대폰 검색"
|
||||
value={searchFilter.search_tel || ""}
|
||||
onChange={(e) => handleAdvancedSearchChange("search_tel", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-muted-foreground mb-1 block text-xs font-medium">이메일</label>
|
||||
<Input
|
||||
placeholder="이메일 검색"
|
||||
value={searchFilter.search_email || ""}
|
||||
onChange={(e) => handleAdvancedSearchChange("search_email", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 고급 검색 초기화 버튼 */}
|
||||
{isAdvancedSearchMode && (
|
||||
<div className="mt-4 border-t pt-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() =>
|
||||
onSearchChange({
|
||||
search_sabun: undefined,
|
||||
search_companyName: undefined,
|
||||
search_deptName: undefined,
|
||||
search_positionName: undefined,
|
||||
search_userId: undefined,
|
||||
search_userName: undefined,
|
||||
search_tel: undefined,
|
||||
search_email: undefined,
|
||||
})
|
||||
}
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
고급 검색 조건 초기화
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 액션 버튼 영역 */}
|
||||
|
||||
Reference in New Issue
Block a user