This commit is contained in:
2026-05-09 11:20:17 +05:00
parent 3e91ebd083
commit 67b7008bf3
2 changed files with 180 additions and 23 deletions

View File

@@ -236,6 +236,7 @@ tr.row-checkout-today { background: #fef2f2 !important; border-left: 4px solid #
</div>
<div class="card-body-custom">
<div class="filter-bar">
<input type="text" id="searchBookings" class="form-control" style="width: 200px; font-size: 0.85rem;" placeholder="Поиск по имени/телефону..." onkeyup="debounceSearch()">
<span class="filter-label">Фильтр:</span>
<select id="filterStatus"><option value="all">Все статусы</option><option value="новая">Новая</option><option value="оплачена">Оплачена</option><option value="зарезервирована">Зарезервирована</option><option value="заселена">Заселена</option><option value="выехала">Выехала</option><option value="отменена">Отменена</option></select>
<select id="filterUrgent"><option value="all">Все записи</option><option value="checkin-soon">Заезд ≤ 3 дней</option><option value="checkout-today">Выезд сегодня</option></select>
@@ -249,6 +250,14 @@ tr.row-checkout-today { background: #fef2f2 !important; border-left: 4px solid #
<tbody id="allBookings"></tbody>
</table>
</div>
<div class="pagination-container" style="display: flex; justify-content: space-between; align-items: center; margin-top: 16px; padding-top: 16px; border-top: 1px solid #e2e8f0;">
<div id="paginationInfo" style="font-size: 0.85rem; color: #64748b;"></div>
<div class="pagination-controls" style="display: flex; gap: 8px;">
<button class="btn btn-outline-secondary btn-sm" id="prevPage" onclick="changePage(-1)" disabled>← Назад</button>
<span id="pageNumbers" style="display: flex; gap: 4px;"></span>
<button class="btn btn-outline-secondary btn-sm" id="nextPage" onclick="changePage(1)" disabled>Вперёд →</button>
</div>
</div>
</div>
</div>
</div>
@@ -560,15 +569,98 @@ async function loadUsers() {
let allBookingsData = [];
let sortDirection = 'asc';
let bookingsLoaded = false;
let currentPage = 1;
let totalPages = 1;
let searchTimeout = null;
async function loadBookings() {
if (bookingsLoaded) { renderBookings(); return; }
async function loadBookings(resetPage = true) {
if (resetPage) currentPage = 1;
try {
allBookingsData = await api('/api/admin/bookings');
const search = document.getElementById('searchBookings').value;
const status = document.getElementById('filterStatus').value;
const limit = 20;
const params = new URLSearchParams({
page: currentPage,
limit: limit,
search: search,
status: status
});
const response = await api('/api/admin/bookings?' + params.toString());
allBookingsData = response.data;
totalPages = response.pagination.totalPages;
currentPage = response.pagination.page;
bookingsLoaded = true;
renderBookings();
updateBookingStats();
} catch(err) { showToast('Нет доступа к бронированиям', 'error'); }
updatePagination();
} catch(err) { showToast('Нет доступа к бронированиям: ' + err.message, 'error'); }
}
function updatePagination() {
const info = document.getElementById('paginationInfo');
const prevBtn = document.getElementById('prevPage');
const nextBtn = document.getElementById('nextPage');
const pageNumbers = document.getElementById('pageNumbers');
const total = allBookingsData.length;
const from = (currentPage - 1) * 20 + 1;
const to = Math.min(currentPage * 20, total);
info.textContent = total > 0 ? `Показано ${from}-${to} из ${total}` : 'Нет записей';
prevBtn.disabled = currentPage <= 1;
nextBtn.disabled = currentPage >= totalPages;
// Page numbers
let pagesHtml = '';
const maxVisible = 5;
let start = Math.max(1, currentPage - Math.floor(maxVisible / 2));
let end = Math.min(totalPages, start + maxVisible - 1);
if (end - start < maxVisible - 1) start = Math.max(1, end - maxVisible + 1);
if (start > 1) {
pagesHtml += `<button class="btn btn-outline-secondary btn-sm" onclick="goToPage(1)">1</button>`;
if (start > 2) pagesHtml += `<span style="padding: 0 4px;">...</span>`;
}
for (let i = start; i <= end; i++) {
if (i === currentPage) {
pagesHtml += `<button class="btn btn-primary btn-sm" onclick="goToPage(${i})">${i}</button>`;
} else {
pagesHtml += `<button class="btn btn-outline-secondary btn-sm" onclick="goToPage(${i})">${i}</button>`;
}
}
if (end < totalPages) {
if (end < totalPages - 1) pagesHtml += `<span style="padding: 0 4px;">...</span>`;
pagesHtml += `<button class="btn btn-outline-secondary btn-sm" onclick="goToPage(${totalPages})">${totalPages}</button>`;
}
pageNumbers.innerHTML = pagesHtml;
}
function changePage(delta) {
const newPage = currentPage + delta;
if (newPage >= 1 && newPage <= totalPages) {
currentPage = newPage;
loadBookings(false);
}
}
function goToPage(page) {
if (page >= 1 && page <= totalPages) {
currentPage = page;
loadBookings(false);
}
}
function debounceSearch() {
if (searchTimeout) clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => loadBookings(true), 500);
}
function getDaysDiff(dateStr) {
@@ -588,14 +680,6 @@ function renderBookings() {
const filterStatus = document.getElementById('filterStatus').value;
const filterUrgent = document.getElementById('filterUrgent').value;
if (filterStatus !== 'all') rows = rows.filter(r => r.status === filterStatus);
if (filterUrgent === 'checkin-soon') {
rows = rows.filter(r => { const diff = getDaysDiff(r.checkin_date); return diff >= 0 && diff <= 3 && r.status !== 'отменена' && r.status !== 'выехала'; });
} else if (filterUrgent === 'checkout-today') {
rows = rows.filter(r => getDaysDiff(r.checkout_date) === 0 && r.status !== 'отменена' && r.status !== 'выехала');
}
rows.sort((a, b) => {
const da = new Date(a.checkin_date), db = new Date(b.checkin_date);
return sortDirection === 'asc' ? da - db : db - da;
@@ -668,8 +752,8 @@ async function changeRoom(id, room_type) {
}
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('filterStatus').addEventListener('change', renderBookings);
document.getElementById('filterUrgent').addEventListener('change', renderBookings);
document.getElementById('filterStatus').addEventListener('change', loadBookings);
document.getElementById('filterUrgent').addEventListener('change', loadBookings);
document.getElementById('sortAsc').style.opacity = '1';
document.getElementById('sortDesc').style.opacity = '0.5';
});
@@ -889,6 +973,10 @@ async function changeDetails(id, field, value) {
try {
const body = {};
if (field === 'adults' || field === 'children') {
if (value === '' || isNaN(parseInt(value))) {
showToast('Введите корректное число', 'error');
return;
}
body[field] = parseInt(value);
} else {
body[field] = value;
@@ -897,7 +985,10 @@ async function changeDetails(id, field, value) {
allBookingsData = allBookingsData.map(b => b.id === id ? data.booking : b);
renderBookings(); updateBookingStats(); loadDashboard();
showToast('Данные обновлены');
} catch(err) { showToast(err.message, 'error'); }
} catch(err) {
showToast(err.message || 'Ошибка при сохранении', 'error');
loadBookings(false);
}
}
checkAuth();