import { keepPreviousData, useInfiniteQuery, useQuery } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import { api, unwrap } from "../api/client"; import { qk } from "../api/queryKeys"; /** Значение с задержкой: не дёргаем поиск на каждый введённый символ. * 100 мс — почти незаметно глазу, но склеивает быструю печать в один запрос. */ function useDebounced(value: T, ms = 100): T { const [debounced, setDebounced] = useState(value); useEffect(() => { const t = setTimeout(() => setDebounced(value), ms); return () => clearTimeout(t); }, [value, ms]); return debounced; } const HISTORY_PAGE = 20; /** История партий игрока страницами по 20 («Показать ещё»). * * Режим и подробность приходят вместе со списком: это витрина ВЛАДЕЛЬЦА профиля, * её видят и гости. Постранично, а не одним большим запросом: у эндпоинта потолок * limit=100, и на 101-й партии обрезка вернулась бы. */ export function useUserMatches(userId: number | null) { return useInfiniteQuery({ queryKey: userId != null ? qk.userMatches(userId) : ["userMatches", "none"], enabled: userId != null, initialPageParam: 0, queryFn: async ({ pageParam }) => unwrap( await api.GET("/api/users/{user_id}/matches", { params: { path: { user_id: userId as number }, query: { limit: HISTORY_PAGE, offset: pageParam as number }, }, }), ), getNextPageParam: (last, pages) => { const loaded = pages.reduce((n, p) => n + p.items.length, 0); return loaded < last.total ? loaded : undefined; }, }); } /** Подсказки по нику среди всех активных игроков (GET /api/users/search). */ export function useUserSearch(q: string, limit = 3) { const dq = useDebounced(q.trim()); return useQuery({ queryKey: qk.userSearch(dq, limit), enabled: dq.length > 0, // Список не мигает пустым между буквами; при пустом q данные остаются — // видимость выпадашки родитель гейтит по q, а не по data. placeholderData: keepPreviousData, staleTime: 30_000, queryFn: async () => unwrap(await api.GET("/api/users/search", { params: { query: { q: dq, limit } } })), }); }