Files
Audio-Classifier/frontend/components/FilterPanel.tsx
2025-12-22 15:53:26 +01:00

249 lines
8.7 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 { useState, useEffect } from "react"
import type { FilterParams } from "@/lib/types"
interface FilterPanelProps {
filters: FilterParams
onFiltersChange: (filters: FilterParams) => void
availableGenres: string[]
availableMoods: string[]
availableInstruments: string[]
availableKeys: string[]
}
export default function FilterPanel({
filters,
onFiltersChange,
availableGenres,
availableMoods,
availableInstruments,
availableKeys,
}: FilterPanelProps) {
const [localFilters, setLocalFilters] = useState<FilterParams>(filters)
// Update local filters when parent changes
useEffect(() => {
setLocalFilters(filters)
}, [filters])
const handleFilterChange = (key: keyof FilterParams, value: any) => {
const newFilters = { ...localFilters, [key]: value }
setLocalFilters(newFilters)
onFiltersChange(newFilters)
}
const clearFilters = () => {
const emptyFilters: FilterParams = {}
setLocalFilters(emptyFilters)
onFiltersChange(emptyFilters)
}
const hasActiveFilters = Object.keys(localFilters).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 (&lt;100 BPM)</option>
<option value="medium">Medium (100-140 BPM)</option>
<option value="fast">Fast (&gt;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 */}
{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>
)}
</div>
)}
</div>
)
}