feat(tgu-slots): подтверждение брони всегда + «Снять всё» снимает всю заявку
- Бронь теперь всегда через диалог подтверждения (симметрично «Снять всё»), убран порог в 500 слотов. - «Снять всё» дочитывает брони заявки через by-request и снимает объединение локального и серверного наборов bookedSlotId — гарантированно вся заявка. - Карта: забронированные слоты вне окна суток подчинены фильтрам видимости (статус «бронь»/КА/ветвь) — при выключенном фильтре брони их не видно.
This commit is contained in:
@@ -36,8 +36,6 @@ const DAY_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
/** Размер чанка при бронировании больших наборов (бэк бронирует слоты по одному). */
|
||||
const BOOK_CHUNK = 200;
|
||||
/** Выше этого числа слотов бронь требует подтверждения. */
|
||||
const BOOK_CONFIRM_THRESHOLD = 500;
|
||||
/** Оценка высоты строки таблицы (px) для виртуализатора. */
|
||||
const ROW_HEIGHT = 34;
|
||||
|
||||
@@ -517,12 +515,8 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
||||
if (!selectedRequestId || opBusy || selectedBookableCount === 0) return;
|
||||
const toBook = collectSelected();
|
||||
if (toBook.length === 0) return;
|
||||
// Крупная бронь — сначала подтверждение, потом чанкованный POST.
|
||||
if (toBook.length > BOOK_CONFIRM_THRESHOLD) {
|
||||
// Бронь — всегда через подтверждение (симметрично «Снять всё»), затем чанкованный POST.
|
||||
setPendingBook(toBook);
|
||||
return;
|
||||
}
|
||||
void performBook(toBook);
|
||||
};
|
||||
|
||||
const confirmPendingBook = () => {
|
||||
@@ -579,7 +573,19 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
||||
|
||||
// Снять ВСЕ брони заявки — тоже пачками (request-aware batch) с прогрессом. Необратимо →
|
||||
// подтверждение. Отдельный флаг cancelling, чтобы «Снятие…» горело именно на этой кнопке.
|
||||
const performCancelAll = () => runCancel(bookedSlots.map((slot) => slot.bookedSlotId), setCancelling);
|
||||
// Источник истины — backend: перечитываем брони by-request и снимаем ОБЪЕДИНЕНИЕ локального
|
||||
// набора и серверного. Так «Снять всё» убирает вообще все брони заявки, даже если локальный
|
||||
// bookedSlots разошёлся с базой (bookedSlotId надёжен независимо от cycle-фрейма брони).
|
||||
const performCancelAll = async () => {
|
||||
const idSet = new Set(bookedSlots.map((slot) => slot.bookedSlotId));
|
||||
try {
|
||||
const fresh = await fetchBookedSlotsByRequest(selectedRequestId);
|
||||
for (const slot of fresh) idSet.add(slot.bookedSlotId);
|
||||
} catch {
|
||||
// by-request недоступен — снимаем по локальному набору броней.
|
||||
}
|
||||
await runCancel(Array.from(idSet), setCancelling);
|
||||
};
|
||||
|
||||
const cancelAll = () => {
|
||||
if (!selectedRequestId || opBusy || bookedSlots.length === 0) return;
|
||||
@@ -730,11 +736,16 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
||||
}
|
||||
}
|
||||
// Брони вне текущего окна суток (их нет в filteredSlots) — показываем на карте отдельным слоем,
|
||||
// чтобы оператор видел все брони заявки. Те, что в окне, уже нарисованы циклом выше.
|
||||
// чтобы оператор видел все брони заявки. Те, что в окне, уже нарисованы циклом выше. НО слой
|
||||
// подчиняем фильтрам видимости: выключен статус «бронь» (или скрыт КА/ветвь) — бронь не рисуем,
|
||||
// иначе фильтр убирал бы бронь из таблицы, но не с карты.
|
||||
if (statusFilter.has("booked")) {
|
||||
const slotKeysShown = new Set(filteredSlots.map(slotKey));
|
||||
for (const slot of bookedSlots) {
|
||||
const key = slotKey(slot);
|
||||
if (slotKeysShown.has(key)) continue;
|
||||
if (!satFilter.has(slot.satelliteId)) continue;
|
||||
if (!branchFilter.has(slot.revolutionSign)) continue;
|
||||
for (const ring of ringsFor(key, slot.contour)) {
|
||||
if (ring.length < 3) continue;
|
||||
polygons.push({
|
||||
@@ -749,8 +760,21 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return { polygons, lines: [], markers: [] };
|
||||
}, [hasRequest, requestItems, selectedRequest, slots, filteredSlots, bookedSlots, bookedKeys, ringsFor]);
|
||||
}, [
|
||||
hasRequest,
|
||||
requestItems,
|
||||
selectedRequest,
|
||||
slots,
|
||||
filteredSlots,
|
||||
bookedSlots,
|
||||
bookedKeys,
|
||||
ringsFor,
|
||||
statusFilter,
|
||||
satFilter,
|
||||
branchFilter
|
||||
]);
|
||||
|
||||
const layers = useMemo<Tgu2DMapLayersState>(
|
||||
() => ({
|
||||
@@ -1186,7 +1210,7 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
||||
)}
|
||||
{pendingBook && (
|
||||
<div className="tgu-slots-confirm">
|
||||
<span>Забронировать {pendingBook.length} слотов? Это крупная бронь.</span>
|
||||
<span>Забронировать выбранные слоты ({pendingBook.length})?</span>
|
||||
<div className="tgu-slots-confirm__actions">
|
||||
<button className="tgu-button tgu-button--primary tgu-button--sm" type="button" onClick={confirmPendingBook}>
|
||||
Забронировать
|
||||
|
||||
Reference in New Issue
Block a user