Sla (#11)
Tests / backend-test (push) Successful in 1m50s
Tests / frontend-test (push) Successful in 49s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/11
This commit was merged in pull request #11.
This commit is contained in:
2026-08-13 04:44:08 +00:00
parent 7f2aabbf29
commit d076f4e98d
103 changed files with 2606 additions and 1265 deletions
@@ -62,6 +62,12 @@ export function AsyncSearchableSelect<T>({
// Растущий счётчик запросов: если ответ пришёл не от последнего запроса
// (например, пользователь допечатал ещё символ) — просто игнорируем его.
const requestIdRef = React.useRef(0);
// getValue нужен внутри runSearch (дедуп при доклейке страницы), но в deps его
// класть нельзя: пропс приходит инлайновой стрелкой и меняет ссылку на каждом
// рендере — runSearch пересоздавался бы постоянно и перезапускал эффект
// бесконечной прокрутки. Держим последнюю версию в ref.
const getValueRef = React.useRef(getValue);
getValueRef.current = getValue;
const runSearch = React.useCallback((q: string, targetPage: number, append: boolean) => {
const requestId = ++requestIdRef.current;
@@ -71,7 +77,16 @@ export function AsyncSearchableSelect<T>({
search(q, targetPage)
.then(result => {
if (requestIdRef.current !== requestId) return; // устарел
setItems(prev => (append ? [...prev, ...result.items] : result.items));
// Дедуп при доклейке: соседние страницы могут пересекаться, если индекс
// переупорядочился между запросами (StartRow смещает окно по РАНГУ, а не
// по устойчивому курсору). Дубликат здесь = два пункта с одинаковым
// React-ключом, то есть заявка в списке дважды. Внутри одной страницы
// дубли отсекает уже источник (см. searchRequests в requestManageService).
setItems(prev => {
if (!append) return result.items;
const shown = new Set(prev.map(i => getValueRef.current(i)));
return [...prev, ...result.items.filter(i => !shown.has(getValueRef.current(i)))];
});
setHasMore(result.hasMore);
setPage(targetPage);
})