veza/apps/web/src/features/tracks/components/TrackSearch.tsx
2025-12-03 22:56:50 +01:00

120 lines
3.6 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import { searchTracks, TrackSearchParams, TrackSearchError } from '../services/trackSearchService';
import { Track } from '../types/track';
import { TrackSearchFilters } from './TrackSearchFilters';
import { TrackSearchResults } from './TrackSearchResults';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { useDebounce } from '@/hooks/useDebounce';
import { Search } from 'lucide-react';
import { cn } from '@/lib/utils';
/**
* TrackSearch Component
* T0305: Composant principal pour la recherche avancée de tracks
*/
interface TrackSearchProps {
className?: string;
initialQuery?: string;
}
export function TrackSearch({ className, initialQuery = '' }: TrackSearchProps) {
const [query, setQuery] = useState(initialQuery);
const [filters, setFilters] = useState<Partial<TrackSearchParams>>({});
const [tracks, setTracks] = useState<Track[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Debounce de la query pour éviter trop de requêtes
const debouncedQuery = useDebounce(query, 500);
// Fonction pour effectuer la recherche
const performSearch = useCallback(async () => {
setLoading(true);
setError(null);
try {
const params: TrackSearchParams = {
query: debouncedQuery.trim() || undefined,
...filters,
page,
limit: 20,
};
const response = await searchTracks(params);
setTracks(response.tracks);
setTotal(response.pagination.total);
} catch (err) {
let errorMessage = 'Erreur lors de la recherche';
if (err instanceof TrackSearchError) {
errorMessage = err.message;
} else if (err instanceof Error) {
errorMessage = err.message;
}
setError(errorMessage);
setTracks([]);
setTotal(0);
} finally {
setLoading(false);
}
}, [debouncedQuery, filters, page]);
// Effectuer la recherche quand les paramètres changent
useEffect(() => {
performSearch();
}, [performSearch]);
// Réinitialiser la page quand les filtres ou la query changent
useEffect(() => {
setPage(1);
}, [debouncedQuery, filters.genre, filters.format, filters.tags, filters.minDuration, filters.maxDuration, filters.minBPM, filters.maxBPM, filters.minDate, filters.maxDate]);
const handleSearch = () => {
setPage(1);
performSearch();
};
return (
<div className={cn('space-y-6', className)}>
{/* Search Bar */}
<div className="flex gap-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
type="text"
placeholder="Rechercher des tracks..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSearch();
}
}}
className="pl-10"
/>
</div>
<Button onClick={handleSearch} disabled={loading}>
Rechercher
</Button>
</div>
{/* Filters */}
<TrackSearchFilters filters={filters} onFiltersChange={setFilters} />
{/* Results */}
<TrackSearchResults
tracks={tracks}
total={total}
page={page}
limit={20}
onPageChange={setPage}
loading={loading}
error={error}
/>
</div>
);
}