Files
cleaning-company/public/admin-schedule.html
Владимир f5c68bf0c7 commit 08.01
2026-01-08 12:38:09 +00:00

431 lines
18 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: #2c3e50; color: white; padding: 20px 50px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); position: sticky; top: 0;
}
.header-content {
max-width: 1200px; margin: 0 auto;
display: flex; justify-content: space-between; align-items: center;
}
.logo { font-size: 24px; font-weight: bold; }
.header-nav { display: flex; gap: 20px; }
.btn {
padding: 12px 25px; border: none; border-radius: 25px;
cursor: pointer; font-size: 16px; text-decoration: none;
display: inline-block; transition: all 0.3s; color: white;
}
.btn-primary { background: #667eea; }
.btn-primary:hover { background: #5a67d8; }
.btn-secondary { background: #6c757d; }
.btn-secondary:hover { background: #5a6268; }
/* Контейнер */
.container { max-width: 1200px; margin: 40px auto; padding: 0 20px; }
h1 { text-align: center; color: #333; margin-bottom: 40px; font-size: 32px; }
/* Выбор сотрудника */
.employee-select {
background: white; padding: 30px; border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1); margin-bottom: 30px;
display: flex; gap: 20px; align-items: center; flex-wrap: wrap;
}
@media (max-width: 768px) { .employee-select { flex-direction: column; text-align: center; } }
label { font-weight: bold; color: #555; font-size: 18px; }
select {
padding: 15px 20px; border: 2px solid #e9ecef;
border-radius: 10px; font-size: 16px; min-width: 300px;
}
/* Календарь */
.calendar-section {
background: white; padding: 40px; border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1); margin-bottom: 30px;
}
.calendar-header {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 20px; flex-wrap: wrap; gap: 10px;
}
.calendar-nav-btn {
padding: 12px 20px; background: #667eea; color: white;
border: none; border-radius: 10px; cursor: pointer; font-size: 16px;
}
.calendar-nav-btn:hover { background: #5a67d8; }
.month-year { font-size: 24px; font-weight: bold; color: #333; }
.calendar-grid {
display: grid; grid-template-columns: repeat(7, 1fr);
gap: 8px; text-align: center;
}
.calendar-day {
padding: 20px 10px; border: 2px solid #e9ecef;
border-radius: 12px; cursor: pointer; transition: all 0.3s;
font-weight: 500; min-height: 80px; display: flex; flex-direction: column;
}
.calendar-day:hover { border-color: #667eea; background: #f8f9ff; }
.calendar-day.selected { border-color: #667eea; background: #e3f2fd; }
.calendar-day.other-month { color: #ccc; }
.weekdays { font-weight: bold; color: #555; padding: 15px 0; font-size: 16px; }
/* Интервалы времени */
.time-intervals {
background: white; padding: 40px; border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}
.interval-form {
display: grid; grid-template-columns: 1fr 1fr auto;
gap: 20px; align-items: end; margin-bottom: 20px;
}
@media (max-width: 768px) { .interval-form { grid-template-columns: 1fr; } }
input[type="time"] { padding: 15px; border: 2px solid #e9ecef; border-radius: 10px; }
input[type="time"]:focus { border-color: #667eea; outline: none; }
.interval-item {
background: #f8f9fa; padding: 20px; border-radius: 15px;
border-left: 4px solid #667eea; margin-bottom: 15px;
}
.delete-interval {
background: #dc3545; color: white; border: none;
border-radius: 50%; width: 35px; height: 35px;
cursor: pointer; font-size: 18px;
}
.delete-interval:hover { background: #c82333; }
.no-intervals { text-align: center; color: #666; padding: 40px; font-style: italic; }
</style>
</head>
<body>
<!-- Хедер -->
<header>
<div class="header-content">
<div class="logo">🧹 КлинСервис - Админка</div>
<div class="header-nav">
<a href="admin-services.html" class="btn btn-secondary">Услуги</a>
<a href="admin-bookings.html" class="btn btn-secondary">Брони</a>
<a href="index.html" class="btn btn-primary">На главную</a>
<button class="btn btn-secondary" onclick="logout()">Выйти</button>
</div>
</div>
</header>
<div class="container">
<h1>📅 Расписание сотрудников</h1>
<!-- Выбор сотрудника -->
<div class="employee-select">
<div>
<label>Выберите сотрудника:</label>
<select id="employeeSelect" onchange="loadEmployeeSchedule()">
<option value="">— Выберите сотрудника —</option>
</select>
</div>
<div id="employeeInfo" style="display:none;">
<strong id="selectedEmployeeName"></strong>
<span style="color: #666; margin-left: 10px;">(ID: <span id="selectedEmployeeId"></span>)</span>
</div>
</div>
<!-- Календарь -->
<div class="calendar-section" id="calendarSection" style="display:none;">
<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="time-intervals" id="timeIntervalsSection" style="display:none;">
<h3 id="selectedDateTitle">Интервалы времени</h3>
<!-- Форма добавления интервала -->
<form onsubmit="addInterval(event)" class="interval-form">
<input type="time" id="startTime" required>
<input type="time" id="endTime" required>
<label>
<input type="checkbox" id="isUnavailable"> Недоступен
</label>
<button type="submit" class="btn btn-primary"> Добавить интервал</button>
</form>
<!-- Список интервалов -->
<div id="intervalsList">
<div class="no-intervals">Выберите дату в календаре</div>
</div>
</div>
</div>
<script>
let token = localStorage.getItem('token');
let selectedEmployeeId = null;
let selectedDate = null;
let currentMonth = new Date().getMonth();
let currentYear = new Date().getFullYear();
let employees = [];
let intervals = [];
// Проверка авторизации
if (!token) {
window.location.href = 'register-login.html';
}
// Загрузка сотрудников при старте
window.onload = async function() {
await loadEmployees();
};
// Загрузить список сотрудников
async function loadEmployees() {
try {
const response = await fetch('/api/admin/users?role=employee', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
const allUsers = await response.json();
employees = allUsers.filter(user => user.role === 'employee' || user.role === 'admin');
const select = document.getElementById('employeeSelect');
select.innerHTML = '<option value="">— Выберите сотрудника —</option>';
for (let employee of employees) {
select.innerHTML += `<option value="${employee.id}">${employee.name}</option>`;
}
}
} catch (error) {
alert('Ошибка загрузки сотрудников');
}
}
// При смене сотрудника
function loadEmployeeSchedule() {
const select = document.getElementById('employeeSelect');
selectedEmployeeId = select.value;
if (selectedEmployeeId) {
document.getElementById('employeeInfo').style.display = 'block';
document.getElementById('selectedEmployeeId').textContent = selectedEmployeeId;
document.getElementById('selectedEmployeeName').textContent = select.options[select.selectedIndex].text;
document.getElementById('calendarSection').style.display = 'block';
renderCalendar();
} else {
document.getElementById('calendarSection').style.display = 'none';
document.getElementById('timeIntervalsSection').style.display = 'none';
document.getElementById('employeeInfo').style.display = 'none';
}
}
// Простой календарь
function renderCalendar() {
const calendarDays = document.getElementById('calendarDays');
const monthYear = document.getElementById('monthYear');
monthYear.textContent = new Date(currentYear, currentMonth).toLocaleDateString('ru', {
year: 'numeric', month: 'long'
});
calendarDays.innerHTML = '';
const firstDay = new Date(currentYear, currentMonth, 1).getDay();
const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
let dayCounter = 1 - firstDay + 1;
for (let i = 0; i < 42; i++) {
const day = document.createElement('div');
day.className = 'calendar-day';
if (dayCounter < 1) {
day.textContent = new Date(currentYear, currentMonth, 0).getDate() + dayCounter;
day.classList.add('other-month');
dayCounter++;
} else if (dayCounter <= daysInMonth) {
const dateStr = `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(dayCounter).padStart(2, '0')}`;
day.textContent = dayCounter;
day.onclick = () => selectDate(dateStr);
dayCounter++;
} else {
day.textContent = dayCounter - daysInMonth;
day.classList.add('other-month');
dayCounter++;
}
calendarDays.appendChild(day);
}
}
function selectDate(date) {
selectedDate = date;
document.querySelectorAll('.calendar-day').forEach(day => day.classList.remove('selected'));
event.target.classList.add('selected');
document.getElementById('selectedDateTitle').textContent = `Интервалы на ${new Date(date).toLocaleDateString('ru')}`;
loadIntervals();
}
function prevMonth() {
currentMonth--;
if (currentMonth < 0) {
currentMonth = 11;
currentYear--;
}
renderCalendar();
}
function nextMonth() {
currentMonth++;
if (currentMonth > 11) {
currentMonth = 0;
currentYear++;
}
renderCalendar();
}
// Загрузить интервалы для даты
async function loadIntervals() {
try {
document.getElementById('timeIntervalsSection').style.display = 'block';
const response = await fetch(`/api/admin/availabilities?employee_id=${selectedEmployeeId}&date=${selectedDate}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
intervals = await response.json();
renderIntervals();
} catch (error) {
intervals = [];
renderIntervals();
}
}
// Отобразить интервалы
function renderIntervals() {
const container = document.getElementById('intervalsList');
if (intervals.length === 0) {
container.innerHTML = '<div class="no-intervals">Нет интервалов на эту дату</div>';
return;
}
container.innerHTML = '';
for (let interval of intervals) {
const item = document.createElement('div');
item.className = 'interval-item';
item.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center;">
<strong>${interval.starttime.slice(0,5)} - ${interval.endtime.slice(0,5)}</strong>
<div>
<label style="margin-right: 15px;">
<input type="checkbox" ${interval.isavailable ? '' : 'checked'} onchange="updateInterval(${interval.id}, this.checked)">
Недоступен
</label>
<button class="delete-interval" onclick="deleteInterval(${interval.id})" title="Удалить">×</button>
</div>
</div>
`;
container.appendChild(item);
}
}
// Добавить интервал
async function addInterval(event) {
event.preventDefault();
const startTime = document.getElementById('startTime').value;
const endTime = document.getElementById('endTime').value;
const isUnavailable = document.getElementById('isUnavailable').checked;
if (!startTime || !endTime) {
alert('Заполните время начала и окончания');
return;
}
try {
const response = await fetch('/api/admin/availabilities', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
employee_id: selectedEmployeeId,
date: selectedDate,
starttime: startTime + ':00',
endtime: endTime + ':00',
isavailable: !isUnavailable
})
});
if (response.ok) {
document.getElementById('startTime').value = '';
document.getElementById('endTime').value = '';
document.getElementById('isUnavailable').checked = false;
loadIntervals();
}
} catch (error) {
alert('Ошибка добавления интервала');
}
}
// Обновить доступность
async function updateInterval(id, isAvailable) {
try {
const response = await fetch(`/api/admin/availabilities/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ isavailable: isAvailable })
});
if (!response.ok) {
alert('Ошибка обновления');
loadIntervals(); // Вернуть как было
}
} catch (error) {
alert('Ошибка сети');
loadIntervals();
}
}
// Удалить интервал
async function deleteInterval(id) {
if (confirm('Удалить интервал?')) {
try {
const response = await fetch(`/api/admin/availabilities/${id}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
loadIntervals();
}
} catch (error) {
alert('Ошибка удаления');
}
}
}
function logout() {
localStorage.removeItem('token');
window.location.href = 'index.html';
}
</script>
</body>
</html>