veza/apps/web/src/components/search/GlobalSearchBar.tsx
senke ddc244405c feat: Ajouter logs de debug pour la recherche globale
- Logs détaillés dans GlobalSearchBar pour fetchSuggestions
- Logs pour chaque étape de la recherche (tracks, playlists, users)
- Logs de performance avec durée des requêtes
- Logs dans Search component pour interactions utilisateur
- Logs pour navigation et sélection de résultats
- Utilisation de console.log avec préfixes [🔍] pour faciliter le filtrage dans DevTools
2026-01-18 12:38:53 +01:00

253 lines
9.3 KiB
TypeScript

import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { Search, type SearchResult } from './Search';
import { searchTracks } from '@/features/tracks/services/trackListService';
import { playlistsApi } from '@/services/api/playlists';
import { searchUsers } from '@/features/search/services/searchService';
import { useTranslation } from '@/hooks/useTranslation';
import { logger } from '@/utils/logger';
import { isFeatureEnabled } from '@/config/features';
/**
* FE-COMP-008: Global search bar component with autocomplete
* Can be used in the header or anywhere in the app
*/
export interface GlobalSearchBarProps {
className?: string;
placeholder?: string;
onSearch?: (query: string) => void;
}
/**
* Global search bar with autocomplete suggestions for tracks, playlists, and users
*/
export function GlobalSearchBar({
className,
placeholder,
onSearch,
}: GlobalSearchBarProps) {
const navigate = useNavigate();
const { t } = useTranslation();
// Fetch autocomplete suggestions
const fetchSuggestions = useCallback(
async (query: string): Promise<SearchResult[]> => {
console.log('[🔍 GlobalSearchBar] fetchSuggestions called', { query });
logger.debug('Fetching search suggestions', { query, component: 'GlobalSearchBar' });
if (!query.trim()) {
console.log('[🔍 GlobalSearchBar] Empty query, returning empty results');
return [];
}
try {
const startTime = performance.now();
console.log('[🔍 GlobalSearchBar] Starting parallel search requests', {
query,
playlistSearchEnabled: isFeatureEnabled('PLAYLIST_SEARCH'),
});
// Fetch suggestions from all sources in parallel
// FIX: Gérer les feature flags (PLAYLIST_SEARCH peut être désactivé)
const searchPromises: Array<Promise<any>> = [
searchTracks(query, { pagination: { page: 1, limit: 3 } }),
searchUsers({ query, page: 1, limit: 3 }),
];
// Ajouter la recherche de playlists seulement si la feature est activée
if (isFeatureEnabled('PLAYLIST_SEARCH')) {
console.log('[🔍 GlobalSearchBar] Playlist search enabled, adding to promises');
searchPromises.push(
playlistsApi.search({ q: query, page: 1, limit: 3 }),
);
} else {
console.log('[🔍 GlobalSearchBar] Playlist search disabled, using empty result');
// Si désactivé, retourner une promesse résolue avec un résultat vide
searchPromises.push(Promise.resolve({ playlists: [], total: 0 }));
}
const [tracksData, usersData, playlistsData] = await Promise.allSettled(
searchPromises,
);
const duration = performance.now() - startTime;
console.log('[🔍 GlobalSearchBar] Search requests completed', {
duration: `${duration.toFixed(2)}ms`,
tracksStatus: tracksData.status,
usersStatus: usersData.status,
playlistsStatus: playlistsData.status,
});
const results: SearchResult[] = [];
// Add track suggestions
// FIX: searchTracks retourne PaginatedResponse<Track> avec propriété 'data'
if (tracksData.status === 'fulfilled' && tracksData.value?.data) {
const tracksCount = tracksData.value.data.length;
console.log('[🔍 GlobalSearchBar] Adding track suggestions', { count: tracksCount });
tracksData.value.data.forEach((track: any) => {
results.push({
id: track.id,
type: 'track',
title: track.title || track.name || 'Untitled',
subtitle: track.artist || track.artist_name || undefined,
image: track.cover_url || track.cover || track.thumbnail_url,
});
});
} else {
console.log('[🔍 GlobalSearchBar] No track suggestions', {
status: tracksData.status,
hasData: tracksData.status === 'fulfilled' && !!tracksData.value?.data,
error: tracksData.status === 'rejected' ? tracksData.reason : undefined,
});
}
// Add playlist suggestions (seulement si la feature est activée)
if (
isFeatureEnabled('PLAYLIST_SEARCH') &&
playlistsData.status === 'fulfilled' &&
playlistsData.value?.playlists
) {
const playlistsCount = playlistsData.value.playlists.length;
console.log('[🔍 GlobalSearchBar] Adding playlist suggestions', { count: playlistsCount });
playlistsData.value.playlists.forEach((playlist: any) => {
results.push({
id: playlist.id,
type: 'playlist',
title: playlist.title || 'Untitled Playlist',
subtitle: playlist.is_public ? 'Public' : 'Private',
image: playlist.cover_url || playlist.thumbnail_url,
});
});
} else {
console.log('[🔍 GlobalSearchBar] No playlist suggestions', {
featureEnabled: isFeatureEnabled('PLAYLIST_SEARCH'),
status: playlistsData.status,
hasData: playlistsData.status === 'fulfilled' && !!playlistsData.value?.playlists,
});
}
// Add user suggestions
if (usersData.status === 'fulfilled' && usersData.value?.users) {
const usersCount = usersData.value.users.length;
console.log('[🔍 GlobalSearchBar] Adding user suggestions', { count: usersCount });
usersData.value.users.forEach((user: any) => {
results.push({
id: user.id,
type: 'user',
title: user.username,
subtitle: user.email,
image: user.avatar_url,
});
});
} else {
console.log('[🔍 GlobalSearchBar] No user suggestions', {
status: usersData.status,
hasData: usersData.status === 'fulfilled' && !!usersData.value?.users,
error: usersData.status === 'rejected' ? usersData.reason : undefined,
});
}
const finalResults = results.slice(0, 8); // Limit to 8 total suggestions
console.log('[🔍 GlobalSearchBar] Final suggestions', {
total: results.length,
limited: finalResults.length,
breakdown: {
tracks: finalResults.filter((r) => r.type === 'track').length,
playlists: finalResults.filter((r) => r.type === 'playlist').length,
users: finalResults.filter((r) => r.type === 'user').length,
},
});
logger.debug('Search suggestions fetched', {
query,
totalResults: results.length,
limitedResults: finalResults.length,
component: 'GlobalSearchBar',
});
return finalResults;
} catch (error) {
console.error('[🔍 GlobalSearchBar] Error fetching suggestions', {
error,
query,
errorMessage: error instanceof Error ? error.message : String(error),
errorStack: error instanceof Error ? error.stack : undefined,
});
logger.error('Error fetching search suggestions', {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
query,
component: 'GlobalSearchBar',
});
return [];
}
},
[],
);
// Handle search action - navigation vers la page de recherche
const handleSearch = useCallback(
(query: string) => {
console.log('[🔍 GlobalSearchBar] handleSearch called', { query });
if (query.trim()) {
const searchUrl = `/search?q=${encodeURIComponent(query)}`;
console.log('[🔍 GlobalSearchBar] Navigating to search page', { searchUrl });
navigate(searchUrl);
onSearch?.(query);
logger.debug('Search submitted', { query, component: 'GlobalSearchBar' });
} else {
console.log('[🔍 GlobalSearchBar] Empty query, skipping navigation');
}
},
[navigate, onSearch],
);
// Handle result selection
const handleResultSelect = useCallback(
(result: SearchResult) => {
console.log('[🔍 GlobalSearchBar] handleResultSelect called', {
type: result.type,
id: result.id,
title: result.title,
});
logger.debug('Search result selected', {
type: result.type,
id: result.id,
component: 'GlobalSearchBar',
});
switch (result.type) {
case 'track':
navigate(`/tracks/${result.id}`);
break;
case 'playlist':
navigate(`/playlists/${result.id}`);
break;
case 'user':
navigate(`/users/${result.id}`);
break;
}
},
[navigate],
);
return (
<Search
onSearch={handleSearch}
onResultSelect={handleResultSelect}
fetchSuggestions={fetchSuggestions}
placeholder={
placeholder ||
t('common.search') ||
'Search tracks, playlists, users...'
}
showSuggestions={true}
showHistory={true}
className={className}
// CRITIQUE FIX #21: Augmenter le délai de debouncing à 500ms pour réduire les requêtes API
// Un délai de 500ms est optimal pour équilibrer réactivité et performance
debounceDelay={500}
/>
);
}