gongxiangp2/components/home/FilterPanel.tsx

176 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { memo, useCallback } from 'react'
export interface CategoryItem {
id: string
name: string
count: number
}
export interface MonthItem {
value: string
label: string
count: number
}
export interface FacetTotals {
materialType: number
category: number
scene: number
platform: number
productCategory: number
month: number
}
interface FilterPanelProps {
selectedMaterialType: string
setSelectedMaterialType: (value: string) => void
selectedCategory: string
setSelectedCategory: (value: string) => void
selectedScene: string
setSelectedScene: (value: string) => void
selectedPlatform: string
setSelectedPlatform: (value: string) => void
selectedProductCategory: string
setSelectedProductCategory: (value: string) => void
selectedMonth: string
setSelectedMonth: (value: string) => void
sortBy: string
setSortBy: (value: string) => void
sortOrder: 'desc' | 'asc'
setSortOrder: (value: 'desc' | 'asc') => void
materialTypes: { value: string; label: string; count?: number }[]
categories: CategoryItem[]
scenes: CategoryItem[]
platforms: CategoryItem[]
productCategories: CategoryItem[]
months: MonthItem[]
totalMaterialsCount: number
materialsCount: number
facetTotals?: FacetTotals
isFilterCollapsed: boolean
setIsFilterCollapsed: (value: boolean) => void
themeColor: string
facetsLoading?: boolean
}
function FilterPanel(props: FilterPanelProps) {
const {
selectedMaterialType, setSelectedMaterialType,
selectedCategory, setSelectedCategory,
selectedScene, setSelectedScene,
selectedPlatform, setSelectedPlatform,
selectedProductCategory, setSelectedProductCategory,
selectedMonth, setSelectedMonth,
sortBy, setSortBy, sortOrder, setSortOrder,
materialTypes, categories, scenes, platforms, productCategories, months,
totalMaterialsCount, materialsCount, facetTotals,
isFilterCollapsed, setIsFilterCollapsed, themeColor,
facetsLoading = false,
} = props
const handleSortChange = useCallback((newSortBy: string) => {
if (sortBy === newSortBy) {
setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc')
} else {
setSortBy(newSortBy)
setSortOrder('desc')
}
}, [sortBy, sortOrder, setSortBy, setSortOrder])
const totals = {
materialType: facetTotals?.materialType ?? totalMaterialsCount,
category: facetTotals?.category ?? totalMaterialsCount,
scene: facetTotals?.scene ?? totalMaterialsCount,
platform: facetTotals?.platform ?? totalMaterialsCount,
productCategory: facetTotals?.productCategory ?? totalMaterialsCount,
month: facetTotals?.month ?? totalMaterialsCount,
}
const activeFilterCount = [selectedMaterialType, selectedCategory, selectedScene, selectedPlatform, selectedProductCategory, selectedMonth].filter(Boolean).length
const btnBase = 'px-4 py-2 rounded-full text-sm font-medium transition-all'
const btnActive = 'text-white'
const btnInactive = 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'
const countText = (value: number) => (facetsLoading ? '…' : String(value))
return (
<>
<div className="mb-4">
<div className="flex items-center justify-between mb-3">
<h2 className="text-sm font-medium text-gray-700">类型分页</h2>
{selectedMaterialType && <span className="text-xs text-gray-500">{facetsLoading ? '计数加载中' : `${materialsCount} 个素材`}</span>}
</div>
<div className="flex flex-wrap gap-2" aria-busy={facetsLoading}>
<button onClick={() => setSelectedMaterialType('')} className={`${btnBase} ${selectedMaterialType === '' ? btnActive : btnInactive}`} style={selectedMaterialType === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedMaterialType === '' ? 'text-gray-300' : 'text-gray-500'}`}>({countText(totals.materialType)})</span></button>
{materialTypes.map((type) => (
<button key={type.value} onClick={() => setSelectedMaterialType(type.value === selectedMaterialType ? '' : type.value)} className={`${btnBase} ${selectedMaterialType === type.value ? btnActive : btnInactive}`} style={selectedMaterialType === type.value ? { backgroundColor: themeColor } : {}}>{type.label}<span className={`ml-1.5 text-xs ${selectedMaterialType === type.value ? 'text-gray-300' : 'text-gray-500'}`}>({countText(type.count || 0)})</span></button>
))}
{facetsLoading && materialTypes.length === 0 && [0, 1, 2, 3].map((item) => (
<span key={item} className="h-9 w-20 rounded-full bg-[#eceef0]" aria-hidden="true" />
))}
</div>
</div>
<div className="mb-4 border-t border-gray-200 pt-4">
<button onClick={() => setIsFilterCollapsed(!isFilterCollapsed)} className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors">
<svg className={`w-4 h-4 transition-transform duration-200 ${isFilterCollapsed ? '' : 'rotate-180'}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
{isFilterCollapsed ? '展开更多筛选' : '收起筛选'}
{isFilterCollapsed && activeFilterCount > 0 && <span className="ml-1 px-1.5 py-0.5 bg-blue-100 text-blue-600 rounded text-xs">{activeFilterCount}</span>}
</button>
</div>
<div className={`overflow-hidden transition-all duration-300 ${isFilterCollapsed ? 'max-h-0 opacity-0' : 'max-h-[2000px] opacity-100'}`}>
<div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700">使用店铺</h2>{selectedCategory && <span className="text-xs text-gray-500">{facetsLoading ? '计数加载中' : `${materialsCount} 个素材`}</span>}</div>
<div className="flex flex-wrap gap-2">
<button onClick={() => setSelectedCategory('')} className={`${btnBase} ${selectedCategory === '' ? btnActive : btnInactive}`} style={selectedCategory === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedCategory === '' ? 'text-gray-300' : 'text-gray-500'}`}>({countText(totals.category)})</span></button>
{categories.map((c) => <button key={c.id} onClick={() => setSelectedCategory(c.name === selectedCategory ? '' : c.name)} className={`${btnBase} ${selectedCategory === c.name ? btnActive : btnInactive}`} style={selectedCategory === c.name ? { backgroundColor: themeColor } : {}}>{c.name}<span className={`ml-1.5 text-xs ${selectedCategory === c.name ? 'text-gray-300' : 'text-gray-500'}`}>({countText(c.count || 0)})</span></button>)}
</div>
</div>
<div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700">场景类别</h2>{selectedScene && <span className="text-xs text-gray-500">{facetsLoading ? '计数加载中' : `${materialsCount} 个素材`}</span>}</div>
<div className="flex flex-wrap gap-2">
<button onClick={() => setSelectedScene('')} className={`${btnBase} ${selectedScene === '' ? btnActive : btnInactive}`} style={selectedScene === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedScene === '' ? 'text-gray-300' : 'text-gray-500'}`}>({countText(totals.scene)})</span></button>
{scenes.map((s) => <button key={s.id} onClick={() => setSelectedScene(s.name === selectedScene ? '' : s.name)} className={`${btnBase} ${selectedScene === s.name ? btnActive : btnInactive}`} style={selectedScene === s.name ? { backgroundColor: themeColor } : {}}>{s.name}<span className={`ml-1.5 text-xs ${selectedScene === s.name ? 'text-gray-300' : 'text-gray-500'}`}>({countText(s.count || 0)})</span></button>)}
</div>
</div>
{(platforms.length > 0 || facetsLoading) && <div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700">平台分类</h2>{selectedPlatform && <span className="text-xs text-gray-500">{facetsLoading ? '计数加载中' : `${materialsCount} 个素材`}</span>}</div>
<div className="flex flex-wrap gap-2">
<button onClick={() => setSelectedPlatform('')} className={`${btnBase} ${selectedPlatform === '' ? btnActive : btnInactive}`} style={selectedPlatform === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedPlatform === '' ? 'text-gray-300' : 'text-gray-500'}`}>({countText(totals.platform)})</span></button>
{platforms.map((p) => <button key={p.id} onClick={() => setSelectedPlatform(p.name === selectedPlatform ? '' : p.name)} className={`${btnBase} ${selectedPlatform === p.name ? btnActive : btnInactive}`} style={selectedPlatform === p.name ? { backgroundColor: themeColor } : {}}>{p.name}<span className={`ml-1.5 text-xs ${selectedPlatform === p.name ? 'text-gray-300' : 'text-gray-500'}`}>({countText(p.count || 0)})</span></button>)}
</div>
</div>}
{(productCategories.length > 0 || facetsLoading) && <div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700">产品类别</h2>{selectedProductCategory && <span className="text-xs text-gray-500">{facetsLoading ? '计数加载中' : `${materialsCount} 个素材`}</span>}</div>
<div className="flex flex-wrap gap-2">
<button onClick={() => setSelectedProductCategory('')} className={`${btnBase} ${selectedProductCategory === '' ? btnActive : btnInactive}`} style={selectedProductCategory === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedProductCategory === '' ? 'text-gray-300' : 'text-gray-500'}`}>({countText(totals.productCategory)})</span></button>
{productCategories.map((c) => <button key={c.id} onClick={() => setSelectedProductCategory(c.name === selectedProductCategory ? '' : c.name)} className={`${btnBase} ${selectedProductCategory === c.name ? btnActive : btnInactive}`} style={selectedProductCategory === c.name ? { backgroundColor: themeColor } : {}}>{c.name}<span className={`ml-1.5 text-xs ${selectedProductCategory === c.name ? 'text-gray-300' : 'text-gray-500'}`}>({countText(c.count || 0)})</span></button>)}
</div>
</div>}
<div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700">上传月份</h2></div>
<div className="flex flex-wrap gap-2">
<button onClick={() => setSelectedMonth('')} className={`${btnBase} ${selectedMonth === '' ? btnActive : btnInactive}`} style={selectedMonth === '' ? { backgroundColor: themeColor } : {}}>全部<span className={`ml-1.5 text-xs ${selectedMonth === '' ? 'text-gray-300' : 'text-gray-500'}`}>({countText(totals.month)})</span></button>
{months.map((m) => <button key={m.value} onClick={() => setSelectedMonth(m.value === selectedMonth ? '' : m.value)} className={`${btnBase} ${selectedMonth === m.value ? btnActive : btnInactive}`} style={selectedMonth === m.value ? { backgroundColor: themeColor } : {}}>{m.label}<span className={`ml-1.5 text-xs ${selectedMonth === m.value ? 'text-gray-300' : 'text-gray-500'}`}>({countText(m.count)})</span></button>)}
</div>
</div>
<div className="mb-4">
<div className="flex items-center justify-between mb-3"><h2 className="text-sm font-medium text-gray-700">排序方式</h2></div>
<div className="flex flex-wrap gap-2">
<button onClick={() => handleSortChange('createdAt')} className={`${btnBase} flex items-center gap-1 ${sortBy === 'createdAt' ? btnActive : btnInactive}`} style={sortBy === 'createdAt' ? { backgroundColor: themeColor } : {}}>
{sortBy === 'createdAt' && sortOrder === 'asc' ? '最早上传' : '最新上传'}
{sortBy === 'createdAt' && <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={sortOrder === 'desc' ? 'M19 9l-7 7-7-7' : 'M5 15l7-7 7 7'} /></svg>}
</button>
<button onClick={() => handleSortChange('downloadCount')} className={`${btnBase} flex items-center gap-1 ${sortBy === 'downloadCount' ? btnActive : btnInactive}`} style={sortBy === 'downloadCount' ? { backgroundColor: themeColor } : {}}>
{sortBy === 'downloadCount' && sortOrder === 'asc' ? '下载最少' : '下载最多'}
{sortBy === 'downloadCount' && <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={sortOrder === 'desc' ? 'M19 9l-7 7-7-7' : 'M5 15l7-7 7 7'} /></svg>}
</button>
</div>
<div className="text-xs text-gray-500 mt-2">可以组合使用多个筛选条件,找到更精确的素材</div>
</div>
</div>
</>
)
}
export default memo(FilterPanel)