250 lines
9.3 KiB
TypeScript
250 lines
9.3 KiB
TypeScript
import React from 'react';
|
||
import { ModelStatus, Gender, ModelSortBy, SortOrder } from '../types/model';
|
||
import { MagnifyingGlassIcon, FunnelIcon, ArrowUpIcon, ArrowDownIcon } from '@heroicons/react/24/outline';
|
||
|
||
import { CustomSelect } from './CustomSelect'
|
||
interface ModelSearchProps {
|
||
searchQuery: string;
|
||
onSearchChange: (query: string) => void;
|
||
statusFilter: ModelStatus | 'all';
|
||
onStatusFilterChange: (status: ModelStatus | 'all') => void;
|
||
genderFilter: Gender | 'all';
|
||
onGenderFilterChange: (gender: Gender | 'all') => void;
|
||
sortBy: ModelSortBy;
|
||
onSortByChange: (sortBy: ModelSortBy) => void;
|
||
sortOrder: SortOrder;
|
||
onSortOrderChange: (order: SortOrder) => void;
|
||
}
|
||
|
||
const ModelSearch: React.FC<ModelSearchProps> = ({
|
||
searchQuery,
|
||
onSearchChange,
|
||
statusFilter,
|
||
onStatusFilterChange,
|
||
genderFilter,
|
||
onGenderFilterChange,
|
||
sortBy,
|
||
onSortByChange,
|
||
sortOrder,
|
||
onSortOrderChange
|
||
}) => {
|
||
const getStatusText = (status: ModelStatus | 'all') => {
|
||
switch (status) {
|
||
case 'all':
|
||
return '全部状态';
|
||
case ModelStatus.Active:
|
||
return '活跃';
|
||
case ModelStatus.Inactive:
|
||
return '不活跃';
|
||
case ModelStatus.Retired:
|
||
return '退役';
|
||
case ModelStatus.Suspended:
|
||
return '暂停';
|
||
default:
|
||
return '未知';
|
||
}
|
||
};
|
||
|
||
const getGenderText = (gender: Gender | 'all') => {
|
||
switch (gender) {
|
||
case 'all':
|
||
return '全部性别';
|
||
case Gender.Male:
|
||
return '男';
|
||
case Gender.Female:
|
||
return '女';
|
||
case Gender.Other:
|
||
return '其他';
|
||
default:
|
||
return '未知';
|
||
}
|
||
};
|
||
|
||
const getSortByText = (sortBy: ModelSortBy) => {
|
||
switch (sortBy) {
|
||
case ModelSortBy.Name:
|
||
return '姓名';
|
||
case ModelSortBy.CreatedAt:
|
||
return '创建时间';
|
||
case ModelSortBy.UpdatedAt:
|
||
return '更新时间';
|
||
case ModelSortBy.Rating:
|
||
return '评分';
|
||
case ModelSortBy.Age:
|
||
return '年龄';
|
||
case ModelSortBy.Height:
|
||
return '身高';
|
||
default:
|
||
return '创建时间';
|
||
}
|
||
};
|
||
|
||
|
||
|
||
return (
|
||
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-4 animate-fade-in">
|
||
<div className="flex flex-col lg:flex-row gap-4">
|
||
{/* 搜索框 */}
|
||
<div className="flex-1">
|
||
<div className="relative">
|
||
<MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||
<input
|
||
type="text"
|
||
placeholder="搜索模特姓名、艺名或标签..."
|
||
value={searchQuery}
|
||
onChange={(e) => onSearchChange(e.target.value)}
|
||
className="w-full pl-10 pr-3 py-2 border border-gray-200 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200 placeholder-gray-400 text-sm text-gray-900 hover:border-gray-300"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 过滤器 */}
|
||
<div className="flex flex-wrap gap-3">
|
||
{/* 状态过滤 */}
|
||
<div className="flex items-center gap-2">
|
||
<div className="flex items-center gap-1.5 text-xs font-medium text-gray-700">
|
||
<FunnelIcon className="h-3.5 w-3.5 text-gray-500" />
|
||
状态
|
||
</div>
|
||
<CustomSelect
|
||
value={statusFilter}
|
||
onChange={(value) => onStatusFilterChange(value as ModelStatus | 'all')}
|
||
options={[
|
||
{ value: 'all', label: '全部状态' },
|
||
{ value: ModelStatus.Active, label: '活跃' },
|
||
{ value: ModelStatus.Inactive, label: '不活跃' },
|
||
{ value: ModelStatus.Retired, label: '退役' },
|
||
{ value: ModelStatus.Suspended, label: '暂停' }
|
||
]}
|
||
className="min-w-[100px]"
|
||
/>
|
||
</div>
|
||
|
||
{/* 性别过滤 */}
|
||
<div className="flex items-center gap-2">
|
||
<div className="text-xs font-medium text-gray-700">性别</div>
|
||
<CustomSelect
|
||
value={genderFilter}
|
||
onChange={(value) => onGenderFilterChange(value as Gender | 'all')}
|
||
options={[
|
||
{ value: 'all', label: '全部性别' },
|
||
{ value: Gender.Male, label: '男' },
|
||
{ value: Gender.Female, label: '女' },
|
||
{ value: Gender.Other, label: '其他' }
|
||
]}
|
||
className="min-w-[80px]"
|
||
/>
|
||
</div>
|
||
|
||
{/* 排序选择 */}
|
||
<div className="flex items-center gap-2">
|
||
<div className="text-xs font-medium text-gray-700">排序</div>
|
||
<div className="flex items-center gap-1.5">
|
||
<CustomSelect
|
||
value={sortBy}
|
||
onChange={(value) => onSortByChange(value as ModelSortBy)}
|
||
options={[
|
||
{ value: ModelSortBy.CreatedAt, label: '创建时间' },
|
||
{ value: ModelSortBy.UpdatedAt, label: '更新时间' },
|
||
{ value: ModelSortBy.Name, label: '姓名' },
|
||
{ value: ModelSortBy.Rating, label: '评分' },
|
||
{ value: ModelSortBy.Age, label: '年龄' },
|
||
{ value: ModelSortBy.Height, label: '身高' }
|
||
]}
|
||
className="min-w-[100px]"
|
||
/>
|
||
|
||
{/* 排序方向 */}
|
||
<button
|
||
onClick={() => onSortOrderChange(sortOrder === SortOrder.Asc ? SortOrder.Desc : SortOrder.Asc)}
|
||
className="flex items-center justify-center w-8 h-8 border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all duration-200"
|
||
title={sortOrder === SortOrder.Asc ? '当前升序,点击切换为降序' : '当前降序,点击切换为升序'}
|
||
>
|
||
{sortOrder === SortOrder.Asc ? (
|
||
<ArrowUpIcon className="h-4 w-4 text-gray-600" />
|
||
) : (
|
||
<ArrowDownIcon className="h-4 w-4 text-gray-600" />
|
||
)}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 活动过滤器显示 */}
|
||
{(searchQuery || statusFilter !== 'all' || genderFilter !== 'all') && (
|
||
<div className="mt-4 pt-3 border-t border-gray-100">
|
||
<div className="flex flex-wrap items-center gap-2">
|
||
<span className="text-xs font-medium text-gray-700">当前筛选:</span>
|
||
|
||
{searchQuery && (
|
||
<span className="inline-flex items-center gap-1.5 px-2 py-1 bg-primary-50 text-primary-700 text-xs rounded-md border border-primary-200">
|
||
<MagnifyingGlassIcon className="h-3 w-3" />
|
||
搜索: {searchQuery}
|
||
<button
|
||
onClick={() => onSearchChange('')}
|
||
className="ml-0.5 text-primary-600 hover:text-primary-800 transition-colors"
|
||
title="清除搜索"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
)}
|
||
|
||
{statusFilter !== 'all' && (
|
||
<span className="inline-flex items-center gap-1.5 px-2 py-1 bg-green-50 text-green-700 text-xs rounded-md border border-green-200">
|
||
<FunnelIcon className="h-3 w-3" />
|
||
状态: {getStatusText(statusFilter)}
|
||
<button
|
||
onClick={() => onStatusFilterChange('all')}
|
||
className="ml-0.5 text-green-600 hover:text-green-800 transition-colors"
|
||
title="清除状态筛选"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
)}
|
||
|
||
{genderFilter !== 'all' && (
|
||
<span className="inline-flex items-center gap-1.5 px-2 py-1 bg-purple-50 text-purple-700 text-xs rounded-md border border-purple-200">
|
||
性别: {getGenderText(genderFilter)}
|
||
<button
|
||
onClick={() => onGenderFilterChange('all')}
|
||
className="ml-0.5 text-purple-600 hover:text-purple-800 transition-colors"
|
||
title="清除性别筛选"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
)}
|
||
|
||
{/* 清除所有过滤器 */}
|
||
<button
|
||
onClick={() => {
|
||
onSearchChange('');
|
||
onStatusFilterChange('all');
|
||
onGenderFilterChange('all');
|
||
}}
|
||
className="inline-flex items-center gap-1.5 px-3 py-1 text-xs text-gray-600 hover:text-gray-800 border border-gray-200 rounded-md hover:bg-gray-50 hover:border-gray-300 transition-all duration-200"
|
||
>
|
||
清除所有筛选
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 排序信息显示 */}
|
||
<div className="mt-3 pt-2 border-t border-gray-100">
|
||
<div className="flex items-center gap-1.5 text-xs text-gray-500">
|
||
<span>排序方式:</span>
|
||
<span className="font-medium text-gray-700">
|
||
{getSortByText(sortBy)} {sortOrder === SortOrder.Asc ? '升序' : '降序'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ModelSearch;
|