- конфликтные слоты нельзя добавить в выбор под бронь (клик по строке,
Shift-диапазон, выбор полосы на карте, «выбрать все») — жёсткая блокировка;
снятие выбора не блокируется (конфликт считается только для невыбранных)
- слоты одной полосы между собой не конфликтуют (groupOf) — иначе нельзя было
бы набрать полосу поштучным кликом
- чекбокс «Конфликты» теперь и гасит, и блокирует выбор; выкл. — выбирать любые
- переход на прохождение (◀/▶) больше не подсвечивает никакой слот: активная
подсветка строки — только по явному клику; добавлена прокрутка к началу прохода
- повторный Shift+клик по тому же концу снимает выделение (таблица и карта)
Степпер прохождений (виток как проход над районом):
- slotPasses.ts: группировка слотов в проходы (КА·cycle·revolution·sign) в
хронологии по всем КА + юнит-тесты;
- PassStepper.tsx: ◀/▶ листают проходы, окно суток следует за курсором,
таблица скроллится к проходу, карта не прыгает; выбранный проход
изолирует отображение, «Все» — назад ко всему набору;
- дата/время прохождения отдельной строкой во всю ширину (видно целиком).
Подсветка конфликтов выбора (мягкая подсказка «станет недоступным»):
- slotConflicts.ts: тот же КА + недостаточный зазор на манёвр — 10 с при том
же крене, 30 с при смене крена (ROLL_EPSILON); конфликтные слоты гаснут в
таблице и на карте, остаются выбираемыми (жёсткая выполнимость — за бэком);
- производительность: оконный поиск по времени (~O(N·logM)), кандидаты —
только видимые, выбор через useDeferredValue (не молотит во время drag);
- чекбокс «Гасить конфликты» в строке выбора: выключает и слой, и расчёт;
- юнит-тесты на правило зазора и оконный поиск.
Карта: проп dimmedObjectIds — серый «налёт» поверх конфликтных слотов в SVG
overlay (без изменения canvas).
Слайдер «Сутки» раскрывался на весь диапазон после брони: bookedSlots менялся → новый референс days → эффект сбрасывал окно, хотя набор суток тот же. Завязал сброс на сигнатуру набора суток (границы дней), а не на референс массива.
Замена авто-чипа «полоса ×N» на ручной жест. Полоса переопределена:
один КА + один крен (ε) + подряд по времени; виток в границу не входит.
- slotStrips.ts: groupStrips → { idByKey, membersOf }; группировка по КА →
кластеры по крену (ROLL_EPSILON=0.5) → деление по каденции (CADENCE_GAP_FACTOR=1.5)
- slotSelection.ts: selectStripRange вместо toggleStripSelection
- TguSlotsTab.tsx: клик по строке = якорь + выбор; Shift+клик тянет полосу
между якорем и строкой; выход за полосу запрещён + подсказка; рельс полосы якоря
- tgu-planning.css: убраны стили чипа/hover; user-select:none на строках
- тесты обновлены (125 зелёных), tsc/build чисто
- бэклог-pcp-tgu-ops-ui: #50 (M1/M2/M3 слой поддержки решений), #51 (вызовы через
gateway), #52/#53 (находки /review), #49 (баг обновления BUSY после снятия брони),
раздел «Выполнено» (чистки по /review).
- docs/tasks/TASK_slot-strip-selection: дизайн-план и артефакты /autoplan по выбору
полосами; отметка о реализации it.1.
reqCover(all=true) обогащает занятые (BUSY) слоты держателями: какие заявки
блокируют слот, с именем и важностью (обзор конфликтов при ручной броне).
- SlotHolderDTO (requestId + опц. name/importance), поле holders в SlotDTO
(null для AVAILABLE/BOOKED и прочих путей — обратно совместимо).
- TestSatelliteImpl: для BUSY-слота собирает держателей по пересечению
сдвинутых на cycle интервалов активных броней (та же логика, что band, с mmi),
distinct по requestId (множественный держатель).
- EarthService.requestHolders: батч-резолв name/importance заявок; деградация
через runCatching → emptyMap (request-service недоступен → только id, без падения).
- SlotService.enrichHolders: один батч-запрос на обзор, slot.copy(holders=...).
- SlotDTO переведён в data class (copy вместо ручного перечисления полей).
- Тест: BUSY несёт держателей, BOOKED/AVAILABLE — нет.
- Бронь теперь всегда через диалог подтверждения (симметрично «Снять всё»),
убран порог в 500 слотов.
- «Снять всё» дочитывает брони заявки через by-request и снимает объединение
локального и серверного наборов bookedSlotId — гарантированно вся заявка.
- Карта: забронированные слоты вне окна суток подчинены фильтрам видимости
(статус «бронь»/КА/ветвь) — при выключенном фильтре брони их не видно.
GET booking/by-request/{id} возвращал tn/tk/revolution из базового
cycle-фрейма без сдвига (BookedSlotEntity.toViewDTO), тогда как все
остальные пути (toDetails, allByInterval→shiftedToCycle,
resolveBookedSlotStartTime) сдвигают на cycle*durationCalc и
cycle*cycleRevs. Для броней с cycle > 0 дата уходила на cycle*durationCalc
суток раньше расчётной (cycle=0 совпадал, поэтому баг прятался; #42
починил только ключ sat:slotNum:cycle, не время).
Сдвиг перенесён в SlotService.bookedByReqId через новый
toShiftedViewDTO(satellite); КА резолвится из каталога, нет в каталоге —
базовый фрейм без падения списка. Небезопасный базовый билдер в сущности
удалён. Контур/координаты не сдвигаются (наземный след повторяется
каждый цикл), симметрично shiftedToCycle.
Регрессия в SlotServiceCancelTest: сдвиг tn/tk/витка на cycle брони и
fallback без КА. Бэклог #48 помечен сделанным.
- Панель фильтров видимости (статус/КА/ветвь) над строкой выбора: единый
filteredSlots сужает и таблицу, и карту; пустое состояние с «Сбросить».
- Убран потолок отрисовки карты — общую картину не режем, объём сужает оператор
фильтрами.
- Карту не перецентрируем после расчёта (был прыжок на произвольный первый слот).
- «Показать все слоты» шлёт all=true и показывает занятые бронью (BUSY); бронь
их не берёт (исключены из выбора/счётчика, чекбокс disabled).
- Починен мёртвый маппинг статуса: бэкенд шлёт BUSY, фронт ждал OCCUPIED.
- BUSY отображается как «недоступен» (конфликт с бронью), а не «занят» —
чтобы не путать с самой бронью.
«Показать все слоты» должно показывать полный набор. При all=true reqCover
запрашивает статусы [AVAILABLE, BOOKED, BUSY], а selectCoverageSlots собирает
BUSY отдельно и добавляет в ответ только в ветке без покрытия — в solver
(«Подобрать покрытие») занятые слоты по-прежнему не попадают.
При наложении в крайней точке хват попадал на верхний ползунок to, который на
правом краю заперт fromIndex и не уходит влево, а нижний from был недоступен.
Теперь при fromIndex === toIndex управляемый ползунок выбирается по направлению
движения (влево → from, вправо → to). Чинит и «Сутки» (SlotDaySelector), и
«Витки» (MapPassSelector).
Бэк (slots-service):
- DELETE /booking/batch (cancelReqSlots) — снятие выбранных броней пачкой;
одиночный DELETE теперь request-aware (нужен requestId), чтобы шаренный
между заявками слот рвал только связь своей заявки, а не сносил бронь.
- BookedSlotViewDTO (+toViewDTO) с cycle БРОНИ — slotKey совпадает с ответом
POST booking/request; GET booking/by-request отдаёт View.
Фронт (pcp-tgu-ui-service):
- runChunked + ChunkProgress: бронь и снятие идут пачками по 200 с общим
прогресс-баром (стиль/позиция как у оверлея «Расчёт слотов…», сверху).
- «Снять выбранные», «Снять всё» и ✕ в строке — единый чанковый движок
runCancel (а не bulk-запрос), брони слиты в строки таблицы и выбираемы.
Тесты: SlotServiceCancelTest (шаринг/единоличная/cycle-регрессия), runChunked,
slotsApi.cancelBookedSlots/fetchBookedSlotsByRequest.
По замечаниям: Крен и Статус не помещались (8 колонок на узкую панель).
- «Виток» теперь = виток / ветвь / цикл (281 / восх / 1); отдельная «Слот/цикл» убрана
- «Начало» с секундами (formatLocalDateTimeSeconds) — слоты короткие, без секунд начало=конец
- «Конец» заменён на «Длит., с» (slotDurationSec); colSpan распорок 8→7
- Крен и Статус снова видны
Тесты 70/70, build чисто.
Вкладка «Слоты» масштабирована под ~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 чисто.
- новая вкладка: заявки интервала на карте, выбор заявки кликом/инспектором,
расчёт слотов покрытия, бронирование и снятие брони
- выбор заявки без выпадающего списка (не масштабируется на ~10к): все заявки
на карте, панель справа с кнопкой «Выбрать», имя выбранной заявки в тулбаре,
кнопка «Сброс», фильтр заявок по интервалу
- бронь отмечаем по ответу POST booking/request (by-request отдаёт слот в чужом
cycle-фрейме — ключ не совпадал, бейдж «бронь» не появлялся)
- подсветка и автоскролл активной строки по клику на слот (ручной scrollBy)
- меню выбора при плотном наложении слотов (SlotPickMenu); контур заявки
pickable:false, чтобы не «съедать» меню; панель не закрывается при выборе
- фильтр отображения по суткам (SlotDaySelector)
- gateway: маршрут slots (/api/pcp-slots/**)
- карточка полосы обзора (swath): КА и виток в инспекторе
При первой правке плана ensureDraft→createDraft(replace) падал 409, если по КА
уже есть открытый черновик на это окно (осиротевший с прошлой сессии — авто-отмена
best-effort). В UI это показывалось голым «Conflict» (на стенде не подхвачен
include-message у tgu).
Теперь на 409 от createDraft редактор подхватывает существующий черновик того же
КА с тем же окном (adoptExistingDraft через fetchDraftPlans, сверка окна ±2 c) и
продолжает в нём; если подходящего нет — читаемое сообщение со ссылкой на вкладку
«Планы» вместо «Conflict».
Обобщил ComplexInfoPanel в общий MapObjectInspector (tgu-map-2d): карточки для
всех типов объектов — заявка, включение комплексного плана, ячейка тепловой карты,
маршрут planWorks, станция, маркер КА, линия; список с раскрытием при наложении.
Вкладка «Карта» переведена на enableObjectChooser + onObjectsAtPoint + правый
инспектор — заменила разрозненные Tgu2DMapRequestsPanel/Tgu2DMapStationPanel (удалены)
и объектную часть нижней плашки (Tgu2DMapDetails ужат до контекста плана). Вкладка
«Комплексный план» использует тот же инспектор.
RequestAtPoint переехал в tgu-requests/RequestDetailsPanel (нужен вкладке «Заявки»).
Чекбокс «Тепловая карта» — фоновый слой-заливка ячеек сетки заданий по важности
(cool→hot), источник GET /api/pcp-request/v1/cells/priority-map (cellsApi + drawHeatmap).
Heatmap — полноценный слой MapPolygon (layer "heatmap", importance/cellNum), участвует
в spatial-индексе и chooser, рисуется под контурами плана/заявок.
Клик по объекту открывает правый инспектор ComplexInfoPanel (по аналогии с редактором):
карточки ячейки приоритета, включения комплексного плана и заявки; при наложении —
список объектов под точкой клика с раскрытием. При включённой тепловой карте ячейка
всегда идёт первой в списке и раскрыта по умолчанию.
Бэклог #44 отмечен сделанным.