dramaling-vocab-learning/frontend/components/flashcards/SearchControls.tsx

170 lines
8.2 KiB
TypeScript

import React from 'react'
import Link from 'next/link'
import { SearchActions } from '@/hooks/flashcards/useFlashcardSearch'
interface SearchControlsProps {
searchState: any
searchActions: SearchActions
showAdvancedSearch: boolean
setShowAdvancedSearch: (show: boolean) => void
}
export const SearchControls: React.FC<SearchControlsProps> = ({
searchState,
searchActions,
showAdvancedSearch,
setShowAdvancedSearch
}) => {
return (
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-4 gap-3 sm:gap-0">
<h2 className="text-lg font-semibold text-gray-900"></h2>
<div className="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-4">
{/* 排序控件 */}
<div className="flex items-center gap-2">
<span className="text-sm text-gray-600 hidden sm:inline">:</span>
<select
value={searchState.sorting.sortBy}
onChange={(e) => searchActions.updateSorting({ sortBy: e.target.value })}
className="text-sm border border-gray-300 rounded-md px-3 py-1 focus:ring-2 focus:ring-primary focus:border-primary"
>
<option value="createdAt"></option>
<option value="word"></option>
<option value="cefr">CEFR等級</option>
<option value="timesReviewed"></option>
</select>
<button
onClick={searchActions.toggleSortOrder}
className="p-1 text-gray-500 hover:text-gray-700 transition-colors"
title={searchState.sorting.sortOrder === 'asc' ? '升序 (點擊改為降序)' : '降序 (點擊改為升序)'}
>
<svg className={`w-4 h-4 transition-transform ${searchState.sorting.sortOrder === 'desc' ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 11l5-5m0 0l5 5m-5-5v12" />
</svg>
</button>
</div>
<button
onClick={() => setShowAdvancedSearch(!showAdvancedSearch)}
className="text-sm text-blue-600 hover:text-blue-700 font-medium flex items-center gap-1"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4" />
</svg>
{showAdvancedSearch ? '收起篩選' : '進階篩選'}
</button>
</div>
</div>
{/* 主要搜尋框 */}
<div className="relative mb-4">
<input
type="text"
value={searchState.filters.search}
onChange={(e) => searchActions.updateFilters({ search: e.target.value })}
placeholder="搜尋詞彙、翻譯或定義..."
className="w-full pl-12 pr-20 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-base"
onKeyDown={(e) => {
if (e.key === 'Escape') {
searchActions.clearFilters()
}
}}
/>
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<svg className="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
{(searchState.filters.search || (searchState as any).hasActiveFilters) && (
<div className="absolute inset-y-0 right-0 pr-3 flex items-center">
<button
onClick={searchActions.clearFilters}
className="text-gray-400 hover:text-gray-600 p-1 rounded-full hover:bg-gray-100 transition-colors"
title="清除搜尋"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
)}
</div>
{/* 進階篩選面板 */}
{showAdvancedSearch && (
<div className="bg-gray-50 rounded-lg p-4 space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{/* CEFR等級篩選 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">CEFR等級</label>
<select
value={searchState.filters.cefr}
onChange={(e) => searchActions.updateFilters({ cefr: e.target.value })}
className="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary focus:border-primary"
>
<option value=""></option>
<option value="A1">A1 - </option>
<option value="A2">A2 - </option>
<option value="B1">B1 - </option>
<option value="B2">B2 - </option>
<option value="C1">C1 - </option>
<option value="C2">C2 - </option>
</select>
</div>
{/* 詞性篩選 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2"></label>
<select
value={searchState.filters.partOfSpeech}
onChange={(e) => searchActions.updateFilters({ partOfSpeech: e.target.value })}
className="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary focus:border-primary"
>
<option value=""></option>
<option value="noun"> (noun)</option>
<option value="verb"> (verb)</option>
<option value="adjective"> (adjective)</option>
<option value="adverb"> (adverb)</option>
<option value="preposition"> (preposition)</option>
<option value="interjection"> (interjection)</option>
<option value="phrase"> (phrase)</option>
</select>
</div>
{/* 掌握度篩選 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2"></label>
<select
value={searchState.filters.masteryLevel}
onChange={(e) => searchActions.updateFilters({ masteryLevel: e.target.value })}
className="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary focus:border-primary"
>
<option value=""></option>
<option value="high"> (80%+)</option>
<option value="medium"> (60-79%)</option>
<option value="low"> (&lt;60%)</option>
</select>
</div>
{/* 收藏篩選 */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2"></label>
<label className="flex items-center cursor-pointer">
<input
type="checkbox"
checked={searchState.filters.favoritesOnly}
onChange={(e) => searchActions.updateFilters({ favoritesOnly: e.target.checked })}
className="w-4 h-4 text-yellow-600 bg-gray-100 border-gray-300 rounded focus:ring-yellow-500"
/>
<span className="ml-2 text-sm text-gray-700 flex items-center gap-1">
<span className="text-yellow-500"></span>
</span>
</label>
</div>
</div>
</div>
)}
</div>
)
}