от src избавился

This commit is contained in:
gorbenko.pavel
2026-08-12 12:19:04 +05:00
parent 509f26de7b
commit 4b83d666f1
71 changed files with 67 additions and 1135 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);
})
@@ -17,7 +17,7 @@ import {
} from '../models';
// Тонкий 1:1 фасад над контроллерами бэкенда:
// backend/src/Tnsu.ApprovalRoute.Api/Controllers/{RequestTypes,TemplateRoutes,TemplateRouteSteps}Controller.cs
// backend/Tnsu.ApprovalRoute.Api/Controllers/{RequestTypes,TemplateRoutes,TemplateRouteSteps}Controller.cs
export class ApprovalRouteApi {
constructor(private readonly http: IHttpClient) {}
@@ -152,7 +152,22 @@ export class RequestManageService {
});
const rawRows = (result.PrimarySearchResults ?? []) as Array<{ ListItemID?: string }>;
const ids = rawRows.map(r => Number(r.ListItemID)).filter(id => !isNaN(id) && id > 0);
// Дедуп по Id обязателен: TrimDuplicates: false выше выключает штатную
// дедупликацию Search (она иногда схлопывает нужные нам заявки), и тогда
// индекс может отдать один и тот же элемент несколькими хитами
// (переиндексация, несколько «версий» в crawl). Без этого фильтра хиты
// превращались в пункты комбобокса один-в-один, и заявка показывалась в
// списке дважды — с одинаковым React-ключом. Та же причина и то же лечение,
// что в request-portal (features/myRequests/myRequests.service.ts/hitsToEntries).
const seen = new Set<number>();
const ids = rawRows
.map(r => Number(r.ListItemID))
.filter(id => !isNaN(id) && id > 0)
.filter(id => {
if (seen.has(id)) return false;
seen.add(id);
return true;
});
if (ids.length === 0) return { items: [], hasMore: false };
const list = sp.web.getList(requestType.listId);