feat(tgu-ui): вкладка «Слоты» — расчёт покрытия и бронирование слотов заявки

- новая вкладка: заявки интервала на карте, выбор заявки кликом/инспектором,
  расчёт слотов покрытия, бронирование и снятие брони
- выбор заявки без выпадающего списка (не масштабируется на ~10к): все заявки
  на карте, панель справа с кнопкой «Выбрать», имя выбранной заявки в тулбаре,
  кнопка «Сброс», фильтр заявок по интервалу
- бронь отмечаем по ответу POST booking/request (by-request отдаёт слот в чужом
  cycle-фрейме — ключ не совпадал, бейдж «бронь» не появлялся)
- подсветка и автоскролл активной строки по клику на слот (ручной scrollBy)
- меню выбора при плотном наложении слотов (SlotPickMenu); контур заявки
  pickable:false, чтобы не «съедать» меню; панель не закрывается при выборе
- фильтр отображения по суткам (SlotDaySelector)
- gateway: маршрут slots (/api/pcp-slots/**)
- карточка полосы обзора (swath): КА и виток в инспекторе
This commit is contained in:
Дмитрий Соловьев
2026-06-11 12:20:28 +03:00
parent 802c966347
commit 6689351ef0
18 changed files with 1735 additions and 47 deletions
@@ -23,6 +23,14 @@
overflow-x: auto;
}
.tgu-toolbar__row--range {
min-height: 0;
padding-top: 0.55rem;
padding-bottom: 0.6rem;
gap: 0.5rem;
border-top: 1px solid var(--line);
}
.tgu-toolbar__brand {
display: flex;
align-items: center;
@@ -906,6 +914,215 @@
min-height: 0;
}
/* Вкладка «Слоты»: слева список слотов, справа карта. */
.tgu-slots-body {
display: grid;
grid-template-columns: minmax(22rem, 32rem) minmax(0, 1fr);
min-width: 0;
min-height: 0;
}
.tgu-slots-list {
min-width: 0;
min-height: 0;
overflow: auto;
border-right: 1px solid var(--line);
background: var(--bg-panel);
}
.tgu-slots-empty {
padding: 1rem;
color: var(--text-dim);
font-size: 0.82rem;
}
/* Имя выбранной заявки в тулбаре (на месте прежнего выпадающего списка). */
.tgu-slots-request {
align-items: center;
}
.tgu-slots-request__name {
min-height: 2rem;
display: flex;
align-items: center;
max-width: 18rem;
padding: 0.38rem 0.55rem;
border: 1px solid var(--line);
border-radius: 6px;
background: #101214;
color: var(--text);
font-size: 0.78rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Левая панель в режиме «заявка не выбрана»: фильтр интервала заявок + подсказка. */
.tgu-slots-requests-panel {
display: grid;
gap: 0.6rem;
padding: 0.9rem;
}
/* Кнопка «Сброс» — справа над картой (поверх неё). */
.tgu-slots-reset {
position: absolute;
top: 0.55rem;
right: 0.55rem;
z-index: 21;
background: rgba(23, 26, 28, 0.93);
backdrop-filter: blur(6px);
}
/* Меню выбора слота при плотном наложении — плавающий список справа над картой (под «Сбросом»). */
.tgu-slots-pick {
position: absolute;
top: 3rem;
right: 0.55rem;
z-index: 24;
width: 22rem;
max-width: calc(100% - 1.1rem);
max-height: calc(100% - 3.6rem);
display: flex;
flex-direction: column;
border: 1px solid var(--line);
border-radius: 8px;
background: rgba(23, 26, 28, 0.96);
backdrop-filter: blur(6px);
overflow: hidden;
}
.tgu-slots-pick__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
padding: 0.5rem 0.65rem;
border-bottom: 1px solid var(--line);
font-size: 0.78rem;
font-weight: 600;
color: var(--text);
}
.tgu-slots-pick__list {
overflow: auto;
min-height: 0;
}
.tgu-slots-pick__item {
display: grid;
gap: 0.15rem;
width: 100%;
padding: 0.45rem 0.65rem;
border: 0;
border-bottom: 1px solid var(--line);
background: transparent;
text-align: left;
cursor: pointer;
color: var(--text);
}
.tgu-slots-pick__item:hover {
background: rgba(104, 195, 189, 0.1);
}
.tgu-slots-pick__item.is-active {
background: rgba(104, 195, 189, 0.22);
box-shadow: inset 3px 0 0 var(--accent);
}
.tgu-slots-pick__title {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: 0.78rem;
font-weight: 600;
}
.tgu-slots-pick__meta {
font-family: var(--mono);
font-size: 0.7rem;
color: var(--text-dim);
}
/* Инспектор заявки — плавающая панель справа над картой (до выбора заявки). */
.tgu-slots-inspector {
position: absolute;
top: 0.55rem;
right: 0.55rem;
z-index: 25;
width: 23rem;
max-width: calc(100% - 1.1rem);
max-height: calc(100% - 1.1rem);
overflow: auto;
padding: 0.9rem;
border: 1px solid var(--line);
border-radius: 8px;
background: rgba(23, 26, 28, 0.96);
backdrop-filter: blur(6px);
}
.tgu-slots-table {
width: 100%;
border-collapse: collapse;
font-size: 0.78rem;
}
.tgu-slots-table th,
.tgu-slots-table td {
padding: 0.35rem 0.5rem;
text-align: left;
white-space: nowrap;
border-bottom: 1px solid var(--line);
}
.tgu-slots-table thead th {
position: sticky;
top: 0;
z-index: 1;
background: var(--bg-panel);
color: var(--text-dim);
font-weight: 600;
}
.tgu-slots-table tbody tr {
cursor: pointer;
}
.tgu-slots-table tbody tr:hover {
background: rgba(104, 195, 189, 0.08);
}
.tgu-slots-table tbody tr.is-active {
background: rgba(104, 195, 189, 0.3);
box-shadow: inset 3px 0 0 var(--accent);
}
.tgu-slots-table tbody tr.is-active td {
color: var(--text);
font-weight: 600;
}
.tgu-slots-table tbody tr.is-active:hover {
background: rgba(104, 195, 189, 0.36);
}
.tgu-slots-badge {
display: inline-block;
padding: 0.1rem 0.45rem;
border-radius: 999px;
background: #101214;
border: 1px solid var(--line);
color: var(--text-muted);
font-size: 0.72rem;
}
.tgu-slots-badge--booked {
border-color: rgba(104, 195, 189, 0.58);
background: rgba(104, 195, 189, 0.16);
color: var(--accent-strong);
}
.tgu-map-stage {
position: relative;
min-width: 0;
@@ -1052,6 +1269,75 @@
background: var(--bg-panel);
color: var(--text);
font-weight: 700;
cursor: pointer;
}
.tgu-2d-map__zoom button:hover {
border-color: rgba(104, 195, 189, 0.5);
}
/* Кнопка-линейка: активный режим подсвечиваем как тумблер. */
.tgu-2d-map__zoom-ruler.is-active {
border-color: rgba(104, 195, 189, 0.58);
background: rgba(104, 195, 189, 0.18);
color: var(--accent-strong);
}
/* Замер линейки на оверлее: ломаная, вершины и подписи расстояния. */
.tgu-2d-map__ruler-line {
stroke: #f0ad2e;
stroke-width: 2;
stroke-dasharray: 6 4;
}
.tgu-2d-map__ruler-vertex {
fill: #f0ad2e;
stroke: #1a1305;
stroke-width: 1;
}
.tgu-2d-map__ruler-label {
fill: #ffe7b5;
font-size: 11px;
font-weight: 600;
paint-order: stroke;
stroke: rgba(10, 12, 14, 0.85);
stroke-width: 3;
}
/* Бейдж итоговой длины — в правом нижнем углу, над кнопками управления. */
.tgu-2d-map__ruler-total {
position: absolute;
right: 0.75rem;
bottom: 9rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.3rem 0.55rem;
border: 1px solid rgba(240, 173, 46, 0.42);
border-radius: 7px;
background: rgba(17, 19, 21, 0.9);
color: #ffe7b5;
font-size: 0.78rem;
backdrop-filter: blur(6px);
}
.tgu-2d-map__ruler-total-label {
color: var(--text-dim);
}
.tgu-2d-map__ruler-total-value {
font-weight: 700;
}
.tgu-2d-map__ruler-reset {
border: 1px solid var(--line);
border-radius: 5px;
padding: 0.1rem 0.45rem;
background: #101214;
color: var(--text-muted);
font-size: 0.72rem;
cursor: pointer;
}
.tgu-map-overlay {