diff --git a/services/pcp-tgu-ui-service/src/features/tgu-slots/TguSlotsTab.tsx b/services/pcp-tgu-ui-service/src/features/tgu-slots/TguSlotsTab.tsx index 71ad41c..b1cb8aa 100644 --- a/services/pcp-tgu-ui-service/src/features/tgu-slots/TguSlotsTab.tsx +++ b/services/pcp-tgu-ui-service/src/features/tgu-slots/TguSlotsTab.tsx @@ -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) { - setPendingBook(toBook); - return; - } - void performBook(toBook); + // Бронь — всегда через подтверждение (симметрично «Снять всё»), затем чанкованный POST. + setPendingBook(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,27 +736,45 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) { } } // Брони вне текущего окна суток (их нет в filteredSlots) — показываем на карте отдельным слоем, - // чтобы оператор видел все брони заявки. Те, что в окне, уже нарисованы циклом выше. - const slotKeysShown = new Set(filteredSlots.map(slotKey)); - for (const slot of bookedSlots) { - const key = slotKey(slot); - if (slotKeysShown.has(key)) continue; - for (const ring of ringsFor(key, slot.contour)) { - if (ring.length < 3) continue; - polygons.push({ - id: `booked:${key}`, - name: `КА ${slot.satelliteId} · слот ${slot.slotNumber} (бронь)`, - kind: "complexPlan", - layer: "complexPlan", - points: ring, - spacecraftId: String(slot.satelliteId), - revolution: slot.revolution, - zIndex: z++ - }); + // чтобы оператор видел все брони заявки. Те, что в окне, уже нарисованы циклом выше. НО слой + // подчиняем фильтрам видимости: выключен статус «бронь» (или скрыт КА/ветвь) — бронь не рисуем, + // иначе фильтр убирал бы бронь из таблицы, но не с карты. + 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({ + id: `booked:${key}`, + name: `КА ${slot.satelliteId} · слот ${slot.slotNumber} (бронь)`, + kind: "complexPlan", + layer: "complexPlan", + points: ring, + spacecraftId: String(slot.satelliteId), + revolution: slot.revolution, + zIndex: z++ + }); + } } } 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( () => ({ @@ -1186,7 +1210,7 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) { )} {pendingBook && (
- Забронировать {pendingBook.length} слотов? Это крупная бронь. + Забронировать выбранные слоты ({pendingBook.length})?