feat(slots): снятие брони пачками с прогрессом (симметрично бронированию)

Бэк (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.
This commit is contained in:
Дмитрий Соловьев
2026-06-15 14:26:06 +03:00
parent b9adbce207
commit 33b56ba951
13 changed files with 756 additions and 91 deletions
@@ -1404,7 +1404,8 @@
position: absolute;
z-index: 5;
top: 0.85rem;
left: 0.85rem;
left: 50%;
transform: translateX(-50%);
max-width: 23rem;
border: 1px solid rgba(240, 173, 46, 0.42);
border-radius: 7px;
@@ -1462,6 +1463,83 @@
gap: 0.4rem;
}
/* Прогресс-бар чанковой операции (бронь и снятие). В одной палитре и позиции с оверлеем
«Расчёт слотов…»: сверху по центру, янтарный на тёмном — чтобы не перекрывать нижнюю подсказку
«много слотов в окне» и читаться как одно семейство уведомлений. */
.tgu-chunk-progress {
position: absolute;
z-index: 6;
top: 0.85rem;
left: 50%;
transform: translateX(-50%);
width: min(23rem, 80%);
padding: 0.65rem 0.75rem;
border: 1px solid rgba(240, 173, 46, 0.42);
border-radius: 7px;
background: rgba(17, 19, 21, 0.88);
color: #ffe7b5;
font-size: 0.82rem;
backdrop-filter: blur(6px);
}
.tgu-chunk-progress__head {
display: flex;
justify-content: space-between;
margin-bottom: 0.4rem;
}
.tgu-chunk-progress__count {
color: rgba(255, 231, 181, 0.7);
font-variant-numeric: tabular-nums;
}
.tgu-chunk-progress__track {
height: 5px;
border-radius: 3px;
background: rgba(255, 255, 255, 0.12);
overflow: hidden;
}
.tgu-chunk-progress__fill {
height: 100%;
border-radius: 3px;
background: #f0ad2e;
transition: width 0.15s ease;
}
/* Ячейка статуса брони с кнопкой снятия одного слота. */
.tgu-slots-status-cell {
display: inline-flex;
align-items: center;
gap: 0.4rem;
}
.tgu-slots-cancel-one {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.1rem;
height: 1.1rem;
padding: 0;
border: 1px solid var(--line);
border-radius: 4px;
background: transparent;
color: var(--text-muted);
font-size: 0.7rem;
line-height: 1;
cursor: pointer;
}
.tgu-slots-cancel-one:hover:not(:disabled) {
border-color: var(--danger, #c0524f);
color: var(--danger, #c0524f);
}
.tgu-slots-cancel-one:disabled {
opacity: 0.4;
cursor: default;
}
.tgu-map-errors {
display: grid;
gap: 0.4rem;