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

411 lines
16 KiB
HTML
Raw 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: 10px 20px; border: none; border-radius: 25px;
cursor: pointer; font-size: 14px; 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: 1000px; margin: 40px auto; padding: 0 20px; }
h1 { text-align: center; color: #333; margin-bottom: 40px; font-size: 32px; }
/* Форма выбора */
.selection-form {
background: white; padding: 40px; border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1); margin-bottom: 40px;
}
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 30px; }
@media (max-width: 768px) { .form-row { grid-template-columns: 1fr; } }
label { display: block; margin-bottom: 10px; font-weight: bold; color: #555; }
select, input {
width: 100%; padding: 15px; border: 2px solid #e9ecef;
border-radius: 10px; font-size: 16px; transition: border-color 0.3s;
}
select:focus, input:focus { outline: none; border-color: #667eea; }
/* Простой календарь */
.calendar-section {
background: white; padding: 40px; border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}
.calendar-header {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 20px; flex-wrap: wrap; gap: 10px;
}
.calendar-nav-btn {
padding: 10px 20px; background: #667eea; color: white;
border: none; border-radius: 10px; cursor: pointer;
}
.calendar-nav-btn:hover { background: #5a67d8; }
.calendar-nav-btn:disabled { background: #ccc; cursor: not-allowed; }
.month-year { font-size: 24px; font-weight: bold; color: #333; }
.calendar-grid {
display: grid; grid-template-columns: repeat(7, 1fr);
gap: 5px; text-align: center;
}
.calendar-day {
padding: 15px; border: 1px solid #e9ecef;
border-radius: 10px; cursor: pointer; transition: all 0.3s;
font-weight: 500;
}
.calendar-day:hover { background: #e3f2fd; border-color: #667eea; }
.calendar-day.other-month { color: #ccc; }
.calendar-day.selected {
background: #667eea; color: white; box-shadow: 0 5px 15px rgba(102,126,234,0.4);
}
.calendar-day.disabled {
background: #f8f9fa; color: #ccc; cursor: not-allowed;
}
.weekdays { font-weight: bold; color: #555; padding: 10px 0; }
/* Слоты */
.slots-section {
background: white; padding: 40px; border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1); margin-top: 30px;
}
.slots-grid { display: flex; flex-wrap: wrap; gap: 15px; }
.slot {
padding: 15px 25px; background: #f8f9fa;
border: 2px solid transparent; border-radius: 25px;
cursor: pointer; transition: all 0.3s; font-weight: 500;
}
.slot:hover { background: #e3f2fd; border-color: #667eea; }
.slot.selected { background: #667eea; color: white; border-color: #5a67d8; }
.slot.booked { background: #f8d7da; color: #721c24; cursor: not-allowed; }
.no-slots { text-align: center; color: #666; padding: 40px; font-style: italic; }
/* Кнопка забронировать */
.book-btn {
width: 100%; padding: 20px; background: #28a745;
color: white; border: none; border-radius: 15px;
font-size: 20px; font-weight: bold; cursor: pointer;
margin-top: 30px; transition: all 0.3s;
}
.book-btn:hover:not(:disabled) { background: #218838; transform: translateY(-2px); }
.book-btn:disabled { background: #ccc; cursor: not-allowed; }
</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="my-bookings.html" class="btn btn-secondary" id="profileBtn" style="display:none;">Мои брони</a>
<button class="btn btn-primary" id="logoutBtn" style="display:none;" onclick="logout()">Выйти</button>
</div>
</div>
</header>
<div class="container">
<h1>📅 Выберите дату и время</h1>
<!-- Выбор услуги -->
<div class="selection-form">
<div class="form-row">
<div>
<label>Услуга *</label>
<select id="serviceSelect">
<option value="">Загрузка...</option>
</select>
</div>
<div>
<label>Длительность</label>
<input type="text" id="duration" readonly placeholder="Выберите услугу">
</div>
</div>
<div>
<!-- Календарь -->
<div class="calendar-section">
<div class="calendar-header">
<button class="calendar-nav-btn" onclick="prevMonth()">← Месяц</button>
<div class="month-year" id="monthYear"></div>
<button class="calendar-nav-btn" onclick="nextMonth()">Месяц →</button>
</div>
<div class="calendar-grid">
<div class="weekdays">Пн</div><div class="weekdays">Вт</div><div class="weekdays">Ср</div>
<div class="weekdays">Чт</div><div class="weekdays">Пт</div><div class="weekdays">Сб</div><div class="weekdays">Вс</div>
<div id="calendarDays"></div>
</div>
</div>
<!-- Слоты времени -->
<div class="slots-section" id="slotsSection" style="display:none;">
<h3>Доступные слоты</h3>
<div class="slots-grid" id="slotsGrid"></div>
<button class="book-btn" id="confirmBookingBtn" onclick="confirmBooking()" style="display:none;">
✅ Забронировать
</button>
</div>
</div>
<script>
let services = [];
let selectedServiceId = null;
let selectedDate = null;
let currentMonth = new Date().getMonth();
let currentYear = new Date().getFullYear();
let availableSlots = [];
let token = localStorage.getItem('token');
let selectedSlot = null;
// Проверка авторизации
if (!token) {
window.location.href = 'register-login.html';
}
// Загрузка страницы
window.onload = async function() {
// Убедимся, что элементы существуют
if (!document.getElementById('serviceSelect')) {
console.error('Элемент #serviceSelect не найден');
return;
}
try {
await loadServices();
initCalendar();
} catch (error) {
console.error('Ошибка загрузки:', error);
alert('Не удалось загрузить данные');
}
};
// ✅ 1. ЗАГРУЗКА УСЛУГ (публичный API)
async function loadServices() {
try {
const response = await fetch('/api/services');
const data = await response.json();
services = data.filter(service => service.is_active); // ← is_active, не isactive
const select = document.getElementById('serviceSelect');
select.innerHTML = '<option value="">Выберите услугу</option>';
for (let service of services) {
const option = document.createElement('option');
option.value = service.id;
option.textContent = `${service.name} (${service.duration_minutes} мин) - ${service.price}`;
select.appendChild(option);
}
} catch (error) {
throw new Error('Ошибка загрузки услуг: ' + error.message);
}
}
// 2. При выборе услуги — показываем длительность
document.getElementById('serviceSelect').onchange = function() {
const serviceId = this.value;
if (!serviceId) {
document.getElementById('duration').value = '';
return;
}
selectedServiceId = parseInt(serviceId);
const service = services.find(s => s.id == serviceId);
document.getElementById('duration').value = service ? service.duration_minutes + ' минут' : '';
};
// 3. Календарь
function initCalendar() {
renderCalendar();
}
function renderCalendar() {
const monthYear = document.getElementById('monthYear');
const calendarDays = document.getElementById('calendarDays');
if (!monthYear || !calendarDays) {
console.error('Элементы календаря не найдены');
return;
}
const months = ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'];
monthYear.textContent = `${months[currentMonth]} ${currentYear}`;
calendarDays.innerHTML = '';
const firstDay = new Date(currentYear, currentMonth, 1).getDay();
const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
// Добавляем "серые" дни предыдущего месяца
for (let i = 0; i < (firstDay === 0 ? 6 : firstDay - 1); i++) {
const dayEl = document.createElement('div');
dayEl.className = 'calendar-day other-month';
calendarDays.appendChild(dayEl);
}
// Добавляем дни текущего месяца
const today = new Date();
for (let day = 1; day <= daysInMonth; day++) {
const dayEl = document.createElement('div');
dayEl.className = 'calendar-day';
dayEl.textContent = day;
const dateStr = `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const dateObj = new Date(dateStr);
if (dateObj < today.setHours(0,0,0,0)) {
dayEl.classList.add('disabled');
} else {
dayEl.onclick = () => selectDate(dateStr);
}
calendarDays.appendChild(dayEl);
}
}
function prevMonth() {
if (currentMonth === 0) {
currentMonth = 11;
currentYear--;
} else {
currentMonth--;
}
renderCalendar();
}
function nextMonth() {
if (currentMonth === 11) {
currentMonth = 0;
currentYear++;
} else {
currentMonth++;
}
renderCalendar();
}
// 4. Выбор даты → загрузка слотов
async function selectDate(dateStr) {
if (!selectedServiceId) {
alert('Сначала выберите услугу');
return;
}
selectedDate = dateStr;
document.querySelectorAll('.calendar-day').forEach(el => el.classList.remove('selected'));
event.target.classList.add('selected');
try {
const url = `/api/availability?service_id=${selectedServiceId}&date=${dateStr}`;
const response = await fetch(url);
availableSlots = await response.json();
showSlots(availableSlots);
} catch (error) {
console.error('Ошибка загрузки слотов:', error);
alert('Не удалось загрузить доступное время');
}
}
function showSlots(slots) {
const slotsSection = document.getElementById('slotsSection');
const slotsGrid = document.getElementById('slotsGrid');
const confirmBtn = document.getElementById('confirmBookingBtn');
slotsSection.style.display = 'block';
slotsGrid.innerHTML = '';
if (slots.length === 0) {
slotsGrid.innerHTML = '<div class="no-slots">В этот день нет доступных слотов</div>';
confirmBtn.style.display = 'none';
return;
}
selectedSlot = null; // ← Сбросьте перед новым выбором
slots.forEach(slot => {
const slotEl = document.createElement('div');
slotEl.className = 'slot';
slotEl.textContent = `${slot.start} - ${slot.end}`;
slotEl.onclick = () => {
document.querySelectorAll('.slot').forEach(s => s.classList.remove('selected'));
slotEl.classList.add('selected');
selectedSlot = slot; // ← Присвойте значение
confirmBtn.style.display = 'block';
};
slotsGrid.appendChild(slotEl);
});
}
// 5. Подтверждение бронирования
async function confirmBooking() {
if (!selectedServiceId || !selectedDate || !selectedSlot) {
alert('Выберите всё: услугу, дату и время');
return;
}
try {
const response = await fetch('/api/bookings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
service_id: selectedServiceId,
employee_id: selectedSlot.employee_id,
date: selectedDate,
start_time: selectedSlot.start
})
});
if (response.ok) {
alert('✅ Бронирование успешно создано!');
window.location.href = 'my-bookings.html';
} else {
const err = await response.json();
alert('Ошибка: ' + (err.message || 'Не удалось создать бронь'));
}
} catch (error) {
console.error('Ошибка бронирования:', error);
alert('Ошибка сети');
}
}
function updateHeader() {
if (token) {
document.getElementById('profileBtn').style.display = 'inline-block';
document.getElementById('logoutBtn').style.display = 'inline-block';
}
}
function logout() {
localStorage.removeItem('token');
window.location.href = 'index.html';
}
</script>