diff --git a/src/lib/api/services/sounds.ts b/src/lib/api/services/sounds.ts index ba25a6d..1f0addc 100644 --- a/src/lib/api/services/sounds.ts +++ b/src/lib/api/services/sounds.ts @@ -21,8 +21,16 @@ export interface Sound { updated_at: string } +export type SoundSortField = 'name' | 'filename' | 'duration' | 'size' | 'type' | 'play_count' | 'created_at' | 'updated_at' +export type SortOrder = 'asc' | 'desc' + export interface GetSoundsParams { types?: string[] + search?: string + sort_by?: SoundSortField + sort_order?: SortOrder + limit?: number + offset?: number } export interface GetSoundsResponse { @@ -31,38 +39,52 @@ export interface GetSoundsResponse { export class SoundsService { /** - * Get all sounds with optional type filtering + * Get all sounds with optional filtering, searching, and sorting */ async getSounds(params?: GetSoundsParams): Promise { - const queryParams: Record = {} + const searchParams = new URLSearchParams() + // Handle multiple types if (params?.types) { - // Handle multiple types by building query string manually - const searchParams = new URLSearchParams() params.types.forEach(type => { searchParams.append('types', type) }) - - const response = await apiClient.get(`/api/v1/sounds/?${searchParams.toString()}`) - return response.sounds || [] } - const response = await apiClient.get('/api/v1/sounds/', { params: queryParams }) + // Handle other parameters + if (params?.search) { + searchParams.append('search', params.search) + } + if (params?.sort_by) { + searchParams.append('sort_by', params.sort_by) + } + if (params?.sort_order) { + searchParams.append('sort_order', params.sort_order) + } + if (params?.limit) { + searchParams.append('limit', params.limit.toString()) + } + if (params?.offset) { + searchParams.append('offset', params.offset.toString()) + } + + const url = searchParams.toString() ? `/api/v1/sounds/?${searchParams.toString()}` : '/api/v1/sounds/' + const response = await apiClient.get(url) return response.sounds || [] } /** * Get sounds of a specific type */ - async getSoundsByType(type: string): Promise { - return this.getSounds({ types: [type] }) + async getSoundsByType(type: string, params?: Omit): Promise { + return this.getSounds({ ...params, types: [type] }) } /** * Get SDB type sounds */ - async getSDBSounds(): Promise { - return this.getSoundsByType('SDB') + async getSDBSounds(params?: Omit): Promise { + return this.getSoundsByType('SDB', params) } /** diff --git a/src/pages/SoundsPage.tsx b/src/pages/SoundsPage.tsx index 7817ef9..3979404 100644 --- a/src/pages/SoundsPage.tsx +++ b/src/pages/SoundsPage.tsx @@ -1,9 +1,12 @@ import { useEffect, useState } from 'react' import { AppLayout } from '@/components/AppLayout' import { SoundCard } from '@/components/sounds/SoundCard' -import { soundsService, type Sound } from '@/lib/api/services/sounds' +import { soundsService, type Sound, type SoundSortField, type SortOrder } from '@/lib/api/services/sounds' import { Skeleton } from '@/components/ui/skeleton' -import { AlertCircle } from 'lucide-react' +import { Input } from '@/components/ui/input' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Button } from '@/components/ui/button' +import { AlertCircle, Search, SortAsc, SortDesc, X, RefreshCw } from 'lucide-react' import { toast } from 'sonner' import { useTheme } from '@/hooks/use-theme' import { soundEvents, SOUND_EVENTS } from '@/lib/events' @@ -51,6 +54,11 @@ export function SoundsPage() { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [currentColors, setCurrentColors] = useState(lightModeColors) + + // Search and sorting state + const [searchQuery, setSearchQuery] = useState('') + const [sortBy, setSortBy] = useState('name') + const [sortOrder, setSortOrder] = useState('asc') const handlePlaySound = async (sound: Sound) => { try { @@ -76,24 +84,39 @@ export function SoundsPage() { return currentColors[index] } - useEffect(() => { - const fetchSounds = async () => { - try { - setLoading(true) - setError(null) - const sdbSounds = await soundsService.getSDBSounds() - setSounds(sdbSounds) - } catch (err) { - const errorMessage = err instanceof Error ? err.message : 'Failed to fetch sounds' - setError(errorMessage) - toast.error(errorMessage) - } finally { - setLoading(false) - } + const fetchSounds = async () => { + try { + setLoading(true) + setError(null) + const sdbSounds = await soundsService.getSDBSounds({ + search: debouncedSearchQuery.trim() || undefined, + sort_by: sortBy, + sort_order: sortOrder, + }) + setSounds(sdbSounds) + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to fetch sounds' + setError(errorMessage) + toast.error(errorMessage) + } finally { + setLoading(false) } + } + // Debounce search query + const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery) + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedSearchQuery(searchQuery) + }, 300) + + return () => clearTimeout(handler) + }, [searchQuery]) + + useEffect(() => { fetchSounds() - }, []) + }, [debouncedSearchQuery, sortBy, sortOrder]) // Listen for sound_played events and update play_count useEffect(() => { @@ -189,6 +212,68 @@ export function SoundsPage() { )} + + {/* Search and Sort Controls */} +
+
+
+ + setSearchQuery(e.target.value)} + className="pl-9 pr-9" + /> + {searchQuery && ( + + )} +
+
+ +
+ + + + + +
+
+ {renderContent()}