Files
dc-observatio/services/pcp-tgu-ui-service/src/features/tgu-slots/PassStepper.tsx
T
Дмитрий Соловьев c402f376c5 feat(tgu-ui): степпер по прохождениям и подсветка конфликтов выбора слотов
Степпер прохождений (виток как проход над районом):
- slotPasses.ts: группировка слотов в проходы (КА·cycle·revolution·sign) в
  хронологии по всем КА + юнит-тесты;
- PassStepper.tsx: ◀/▶ листают проходы, окно суток следует за курсором,
  таблица скроллится к проходу, карта не прыгает; выбранный проход
  изолирует отображение, «Все» — назад ко всему набору;
- дата/время прохождения отдельной строкой во всю ширину (видно целиком).

Подсветка конфликтов выбора (мягкая подсказка «станет недоступным»):
- slotConflicts.ts: тот же КА + недостаточный зазор на манёвр — 10 с при том
  же крене, 30 с при смене крена (ROLL_EPSILON); конфликтные слоты гаснут в
  таблице и на карте, остаются выбираемыми (жёсткая выполнимость — за бэком);
- производительность: оконный поиск по времени (~O(N·logM)), кандидаты —
  только видимые, выбор через useDeferredValue (не молотит во время drag);
- чекбокс «Гасить конфликты» в строке выбора: выключает и слой, и расчёт;
- юнит-тесты на правило зазора и оконный поиск.

Карта: проп dimmedObjectIds — серый «налёт» поверх конфликтных слотов в SVG
overlay (без изменения canvas).
2026-06-17 13:31:28 +03:00

73 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
type PassStepperProps = {
/** Индекс текущего витка (0-based) или -1, если виток не выбран. */
index: number;
/** Всего витков в наборе. */
total: number;
/** Подпись текущего витка (КА · оборот · ветвь · время) — пустая, если виток не выбран. */
label: string;
canPrev: boolean;
canNext: boolean;
onPrev: () => void;
onNext: () => void;
/** Сброс курсора витка — вернуться к показу всего набора. */
onClear: () => void;
};
/**
* Степпер по виткам (проходам): ◀/▶ переводят курсор на предыдущий/следующий проход в хронологии,
* таблица скроллится к нему. Пока прохождение выбрано — отображение изолировано до него; «Все»
* возвращает к показу всего набора. Стоит под фильтром «Сутки»: сутки — грубый диапазон, проход —
* точный курсор внутри.
*/
export function PassStepper({
index,
total,
label,
canPrev,
canNext,
onPrev,
onNext,
onClear,
}: PassStepperProps) {
const active = index >= 0;
return (
<div className="map-pass-selector tgu-pass-stepper">
<div className="map-pass-selector__header">
<span className="map-pass-selector__title">Прохождение</span>
<span className="map-pass-selector__nums">{active ? `${index + 1} / ${total}` : `— / ${total}`}</span>
<span className="map-pass-selector__times">{active ? "" : "выберите прохождение ◀ ▶"}</span>
{active && (
<button type="button" className="map-pass-selector__reset" title="Показать все прохождения" onClick={onClear}>
Все
</button>
)}
</div>
{active && <div className="tgu-pass-stepper__label">{label}</div>}
<div className="tgu-pass-stepper__nav">
<button
type="button"
className="tgu-pass-stepper__btn"
onClick={onPrev}
disabled={!canPrev}
title="Предыдущее прохождение"
aria-label="Предыдущее прохождение"
>
</button>
<button
type="button"
className="tgu-pass-stepper__btn"
onClick={onNext}
disabled={!canNext}
title="Следующее прохождение"
aria-label="Следующее прохождение"
>
</button>
</div>
</div>
);
}