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_CHUNK = 200;
|
||||||
/** Выше этого числа слотов бронь требует подтверждения. */
|
|
||||||
const BOOK_CONFIRM_THRESHOLD = 500;
|
|
||||||
/** Оценка высоты строки таблицы (px) для виртуализатора. */
|
/** Оценка высоты строки таблицы (px) для виртуализатора. */
|
||||||
const ROW_HEIGHT = 34;
|
const ROW_HEIGHT = 34;
|
||||||
|
|
||||||
@@ -517,12 +515,8 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
|||||||
if (!selectedRequestId || opBusy || selectedBookableCount === 0) return;
|
if (!selectedRequestId || opBusy || selectedBookableCount === 0) return;
|
||||||
const toBook = collectSelected();
|
const toBook = collectSelected();
|
||||||
if (toBook.length === 0) return;
|
if (toBook.length === 0) return;
|
||||||
// Крупная бронь — сначала подтверждение, потом чанкованный POST.
|
// Бронь — всегда через подтверждение (симметрично «Снять всё»), затем чанкованный POST.
|
||||||
if (toBook.length > BOOK_CONFIRM_THRESHOLD) {
|
|
||||||
setPendingBook(toBook);
|
setPendingBook(toBook);
|
||||||
return;
|
|
||||||
}
|
|
||||||
void performBook(toBook);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmPendingBook = () => {
|
const confirmPendingBook = () => {
|
||||||
@@ -579,7 +573,19 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
|||||||
|
|
||||||
// Снять ВСЕ брони заявки — тоже пачками (request-aware batch) с прогрессом. Необратимо →
|
// Снять ВСЕ брони заявки — тоже пачками (request-aware batch) с прогрессом. Необратимо →
|
||||||
// подтверждение. Отдельный флаг cancelling, чтобы «Снятие…» горело именно на этой кнопке.
|
// подтверждение. Отдельный флаг 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 = () => {
|
const cancelAll = () => {
|
||||||
if (!selectedRequestId || opBusy || bookedSlots.length === 0) return;
|
if (!selectedRequestId || opBusy || bookedSlots.length === 0) return;
|
||||||
@@ -730,11 +736,16 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Брони вне текущего окна суток (их нет в filteredSlots) — показываем на карте отдельным слоем,
|
// Брони вне текущего окна суток (их нет в filteredSlots) — показываем на карте отдельным слоем,
|
||||||
// чтобы оператор видел все брони заявки. Те, что в окне, уже нарисованы циклом выше.
|
// чтобы оператор видел все брони заявки. Те, что в окне, уже нарисованы циклом выше. НО слой
|
||||||
|
// подчиняем фильтрам видимости: выключен статус «бронь» (или скрыт КА/ветвь) — бронь не рисуем,
|
||||||
|
// иначе фильтр убирал бы бронь из таблицы, но не с карты.
|
||||||
|
if (statusFilter.has("booked")) {
|
||||||
const slotKeysShown = new Set(filteredSlots.map(slotKey));
|
const slotKeysShown = new Set(filteredSlots.map(slotKey));
|
||||||
for (const slot of bookedSlots) {
|
for (const slot of bookedSlots) {
|
||||||
const key = slotKey(slot);
|
const key = slotKey(slot);
|
||||||
if (slotKeysShown.has(key)) continue;
|
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)) {
|
for (const ring of ringsFor(key, slot.contour)) {
|
||||||
if (ring.length < 3) continue;
|
if (ring.length < 3) continue;
|
||||||
polygons.push({
|
polygons.push({
|
||||||
@@ -749,8 +760,21 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return { polygons, lines: [], markers: [] };
|
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>(
|
const layers = useMemo<Tgu2DMapLayersState>(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -1186,7 +1210,7 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
|||||||
)}
|
)}
|
||||||
{pendingBook && (
|
{pendingBook && (
|
||||||
<div className="tgu-slots-confirm">
|
<div className="tgu-slots-confirm">
|
||||||
<span>Забронировать {pendingBook.length} слотов? Это крупная бронь.</span>
|
<span>Забронировать выбранные слоты ({pendingBook.length})?</span>
|
||||||
<div className="tgu-slots-confirm__actions">
|
<div className="tgu-slots-confirm__actions">
|
||||||
<button className="tgu-button tgu-button--primary tgu-button--sm" type="button" onClick={confirmPendingBook}>
|
<button className="tgu-button tgu-button--primary tgu-button--sm" type="button" onClick={confirmPendingBook}>
|
||||||
Забронировать
|
Забронировать
|
||||||
|
|||||||
Reference in New Issue
Block a user