import { useEffect, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { useQuery, useMutation } from '@tanstack/react-query'; import { Filter, Loader2 } from 'lucide-react'; import toast from 'react-hot-toast'; import { SearchBar } from '../components/SearchBar'; import { SourceCard } from '../components/SourceCard'; import { StatusBadge } from '../components/StatusBadge'; import { searchApi, tasksApi } from '../api/client'; import { useAuthStore } from '../store/auth'; import { useTaskWebSocket } from '../hooks/useTaskPolling'; import type { Task, SearchResultData } from '../types'; export function Search() { const [searchParams, setSearchParams] = useSearchParams(); const navigate = useNavigate(); const { isAuthenticated } = useAuthStore(); const query = searchParams.get('q') || ''; const [taskId, setTaskId] = useState(null); const [lang, setLang] = useState(''); const [yearFrom, setYearFrom] = useState(''); const [yearTo, setYearTo] = useState(''); // Создать задачу поиска const createSearch = useMutation({ mutationFn: searchApi.create, onSuccess: (response) => { const task: Task = response.data; setTaskId(task.public_id); }, onError: (error: any) => { const msg = error.response?.data?.detail || 'Ошибка поиска'; if (error.response?.status === 401) { toast.error('Войдите, чтобы выполнять поиск'); navigate('/login'); } else { toast.error(msg); } }, }); // Поллинг задачи const { data: task } = useQuery({ queryKey: ['task', taskId], queryFn: () => tasksApi.get(taskId!).then((r) => r.data as Task), enabled: !!taskId, refetchInterval: (query) => { const status = query.state.data?.status; if (status === 'done' || status === 'failed') return false; return 3000; }, }); // WebSocket для real-time обновлений useTaskWebSocket( taskId ?? undefined, !!taskId && task?.status !== 'done' && task?.status !== 'failed' ); // Запустить поиск при изменении query useEffect(() => { if (query && query.length >= 3 && isAuthenticated) { createSearch.mutate({ query, lang: lang || undefined, year_from: yearFrom ? parseInt(yearFrom) : undefined, year_to: yearTo ? parseInt(yearTo) : undefined, }); } }, [query]); const handleSearch = (newQuery: string) => { setTaskId(null); setSearchParams({ q: newQuery }); }; const result = task?.result as SearchResultData | undefined; const isLoading = createSearch.isPending || (!!taskId && task?.status === 'queued') || task?.status === 'processing'; return (
{/* Поисковая строка */}
{/* Боковая панель фильтров */} {/* Результаты */}
{/* Статус задачи */} {task && task.status !== 'done' && (
{task.queue_position && (

Позиция в очереди: {task.queue_position}

)} {task.eta_seconds && (

Ожидаемое время: ~{task.eta_seconds} сек

)}
)} {/* Результаты поиска */} {task?.status === 'done' && result && (

Найдено: {result.total} источников

{result.sources.map((source) => ( ))}
)} {/* Ошибка */} {task?.status === 'failed' && (

Ошибка поиска

{task.error}

)} {/* Пустое состояние */} {!query && !task && (

Введите запрос для поиска источников

)} {/* Не авторизован */} {query && !isAuthenticated && (

Необходима авторизация

Войдите или зарегистрируйтесь для выполнения поиска

)}
); }