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