feat: сортировка «Всех заявок» на рабочее место по дате выхода
По дате выхода удобнее планировать подготовку рабочих мест. Даты для сортировки дочитываются для всего списка пачками (предел длины URL), заявки без даты — всегда в конце. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -293,7 +293,7 @@ const Table: React.FC<{
|
||||
if (cols.startDate) {
|
||||
columns.push({
|
||||
key: 'startDate',
|
||||
title: 'Дата выхода',
|
||||
title: sortable('Дата выхода', 'startDate'),
|
||||
width: '120px',
|
||||
render: (r) => formatDate(r.startDate ?? '') || '—'
|
||||
});
|
||||
@@ -592,6 +592,13 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
|
||||
const full =
|
||||
reuseFull && cachedFull && cachedFull.key === fullKey ? cachedFull.rows : await fetchAndRememberFull();
|
||||
|
||||
// Дату выхода обычно дочитываем только для видимой страницы, но сортировать
|
||||
// по ней надо весь список — дочитываем для всех строк, у кого её ещё нет
|
||||
// (полный список переиспользуется, повторно по сети не идём).
|
||||
if (sort.key === 'startDate') {
|
||||
await hydrateStartDates(context, full.filter((r) => r.startDate === undefined));
|
||||
}
|
||||
|
||||
const refined = sortRows(filterRows(full, localFilters), sort);
|
||||
const pageRows = refined.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);
|
||||
await hydratePendingAuthors(context, types, pageRows);
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface IStoredRefinement {
|
||||
/** Версия формата: поменяется структура — старые записи просто не прочитаются. */
|
||||
const STORAGE_PREFIX = 'rp-table-filters:v1';
|
||||
|
||||
const SORT_KEYS: SortKey[] = ['code', 'type', 'date', 'status'];
|
||||
const SORT_KEYS: SortKey[] = ['code', 'type', 'date', 'status', 'startDate'];
|
||||
const ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
|
||||
|
||||
function storageKey(scope: string, tableKey: string): string {
|
||||
|
||||
@@ -118,6 +118,16 @@ describe('sortRows', () => {
|
||||
expect(codes(sortRows(rows, { key: 'type', direction: 'asc' }))).toEqual(['b', 'a']);
|
||||
});
|
||||
|
||||
it('по дате выхода — в обе стороны, заявки без даты всегда в конце', () => {
|
||||
const rows = [
|
||||
request({ code: 'none', startDate: '' }),
|
||||
request({ code: 'oct', startDate: '2026-10-06T00:00:00Z' }),
|
||||
request({ code: 'sep', startDate: '2026-09-27T00:00:00Z' }),
|
||||
];
|
||||
expect(codes(sortRows(rows, { key: 'startDate', direction: 'asc' }))).toEqual(['sep', 'oct', 'none']);
|
||||
expect(codes(sortRows(rows, { key: 'startDate', direction: 'desc' }))).toEqual(['oct', 'sep', 'none']);
|
||||
});
|
||||
|
||||
it('не мутирует исходный массив', () => {
|
||||
const rows = [request({ code: '4-2609-2' }), request({ code: '4-2609-1' })];
|
||||
sortRows(rows, { key: 'code', direction: 'asc' });
|
||||
|
||||
@@ -5,7 +5,7 @@ import { IMyRequest } from './models/IMyRequest';
|
||||
// одной странице (иначе отсортировались бы 20 строк, а не все заявки).
|
||||
|
||||
/** Колонки, по которым можно сортировать. */
|
||||
export type SortKey = 'code' | 'type' | 'date' | 'status';
|
||||
export type SortKey = 'code' | 'type' | 'date' | 'status' | 'startDate';
|
||||
export type SortDirection = 'asc' | 'desc';
|
||||
|
||||
export interface ISortState {
|
||||
@@ -108,13 +108,23 @@ function compareBy(key: SortKey, a: IMyRequest, b: IMyRequest): number {
|
||||
return a.status.localeCompare(b.status, 'ru');
|
||||
case 'date':
|
||||
return a.created.localeCompare(b.created);
|
||||
case 'startDate':
|
||||
return (a.startDate ?? '').localeCompare(b.startDate ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
/** Новый массив; при равенстве по выбранной колонке — свежие сверху. */
|
||||
/**
|
||||
* Новый массив; при равенстве по выбранной колонке — свежие сверху.
|
||||
* Заявки без даты выхода — всегда в конце, в любом направлении: иначе при
|
||||
* сортировке «по возрастанию» сверху оказались бы незаполненные.
|
||||
*/
|
||||
export function sortRows(rows: IMyRequest[], sort: ISortState): IMyRequest[] {
|
||||
const sign = sort.direction === 'asc' ? 1 : -1;
|
||||
return [...rows].sort((a, b) => {
|
||||
if (sort.key === 'startDate') {
|
||||
const emptyDiff = Number(!a.startDate) - Number(!b.startDate);
|
||||
if (emptyDiff !== 0) return emptyDiff;
|
||||
}
|
||||
const primary = compareBy(sort.key, a, b) * sign;
|
||||
return primary !== 0 ? primary : b.created.localeCompare(a.created);
|
||||
});
|
||||
|
||||
@@ -6,10 +6,17 @@ import { spForSite } from '../../shared/pnp/pnpjs';
|
||||
import { traced } from '../../shared/perfTrace';
|
||||
import { IMyRequest } from './models/IMyRequest';
|
||||
|
||||
// Сколько Id в одном $filter: при сортировке по дате выхода дочитывается весь
|
||||
// список типа (сотни заявок), а «Id eq … or …» на сотни Id упирается в предел
|
||||
// длины URL.
|
||||
const IDS_PER_REQUEST = 50;
|
||||
|
||||
/**
|
||||
* Проставляет startDate — дату выхода сотрудника (колонка StartDate списка
|
||||
* данных). Только для видимой страницы; группировка по партиции строки, как в
|
||||
* hydrateAuthors: в другой партиции тот же Id — чужая заявка. Мутирует строки.
|
||||
* данных). Обычно для видимой страницы, при сортировке по дате выхода — для
|
||||
* всего списка (см. fetchRefinedPage в RequestsTableView). Группировка по
|
||||
* партиции строки, как в hydrateAuthors: в другой партиции тот же Id — чужая
|
||||
* заявка. Мутирует строки.
|
||||
*/
|
||||
export async function hydrateStartDates(context: WebPartContext, rows: IMyRequest[]): Promise<void> {
|
||||
if (rows.length === 0) return;
|
||||
@@ -29,9 +36,16 @@ async function hydrateStartDatesInner(context: WebPartContext, rows: IMyRequest[
|
||||
byPartition.set(r.listId.toLowerCase(), bucket);
|
||||
});
|
||||
|
||||
const requests: Array<{ siteUrl: string; listId: string; typeName: string; ids: number[] }> = [];
|
||||
byPartition.forEach((bucket) => {
|
||||
for (let i = 0; i < bucket.ids.length; i += IDS_PER_REQUEST) {
|
||||
requests.push({ ...bucket, ids: bucket.ids.slice(i, i + IDS_PER_REQUEST) });
|
||||
}
|
||||
});
|
||||
|
||||
const byKey = new Map<string, string>();
|
||||
await Promise.all(
|
||||
Array.from(byPartition.values()).map(async ({ siteUrl, listId, typeName, ids }) => {
|
||||
requests.map(async ({ siteUrl, listId, typeName, ids }) => {
|
||||
try {
|
||||
const sp = spForSite(siteUrl, context);
|
||||
const dataRows = (await sp.web
|
||||
|
||||
Reference in New Issue
Block a user