Refacto UX
This commit is contained in:
@@ -22,7 +22,6 @@ export default function FilterPanel({
|
||||
}: FilterPanelProps) {
|
||||
const [localFilters, setLocalFilters] = useState<FilterParams>(filters)
|
||||
|
||||
// Update local filters when parent changes
|
||||
useEffect(() => {
|
||||
setLocalFilters(filters)
|
||||
}, [filters])
|
||||
@@ -39,208 +38,156 @@ export default function FilterPanel({
|
||||
onFiltersChange(emptyFilters)
|
||||
}
|
||||
|
||||
const hasActiveFilters = Object.keys(localFilters).length > 0
|
||||
const hasActiveFilters = Object.keys(localFilters).filter(key =>
|
||||
localFilters[key as keyof FilterParams] !== undefined &&
|
||||
localFilters[key as keyof FilterParams] !== ""
|
||||
).length > 0
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-4 mb-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Filters</h2>
|
||||
{hasActiveFilters && (
|
||||
<button
|
||||
onClick={clearFilters}
|
||||
className="text-sm text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
Clear all
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* Genre Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Genre
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.genre || ""}
|
||||
onChange={(e) => handleFilterChange("genre", e.target.value || undefined)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">All Genres</option>
|
||||
{availableGenres.map((genre) => (
|
||||
<option key={genre} value={genre}>
|
||||
{genre}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Mood Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Mood
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.mood || ""}
|
||||
onChange={(e) => handleFilterChange("mood", e.target.value || undefined)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">All Moods</option>
|
||||
{availableMoods.map((mood) => (
|
||||
<option key={mood} value={mood}>
|
||||
{mood}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Instrument Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Instrument
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.instrument || ""}
|
||||
onChange={(e) => handleFilterChange("instrument", e.target.value || undefined)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">All Instruments</option>
|
||||
{availableInstruments.map((instrument) => (
|
||||
<option key={instrument} value={instrument}>
|
||||
{instrument}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Key Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Key
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.key || ""}
|
||||
onChange={(e) => handleFilterChange("key", e.target.value || undefined)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">All Keys</option>
|
||||
{availableKeys.map((key) => (
|
||||
<option key={key} value={key}>
|
||||
{key}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Tempo Range Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Tempo
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.tempo_range || ""}
|
||||
onChange={(e) => handleFilterChange("tempo_range", e.target.value || undefined)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">All Tempos</option>
|
||||
<option value="slow">Slow (<100 BPM)</option>
|
||||
<option value="medium">Medium (100-140 BPM)</option>
|
||||
<option value="fast">Fast (>140 BPM)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Sort By */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Sort By
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.sort_by || "analyzed_at"}
|
||||
onChange={(e) => handleFilterChange("sort_by", e.target.value as any)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="analyzed_at">Date Added</option>
|
||||
<option value="filename">Filename</option>
|
||||
<option value="tempo_bpm">BPM</option>
|
||||
<option value="duration_seconds">Duration</option>
|
||||
<option value="energy">Energy</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Sort Order */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Order
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.sort_desc ? "desc" : "asc"}
|
||||
onChange={(e) => handleFilterChange("sort_desc", e.target.value === "desc")}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="asc">Ascending</option>
|
||||
<option value="desc">Descending</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Active Filters Display */}
|
||||
<div className="space-y-6">
|
||||
{/* Clear all button */}
|
||||
{hasActiveFilters && (
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{localFilters.genre && (
|
||||
<span className="inline-flex items-center px-2 py-1 rounded text-xs bg-blue-100 text-blue-800">
|
||||
Genre: {localFilters.genre}
|
||||
<button
|
||||
onClick={() => handleFilterChange("genre", undefined)}
|
||||
className="ml-1 hover:text-blue-900"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
{localFilters.mood && (
|
||||
<span className="inline-flex items-center px-2 py-1 rounded text-xs bg-purple-100 text-purple-800">
|
||||
Mood: {localFilters.mood}
|
||||
<button
|
||||
onClick={() => handleFilterChange("mood", undefined)}
|
||||
className="ml-1 hover:text-purple-900"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
{localFilters.instrument && (
|
||||
<span className="inline-flex items-center px-2 py-1 rounded text-xs bg-green-100 text-green-800">
|
||||
Instrument: {localFilters.instrument}
|
||||
<button
|
||||
onClick={() => handleFilterChange("instrument", undefined)}
|
||||
className="ml-1 hover:text-green-900"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
{localFilters.key && (
|
||||
<span className="inline-flex items-center px-2 py-1 rounded text-xs bg-yellow-100 text-yellow-800">
|
||||
Key: {localFilters.key}
|
||||
<button
|
||||
onClick={() => handleFilterChange("key", undefined)}
|
||||
className="ml-1 hover:text-yellow-900"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
{localFilters.tempo_range && (
|
||||
<span className="inline-flex items-center px-2 py-1 rounded text-xs bg-orange-100 text-orange-800">
|
||||
Tempo: {localFilters.tempo_range}
|
||||
<button
|
||||
onClick={() => handleFilterChange("tempo_range", undefined)}
|
||||
className="ml-1 hover:text-orange-900"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
onClick={clearFilters}
|
||||
className="w-full text-sm text-orange-600 hover:text-orange-700 font-medium py-2 px-3 bg-orange-50 rounded-lg hover:bg-orange-100 transition-colors"
|
||||
>
|
||||
Effacer tous les filtres
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Genre Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-2">
|
||||
Genre
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.genre || ""}
|
||||
onChange={(e) => handleFilterChange("genre", e.target.value || undefined)}
|
||||
className="w-full px-3 py-2 bg-slate-50 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Tous les genres</option>
|
||||
{availableGenres.map((genre) => (
|
||||
<option key={genre} value={genre}>
|
||||
{genre}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Mood Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-2">
|
||||
Ambiance
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.mood || ""}
|
||||
onChange={(e) => handleFilterChange("mood", e.target.value || undefined)}
|
||||
className="w-full px-3 py-2 bg-slate-50 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Toutes les ambiances</option>
|
||||
{availableMoods.map((mood) => (
|
||||
<option key={mood} value={mood}>
|
||||
{mood}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Instrument Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-2">
|
||||
Instrument
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.instrument || ""}
|
||||
onChange={(e) => handleFilterChange("instrument", e.target.value || undefined)}
|
||||
className="w-full px-3 py-2 bg-slate-50 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Tous les instruments</option>
|
||||
{availableInstruments.map((instrument) => (
|
||||
<option key={instrument} value={instrument}>
|
||||
{instrument}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Key Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-2">
|
||||
Tonalité
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.key || ""}
|
||||
onChange={(e) => handleFilterChange("key", e.target.value || undefined)}
|
||||
className="w-full px-3 py-2 bg-slate-50 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Toutes les tonalités</option>
|
||||
{availableKeys.map((key) => (
|
||||
<option key={key} value={key}>
|
||||
{key}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Tempo Range Filter */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-2">
|
||||
Tempo
|
||||
</label>
|
||||
<select
|
||||
value={localFilters.tempo_range || ""}
|
||||
onChange={(e) => handleFilterChange("tempo_range", e.target.value || undefined)}
|
||||
className="w-full px-3 py-2 bg-slate-50 border border-slate-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Tous les tempos</option>
|
||||
<option value="slow">Lent (< 100 BPM)</option>
|
||||
<option value="medium">Moyen (100-140 BPM)</option>
|
||||
<option value="fast">Rapide (> 140 BPM)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Active filters summary */}
|
||||
{hasActiveFilters && (
|
||||
<div className="pt-4 border-t border-slate-200">
|
||||
<p className="text-xs font-semibold text-slate-700 mb-2">Filtres actifs:</p>
|
||||
<div className="space-y-1">
|
||||
{localFilters.genre && (
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-slate-600">Genre:</span>
|
||||
<span className="font-medium text-slate-800">{localFilters.genre}</span>
|
||||
</div>
|
||||
)}
|
||||
{localFilters.mood && (
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-slate-600">Ambiance:</span>
|
||||
<span className="font-medium text-slate-800">{localFilters.mood}</span>
|
||||
</div>
|
||||
)}
|
||||
{localFilters.instrument && (
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-slate-600">Instrument:</span>
|
||||
<span className="font-medium text-slate-800">{localFilters.instrument}</span>
|
||||
</div>
|
||||
)}
|
||||
{localFilters.key && (
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-slate-600">Tonalité:</span>
|
||||
<span className="font-medium text-slate-800">{localFilters.key}</span>
|
||||
</div>
|
||||
)}
|
||||
{localFilters.tempo_range && (
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-slate-600">Tempo:</span>
|
||||
<span className="font-medium text-slate-800">
|
||||
{localFilters.tempo_range === 'slow' && 'Lent'}
|
||||
{localFilters.tempo_range === 'medium' && 'Moyen'}
|
||||
{localFilters.tempo_range === 'fast' && 'Rapide'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user