- Integrated multi-language functionality across various user management components, including user list, roles list, and user authorization pages, enhancing accessibility for diverse users. - Updated UI elements to utilize translation keys, ensuring that all text is dynamically translated based on user preferences. - Improved error handling messages to be localized, providing a better user experience in case of issues. These changes significantly enhance the usability and internationalization of the user management features, making the application more inclusive.
240 lines
9.0 KiB
TypeScript
240 lines
9.0 KiB
TypeScript
import { Search, Plus, ChevronDown, ChevronUp } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { UserSearchFilter } from "@/types/user";
|
|
import { useState } from "react";
|
|
|
|
interface UserToolbarProps {
|
|
searchFilter: UserSearchFilter;
|
|
totalCount: number;
|
|
isSearching?: boolean;
|
|
onSearchChange: (searchFilter: Partial<UserSearchFilter>) => void;
|
|
onCreateClick: () => void;
|
|
t?: (key: string, params?: Record<string, string | number>) => string;
|
|
}
|
|
|
|
/**
|
|
* 사용자 관리 툴바 컴포넌트
|
|
* 통합 검색 + 고급 검색 옵션 지원
|
|
*/
|
|
export function UserToolbar({
|
|
searchFilter,
|
|
totalCount,
|
|
isSearching = false,
|
|
onSearchChange,
|
|
onCreateClick,
|
|
t: tProp,
|
|
}: UserToolbarProps) {
|
|
// 다국어 함수 (prop이 없으면 한국어 기본값 사용)
|
|
const _t = tProp || ((key: string) => {
|
|
const defaults: Record<string, string> = {
|
|
"toolbar.search.placeholder": "통합 검색...",
|
|
"toolbar.searching": "검색 중...",
|
|
"toolbar.advanced.search": "고급 검색",
|
|
"toolbar.advanced.search.title": "고급 검색 옵션",
|
|
"toolbar.advanced.search.desc": "각 필드별로 개별 검색 조건을 설정할 수 있습니다",
|
|
"toolbar.advanced.mode.warning":
|
|
"고급 검색 모드가 활성화되어 있습니다. 통합 검색을 사용하려면 고급 검색 조건을 초기화하세요.",
|
|
"toolbar.advanced.reset": "고급 검색 조건 초기화",
|
|
"toolbar.total.count": "총",
|
|
"toolbar.total.unit": "명",
|
|
"toolbar.create.user": "사용자 등록",
|
|
"toolbar.search.company": "회사명 검색",
|
|
"toolbar.search.dept": "부서명 검색",
|
|
"toolbar.search.position": "직책 검색",
|
|
"toolbar.search.userId": "사용자 ID 검색",
|
|
"toolbar.search.userName": "사용자명 검색",
|
|
"toolbar.search.tel": "전화번호/휴대폰 검색",
|
|
"toolbar.search.email": "이메일 검색",
|
|
};
|
|
return defaults[key] || key;
|
|
});
|
|
const [showAdvancedSearch, setShowAdvancedSearch] = useState(false);
|
|
|
|
// 통합 검색어 변경
|
|
const handleV2SearchChange = (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="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
|
{/* 검색 영역 */}
|
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-center">
|
|
<div className="w-full sm:w-[400px]">
|
|
<div className="relative">
|
|
<Search
|
|
className={`absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 ${
|
|
isSearching ? "animate-pulse text-primary" : "text-muted-foreground"
|
|
}`}
|
|
/>
|
|
<Input
|
|
placeholder={_t("toolbar.search.placeholder")}
|
|
value={searchFilter.searchValue || ""}
|
|
onChange={(e) => handleV2SearchChange(e.target.value)}
|
|
disabled={isAdvancedSearchMode}
|
|
autoComplete="off"
|
|
className={`h-10 pl-10 text-sm ${
|
|
isSearching ? "border-primary ring-2 ring-primary/20" : ""
|
|
} ${isAdvancedSearchMode ? "cursor-not-allowed bg-muted text-muted-foreground" : ""}`}
|
|
/>
|
|
</div>
|
|
{isSearching && <p className="mt-1.5 text-xs text-primary">{_t("toolbar.searching")}</p>}
|
|
{isAdvancedSearchMode && (
|
|
<p className="mt-1.5 text-xs text-warning">
|
|
{_t("toolbar.advanced.mode.warning")}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 고급 검색 토글 버튼 */}
|
|
<Button
|
|
variant="outline"
|
|
size="default"
|
|
onClick={() => setShowAdvancedSearch(!showAdvancedSearch)}
|
|
className="h-10 gap-2 text-sm font-medium"
|
|
>
|
|
{_t("toolbar.advanced.search")}
|
|
{showAdvancedSearch ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 액션 버튼 영역 */}
|
|
<div className="flex items-center gap-4">
|
|
{/* 조회 결과 정보 */}
|
|
<div className="text-sm text-muted-foreground">
|
|
{_t("toolbar.total.count")} <span className="font-semibold text-foreground">{totalCount.toLocaleString()}</span> {_t("toolbar.total.unit")}
|
|
</div>
|
|
|
|
{/* 사용자 등록 버튼 */}
|
|
<Button onClick={onCreateClick} size="default" className="h-10 gap-2 text-sm font-medium">
|
|
<Plus className="h-4 w-4" />
|
|
{_t("toolbar.create.user")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 고급 검색 옵션 */}
|
|
{showAdvancedSearch && (
|
|
<div className="space-y-4">
|
|
<div className="space-y-1">
|
|
<h4 className="text-sm font-semibold">{_t("toolbar.advanced.search.title")}</h4>
|
|
<p className="text-xs text-muted-foreground">{_t("toolbar.advanced.search.desc")}</p>
|
|
</div>
|
|
|
|
{/* 고급 검색 필드들 */}
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
<Input
|
|
placeholder={_t("toolbar.search.company")}
|
|
value={searchFilter.search_companyName || ""}
|
|
onChange={(e) => handleAdvancedSearchChange("search_companyName", e.target.value)}
|
|
className="h-10 text-sm"
|
|
/>
|
|
|
|
<Input
|
|
placeholder={_t("toolbar.search.dept")}
|
|
value={searchFilter.search_deptName || ""}
|
|
onChange={(e) => handleAdvancedSearchChange("search_deptName", e.target.value)}
|
|
className="h-10 text-sm"
|
|
/>
|
|
|
|
<Input
|
|
placeholder={_t("toolbar.search.position")}
|
|
value={searchFilter.search_positionName || ""}
|
|
onChange={(e) => handleAdvancedSearchChange("search_positionName", e.target.value)}
|
|
className="h-10 text-sm"
|
|
/>
|
|
|
|
<Input
|
|
placeholder={_t("toolbar.search.userId")}
|
|
value={searchFilter.search_userId || ""}
|
|
onChange={(e) => handleAdvancedSearchChange("search_userId", e.target.value)}
|
|
className="h-10 text-sm"
|
|
/>
|
|
|
|
<Input
|
|
placeholder={_t("toolbar.search.userName")}
|
|
value={searchFilter.search_userName || ""}
|
|
onChange={(e) => handleAdvancedSearchChange("search_userName", e.target.value)}
|
|
className="h-10 text-sm"
|
|
/>
|
|
|
|
<Input
|
|
placeholder={_t("toolbar.search.tel")}
|
|
value={searchFilter.search_tel || ""}
|
|
onChange={(e) => handleAdvancedSearchChange("search_tel", e.target.value)}
|
|
className="h-10 text-sm"
|
|
/>
|
|
|
|
<Input
|
|
placeholder={_t("toolbar.search.email")}
|
|
value={searchFilter.search_email || ""}
|
|
onChange={(e) => handleAdvancedSearchChange("search_email", e.target.value)}
|
|
className="h-10 text-sm"
|
|
/>
|
|
</div>
|
|
|
|
{/* 고급 검색 초기화 버튼 */}
|
|
{isAdvancedSearchMode && (
|
|
<div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
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="h-9 text-sm text-muted-foreground hover:text-foreground"
|
|
>
|
|
{_t("toolbar.advanced.reset")}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|