171 lines
8.3 KiB
TypeScript
171 lines
8.3 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="masteryLevel">掌握程度</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">需加強 (<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>
|
|
)
|
|
} |