c64222a90d
Вкладка «Слоты» масштабирована под ~20k слотов/мес без торможения: - wktCache: контур слота парсится один раз (кэш по slotKey), не на каждый ререндер сцены - виртуализация таблицы (@tanstack/react-virtual): в DOM только видимое окно строк - автоподбор покрытия: проброс coverageStrategy (GREEDY/CONTINUOUS) в request-cover, результат — преднабор брони (solver не дублируется, используется бэкендный) - мультивыбор: Shift-диапазон по отсортированному списку, «все сутки»/«видимые», счётчик охвата выбора - бронь крупных наборов чанками (по 200) + подтверждение выше 500 + прогресс - отмена расчёта (AbortController), мягкая подсказка перегруза карты - чистая логика выбора/чанкинга вынесена в slotSelection.ts + юнит-тесты Тесты: 70/70 (17 новых). tsc -b и vite build чисто.
53 lines
2.4 KiB
TypeScript
53 lines
2.4 KiB
TypeScript
// Чистая логика выбора слотов и разбиения на чанки — выделена из TguSlotsTab,
|
|
// чтобы покрыть юнит-тестами без рендера компонента.
|
|
|
|
/**
|
|
* Новое множество выбора после клика по строке.
|
|
* Обычный клик — переключает один ключ. Shift+клик при заданном якоре — выбирает (или
|
|
* снимает) непрерывный диапазон от якоря до текущего индекса; направление задаётся новым
|
|
* состоянием текущей строки (если она была не выбрана — диапазон выбираем, иначе снимаем).
|
|
*
|
|
* @param current Текущее множество выбранных ключей.
|
|
* @param keyAt Доступ к ключу слота по индексу в видимом (отфильтрованном) списке.
|
|
* @param index Индекс строки, по которой кликнули.
|
|
* @param shiftKey Был ли зажат Shift.
|
|
* @param anchorIndex Индекс предыдущего клика (якорь) либо null.
|
|
*/
|
|
export function selectionAfterCheck(
|
|
current: ReadonlySet<string>,
|
|
keyAt: (index: number) => string,
|
|
index: number,
|
|
shiftKey: boolean,
|
|
anchorIndex: number | null,
|
|
): Set<string> {
|
|
const next = new Set(current)
|
|
const targetKey = keyAt(index)
|
|
if (shiftKey && anchorIndex != null) {
|
|
const lo = Math.min(anchorIndex, index)
|
|
const hi = Math.max(anchorIndex, index)
|
|
const select = !current.has(targetKey)
|
|
for (let i = lo; i <= hi; i++) {
|
|
const k = keyAt(i)
|
|
if (select) next.add(k)
|
|
else next.delete(k)
|
|
}
|
|
} else if (next.has(targetKey)) {
|
|
next.delete(targetKey)
|
|
} else {
|
|
next.add(targetKey)
|
|
}
|
|
return next
|
|
}
|
|
|
|
/**
|
|
* Разбивает массив на чанки заданного размера (для чанкованной брони крупных наборов).
|
|
* @param items Исходный массив.
|
|
* @param size Размер чанка; <= 0 — один чанк со всем массивом.
|
|
*/
|
|
export function chunk<T>(items: readonly T[], size: number): T[][] {
|
|
if (size <= 0) return items.length ? [items.slice()] : []
|
|
const out: T[][] = []
|
|
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size))
|
|
return out
|
|
}
|