Files
cleaning-company/public/my-bookings.html
Владимир ae5ab2554b commit 12.01
2026-01-12 14:25:15 +00:00

292 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Мои брони - КлинСервис</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; background: #f8f9fa; }
/* Хедер */
header {
background: white; padding: 20px 50px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
position: sticky; top: 0; z-index: 100;
}
.header-content {
max-width: 1200px; margin: 0 auto;
display: flex; justify-content: space-between; align-items: center;
}
.logo { font-size: 24px; font-weight: bold; color: #667eea; }
.header-nav { display: flex; gap: 20px; align-items: center; }
.btn {
padding: 12px 25px; border: none; border-radius: 25px;
cursor: pointer; font-size: 16px; text-decoration: none;
display: inline-block; transition: all 0.3s;
}
.btn-primary { background: #667eea; color: white; }
.btn-primary:hover { background: #5a67d8; }
.btn-secondary { background: transparent; color: #667eea; border: 2px solid #667eea; }
.btn-secondary:hover { background: #667eea; color: white; }
/* Контейнер */
.container { max-width: 1200px; margin: 40px auto; padding: 0 20px; }
h1 { text-align: center; color: #333; margin-bottom: 40px; font-size: 32px; }
/* Таблица броней */
.bookings-table {
background: white; border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
table { width: 100%; border-collapse: collapse; }
th, td {
padding: 20px; text-align: left; border-bottom: 1px solid #e9ecef;
}
th {
background: #667eea; color: white; font-weight: bold;
position: sticky; top: 0; z-index: 10;
}
tr:hover { background: #f8f9fa; }
/* Статусы */
.status { padding: 8px 16px; border-radius: 20px; font-size: 14px; font-weight: bold; }
.status.confirmed { background: #d4edda; color: #155724; }
.status.completed { background: #d1ecf1; color: #0c5460; }
.status.cancelled { background: #f8d7da; color: #721c24; }
/* Кнопки действий */
.action-buttons { display: flex; gap: 10px; align-items: center; }
.btn-small {
padding: 8px 16px; font-size: 14px; border-radius: 15px;
text-decoration: none; display: inline-block;
}
.btn-cancel { background: #dc3545; color: white; }
.btn-cancel:hover { background: #c82333; }
.btn-cancel:disabled { background: #ccc; cursor: not-allowed; }
/* Модальное окно отмены */
.modal {
display: none; position: fixed; top: 0; left: 0;
width: 100%; height: 100%; background: rgba(0,0,0,0.5);
z-index: 1000;
}
.modal-content {
background: white; margin: 10% auto; padding: 40px;
border-radius: 20px; max-width: 500px; width: 90%;
position: relative;
}
.close {
position: absolute; top: 15px; right: 20px;
font-size: 28px; cursor: pointer; color: #999;
}
.close:hover { color: #333; }
.modal textarea {
width: 100%; height: 100px; padding: 15px;
border: 2px solid #e9ecef; border-radius: 10px;
font-family: Arial, sans-serif; resize: vertical;
}
.modal-buttons { display: flex; gap: 15px; margin-top: 30px; }
.btn-modal { flex: 1; padding: 15px; border: none; border-radius: 10px;
font-size: 16px; font-weight: bold; cursor: pointer; }
.confirm-cancel { background: #dc3545; color: white; }
.confirm-cancel:hover { background: #c82333; }
.no-bookings {
text-align: center; padding: 80px 40px; color: #666;
font-size: 18px;
}
@media (max-width: 768px) {
.container, header { padding-left: 20px; padding-right: 20px; }
.action-buttons { flex-direction: column; }
th, td { padding: 15px 10px; font-size: 14px; }
}
</style>
</head>
<body>
<!-- Хедер -->
<header>
<div class="header-content">
<div class="logo">🧹 КлинСервис</div>
<div class="header-nav">
<a href="index.html" class="btn btn-secondary">Главная</a>
<a href="services.html" class="btn btn-primary">Новая бронь</a>
<button class="btn btn-secondary" onclick="logout()">Выйти</button>
</div>
</div>
</header>
<div class="container">
<h1>📋 Мои бронирования</h1>
<!-- Таблица броней -->
<div class="bookings-table">
<table>
<thead>
<tr>
<th>№ Брони</th>
<th>Дата и время</th>
<th>Услуга</th>
<th>Сотрудник</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="bookingsTable">
<tr>
<td colspan="6" class="no-bookings">Загрузка броней...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Модальное окно отмены -->
<div id="cancelModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span>
<h3>Отменить бронь?</h3>
<p>Укажите причину отмены (необязательно):</p>
<textarea id="cancelReason" placeholder="Например: 'Перенесли отпуск', 'Заболел'"></textarea>
<div class="modal-buttons">
<button class="btn-modal" onclick="closeModal()">Отмена</button>
<button class="btn-modal confirm-cancel" id="confirmCancelBtn" onclick="confirmCancel()">Отменить бронь</button>
</div>
</div>
</div>
<script>
let token = localStorage.getItem('token');
let bookings = [];
let selectedBookingId = null;
// Проверка авторизации
if (!token) {
window.location.href = 'register-login.html';
}
// Загрузка броней при загрузке страницы
window.onload = loadBookings;
// Загрузить список своих броней
async function loadBookings() {
try {
const response = await fetch('/api/bookings', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
bookings = await response.json();
renderBookings();
} else {
alert('Ошибка загрузки броней');
}
} catch (error) {
document.getElementById('bookingsTable').innerHTML =
'<tr><td colspan="6" class="no-bookings">Ошибка загрузки</td></tr>';
}
}
// Отобразить брони в таблице
function renderBookings() {
const tbody = document.getElementById('bookingsTable');
if (bookings.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="no-bookings">У вас нет броней</td></tr>';
return;
}
tbody.innerHTML = '';
// Простой цикл для каждой брони
for (let booking of bookings) {
const row = document.createElement('tr');
// Статус badge
let statusClass = '';
if (booking.status === 'confirmed') statusClass = 'confirmed';
else if (booking.status === 'completed') statusClass = 'completed';
else if (booking.status === 'cancelled') statusClass = 'cancelled';
const canCancel = booking.status === 'confirmed';
// ИСПРАВЛЕНО: используем правильные имена полей с подчёркиваниями
row.innerHTML = `
<td><strong>${booking.booking_number}</strong></td>
<td>${booking.booking_date} ${booking.start_time.slice(0,5)}${booking.end_time.slice(0,5)}</td>
<td>${booking.service ? booking.service.name : 'Услуга удалена'}</td>
<td>${booking.employee ? booking.employee.name : 'Сотрудник не назначен'}</td>
<td><span class="status ${statusClass}">${booking.status}</span></td>
<td>
${canCancel ?
`<button class="btn-small btn-cancel" onclick="showCancelModal(${booking.id})">
Отменить
</button>` :
'—'
}
</td>
`;
tbody.appendChild(row);
}
}
// Показать модальное окно отмены
function showCancelModal(bookingId) {
selectedBookingId = bookingId;
document.getElementById('cancelModal').style.display = 'block';
document.getElementById('cancelReason').value = '';
}
// Закрыть модальное окно
function closeModal() {
document.getElementById('cancelModal').style.display = 'none';
selectedBookingId = null;
}
// Подтвердить отмену
async function confirmCancel() {
const reason = document.getElementById('cancelReason').value;
try {
const response = await fetch(`/api/bookings/${selectedBookingId}/cancel`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ reason: reason || null })
});
const data = await response.json();
if (response.ok) {
alert('✅ Бронь отменена!');
closeModal();
loadBookings(); // Перезагрузить список
} else {
alert('Ошибка: ' + (data.message || data.error || 'Не удалось отменить бронь'));
}
} catch (error) {
alert('Ошибка сети');
}
}
// Выход
function logout() {
localStorage.removeItem('token');
window.location.href = 'index.html';
}
// Закрытие модального окна по клику вне его
window.onclick = function(event) {
const modal = document.getElementById('cancelModal');
if (event.target === modal) {
closeModal();
}
}
</script>
</body>
</html>