дозагрузка свежих данных
This commit is contained in:
@@ -625,6 +625,101 @@ export async function filterPendingByQuery(
|
||||
return freshMatches.concat(olderMatches);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Вкладка «Поиск»: ищет сразу по двум категориям — «Мои заявки» (я автор) и
|
||||
// «где я участвовал как согласующий» (involved, независимо от того, дошла ли
|
||||
// уже очередь) — и показывает их одним списком, с колонками Автор и Статус
|
||||
// одновременно (в отличие от «Мои заявки»/«На согласовании», где по смыслу
|
||||
// раздела одно из двух не нужно).
|
||||
//
|
||||
// Пагинация — клиентская поверх объединённого результата: «Мои заявки» уже
|
||||
// умеет по-настоящему постранично через Search, но «где участвовал» — нет
|
||||
// (см. filterPendingByQuery — там честного $skip/$top не сделать), так что
|
||||
// единой серверной пагинации на двух источниках сразу не построить. Для
|
||||
// сценария «искал что-то один раз» это приемлемо.
|
||||
export interface ISearchPage {
|
||||
rows: IMyRequest[];
|
||||
totalRows: number;
|
||||
}
|
||||
|
||||
// Как getOwnRequests, но без $skip/$top — сразу весь найденный срез (до
|
||||
// KQL_ID_LIMIT), с тем же довеском свежих (< 10 мин) заявок через REST.
|
||||
async function searchOwnAll(
|
||||
context: WebPartContext,
|
||||
types: IRequestType[],
|
||||
query: string
|
||||
): Promise<IMyRequest[]> {
|
||||
if (types.length === 0) return [];
|
||||
|
||||
const sp = spfi(types[0].siteUrl as string).using(SPFx(context));
|
||||
const claimsLogin = `i:0#.f|membership|${currentUserEmail(context)}`;
|
||||
const clauses = [
|
||||
`(${types.map((t) => `Path:"${listAbsUrl(t)}*"`).join(' OR ')})`,
|
||||
`AuthorOWSUSER:"${claimsLogin}"`,
|
||||
];
|
||||
const text = freeTextClause(query);
|
||||
if (text) clauses.push(text);
|
||||
const kql = clauses.join(' AND ');
|
||||
|
||||
const results = await sp.search({
|
||||
Querytext: kql,
|
||||
RowLimit: KQL_ID_LIMIT,
|
||||
SelectProperties: ['ListItemID', 'Title', 'Path'],
|
||||
TrimDuplicates: false,
|
||||
});
|
||||
|
||||
console.log(`[Текущие заявки] Search «Поиск» (мои заявки): KQL = ${kql}, найдено: ${results.TotalRows}`);
|
||||
console.table(results.PrimarySearchResults);
|
||||
|
||||
const entries = hitsToEntries(results, types);
|
||||
const byKey = entries.length > 0 ? await loadByIds(context, entries, 'own') : new Map<string, IMyRequest>();
|
||||
const searchRows = entries.map((e) => byKey.get(`${e.type.id}-${e.id}`)).filter((r): r is IMyRequest => !!r);
|
||||
|
||||
const freshRows = (await Promise.all(types.map((t) => getFreshOwnRowsForType(context, t, query)))).reduce(
|
||||
(all, r) => all.concat(r),
|
||||
[] as IMyRequest[]
|
||||
);
|
||||
const searchKeys = new Set(searchRows.map((r) => `${r.requestTypeId}-${r.id}`));
|
||||
const onlyFresh = freshRows.filter((r) => !searchKeys.has(`${r.requestTypeId}-${r.id}`));
|
||||
|
||||
return onlyFresh.concat(searchRows);
|
||||
}
|
||||
|
||||
export async function searchMyRequests(
|
||||
context: WebPartContext,
|
||||
types: IRequestType[],
|
||||
query: string,
|
||||
page: number
|
||||
): Promise<ISearchPage> {
|
||||
const q = query.trim();
|
||||
if (!q) return { rows: [], totalRows: 0 };
|
||||
|
||||
const [ownRows, involvedIdx] = await Promise.all([
|
||||
searchOwnAll(context, types, q),
|
||||
getPendingApprovalIndex(context, 'involved'),
|
||||
]);
|
||||
|
||||
const involvedFiltered = await filterPendingByQuery(context, involvedIdx.types, involvedIdx.rows, q);
|
||||
|
||||
// Заявка может оказаться и своей, и involved одновременно (сам себе
|
||||
// согласующий) — оставляем как 'own', не дублируем строку.
|
||||
const ownKeys = new Set(ownRows.map((r) => `${r.requestTypeId}-${r.id}`));
|
||||
const involvedOnly = involvedFiltered.filter((r) => !ownKeys.has(`${r.requestTypeId}-${r.id}`));
|
||||
|
||||
const merged = ownRows.concat(involvedOnly).sort((a, b) => b.created.localeCompare(a.created));
|
||||
const totalRows = merged.length;
|
||||
const pageRows = merged.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
|
||||
|
||||
// Автор нужен для 'approval'-строк текущей страницы — 'own' уже пришли с
|
||||
// автором (loadByIds/getFreshOwnRowsForType сами дочитывают Author).
|
||||
const needAuthor = pageRows.filter((r) => r.kind === 'approval');
|
||||
if (needAuthor.length > 0) {
|
||||
await hydratePendingAuthors(context, involvedIdx.types, needAuthor);
|
||||
}
|
||||
|
||||
return { rows: pageRows, totalRows };
|
||||
}
|
||||
|
||||
function toMyRequest(
|
||||
r: Record<string, unknown>,
|
||||
author: { EMail?: string; Title?: string } | undefined,
|
||||
|
||||
Reference in New Issue
Block a user