import React, { useState, useMemo, useCallback } from 'react'; // TODO: P2-F5 — Replace hardcoded TYPE_LABELS with shared constant import { useSearchParams, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useGlobalSearch, SearchResult, SearchFilters } from '@/api/hooks'; import { Card } from '@/components/ui/Card'; import { Input } from '@/components/ui/Input'; import { Button } from '@/components/ui/Button'; import { EmptyState } from '@/components/ui/EmptyState'; import { Skeleton } from '@/components/ui/Skeleton'; import { Tabs } from '@/components/shared/Tabs'; import { SearchFacets } from '@/components/search/SearchFacets'; import { SearchResultCard } from '@/components/search/SearchResultCard'; import { SavedSearches } from '@/components/search/SavedSearches'; const TYPE_LABELS: Record = { company: 'search.companies', contact: 'search.contacts', mail: 'search.mails', file: 'search.files', event: 'search.events', message: 'search.messages', }; const EMPTY_FILTERS: SearchFilters = {}; function filtersToParams(filters: SearchFilters): Record { const params: Record = {}; if (filters.entityTypes?.length) params.entity_types = filters.entityTypes.join(','); if (filters.tags?.length) params.tags = filters.tags.join(','); if (filters.dateFrom) params.date_from = filters.dateFrom; if (filters.dateTo) params.date_to = filters.dateTo; if (filters.sort && filters.sort !== 'relevance') params.sort = filters.sort; return params; } function paramsToFilters(params: URLSearchParams): SearchFilters { const filters: SearchFilters = {}; const entityTypes = params.get('entity_types'); const tags = params.get('tags'); const dateFrom = params.get('date_from'); const dateTo = params.get('date_to'); const sort = params.get('sort'); if (entityTypes) filters.entityTypes = entityTypes.split(',').filter(Boolean); if (tags) filters.tags = tags.split(',').filter(Boolean); if (dateFrom) filters.dateFrom = dateFrom; if (dateTo) filters.dateTo = dateTo; if (sort === 'date' || sort === 'name') filters.sort = sort; return filters; } export function GlobalSearchResultsPage() { const { t } = useTranslation(); const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); const query = searchParams.get('q') || ''; const [searchInput, setSearchInput] = useState(query); const [activeTab, setActiveTab] = useState('all'); const [filters, setFilters] = useState(() => paramsToFilters(searchParams)); const { data: results, isLoading } = useGlobalSearch(query, filters); const handleSearch = (e: React.FormEvent) => { e.preventDefault(); const params: Record = {}; if (searchInput) params.q = searchInput; setSearchParams({ ...params, ...filtersToParams(filters) }); }; const handleFiltersChange = useCallback((next: SearchFilters) => { setFilters(next); const params: Record = {}; if (searchInput) params.q = searchInput; setSearchParams({ ...params, ...filtersToParams(next) }); }, [searchInput, setSearchParams]); const handleClearFilters = useCallback(() => { setFilters(EMPTY_FILTERS); const params: Record = {}; if (searchInput) params.q = searchInput; setSearchParams(params); }, [searchInput, setSearchParams]); const handleRunSaved = useCallback((savedQuery: string, savedFilters: SearchFilters) => { setSearchInput(savedQuery); setFilters(savedFilters); setSearchParams({ q: savedQuery, ...filtersToParams(savedFilters) }); }, [setSearchParams]); const groupedResults = useMemo(() => { const groups: Record = { company: [], contact: [], mail: [], file: [], event: [], message: [] }; if (results) { for (const r of results) { if (groups[r.type]) { groups[r.type].push(r); } } } return groups; }, [results]); const totalResults = useMemo(() => (results ? results.length : 0), [results]); const entityCounts = useMemo(() => { const counts: Record = {}; if (results) { for (const r of results) { counts[r.type] = (counts[r.type] || 0) + 1; } } return counts; }, [results]); const renderResults = (items: SearchResult[]) => { if (items.length === 0) { return ; } return (
{items.map((result) => ( ))}
); }; const tabs = useMemo(() => [ { key: 'all', label: t('search.allTypes'), badge: totalResults, content: (
{totalResults === 0 && !isLoading ? ( ) : ( renderResults(results || []) )}
), }, { key: 'company', label: t('search.companies'), badge: groupedResults.company.length, content:
{renderResults(groupedResults.company)}
, }, { key: 'contact', label: t('search.contacts'), badge: groupedResults.contact.length, content:
{renderResults(groupedResults.contact)}
, }, { key: 'mail', label: t('search.mails'), badge: groupedResults.mail.length, content:
{renderResults(groupedResults.mail)}
, }, { key: 'file', label: t('search.files'), badge: groupedResults.file.length, content:
{renderResults(groupedResults.file)}
, }, { key: 'event', label: t('search.events'), badge: groupedResults.event.length, content:
{renderResults(groupedResults.event)}
, }, ], [t, totalResults, groupedResults, results, isLoading, query, navigate]); return (

{t('search.title')}

setSearchInput(e.target.value)} placeholder={t('common.search')} data-testid="search-input" />
{query && (

{t('search.resultsFor', { query })}:

)} {!query ? ( ) : (
{isLoading ? (
{[1, 2, 3].map((i) => ( ))}
) : (
)}
)}
); }