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

356 lines
15 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; }
/* Форма добавления */
.add-service-form {
background: white; padding: 40px; border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1); margin-bottom: 40px;
}
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; }
label { display: block; margin-bottom: 8px; font-weight: bold; color: #555; }
input, textarea {
width: 100%; padding: 15px; border: 2px solid #e9ecef;
border-radius: 10px; font-size: 16px; transition: border-color 0.3s;
}
input:focus, textarea:focus { outline: none; border-color: #667eea; }
textarea { resize: vertical; min-height: 100px; }
/* Таблица услуг */
.services-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; }
tr:hover { background: #f8f9fa; }
/* Статус */
.status-badge { padding: 6px 12px; border-radius: 15px; font-size: 14px; font-weight: bold; }
.status-active { background: #d4edda; color: #155724; }
.status-inactive { background: #f8d7da; color: #721c24; }
/* Кнопки действий */
.action-buttons { display: flex; gap: 10px; flex-wrap: wrap; }
.btn-small {
padding: 8px 16px; font-size: 14px; border-radius: 15px;
text-decoration: none; border: none; cursor: pointer;
}
.btn-edit { background: #ffc107; color: #212529; }
.btn-edit:hover { background: #e0a800; }
.btn-toggle { background: #17a2b8; color: white; }
.btn-toggle:hover { background: #138496; }
.btn-delete { background: #dc3545; color: white; }
.btn-delete:hover { background: #c82333; }
/* Модальное окно */
.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: 5% auto; padding: 40px;
border-radius: 20px; max-width: 600px; width: 90%; position: relative;
}
.close {
position: absolute; top: 15px; right: 20px; font-size: 28px;
cursor: pointer; color: #999;
}
.close:hover { color: #333; }
@media (max-width: 768px) {
.form-grid { grid-template-columns: 1fr; }
.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="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="add-service-form">
<h3 style="margin-bottom: 25px; color: #333;"> Добавить новую услугу</h3>
<form id="addServiceForm" onsubmit="addService(event)" class="form-grid">
<div>
<label>Название *</label>
<input type="text" id="serviceName" required placeholder="Генеральная уборка">
</div>
<div>
<label>Цена (₽) *</label>
<input type="number" id="servicePrice" required min="0" placeholder="5000">
</div>
<div>
<label>Длительность (минуты) *</label>
<input type="number" id="serviceDuration" required min="30" placeholder="120">
</div>
<div style="grid-column: span 2;">
<label>Описание</label>
<textarea id="serviceDescription" placeholder="Полная уборка с мойкой окон..."></textarea>
</div>
<div style="grid-column: span 2;">
<button type="submit" class="btn btn-primary" style="width: 200px; padding: 15px 30px;">
Создать услугу
</button>
</div>
</form>
</div>
<!-- Таблица услуг -->
<div class="services-table">
<table>
<thead>
<tr>
<th>ID</th>
<th>Название</th>
<th>Описание</th>
<th>Длительность</th>
<th>Цена</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="servicesTable">
<tr>
<td colspan="7" style="text-align: center; padding: 40px; color: #666;">
Загрузка услуг...
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Модальное окно удаления -->
<div id="deleteModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span>
<h3>Удалить услугу?</h3>
<p>Услуга <strong id="deleteServiceName"></strong> будет удалена навсегда.</p>
<div style="display: flex; gap: 15px; margin-top: 30px;">
<button class="btn btn-secondary" onclick="closeModal()" style="flex: 1;">Отмена</button>
<button class="btn btn-delete" onclick="confirmDelete()" style="flex: 1;">Удалить</button>
</div>
</div>
</div>
<script>
let token = localStorage.getItem('token');
let services = [];
let selectedServiceId = null;
// Проверка авторизации и роли
if (!token) {
window.location.href = 'register-login.html';
}
// Загрузка услуг при загрузке страницы
window.onload = loadServices;
// Загрузить услуги из API
async function loadServices() {
try {
const response = await fetch('/api/admin/services', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
services = await response.json();
renderServices();
} else {
alert('Ошибка доступа или загрузки услуг');
window.location.href = 'register-login.html';
}
} catch (error) {
alert('Ошибка сети');
}
}
// Отобразить услуги в таблице
function renderServices() {
const tbody = document.getElementById('servicesTable');
if (services.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" style="text-align: center; padding: 40px; color: #666;">Нет услуг</td></tr>';
return;
}
tbody.innerHTML = '';
// Простой цикл для каждой услуги
for (let service of services) {
const row = document.createElement('tr');
const statusClass = service.isactive ? 'active' : 'inactive';
row.innerHTML = `
<td>${service.id}</td>
<td><strong>${service.name}</strong></td>
<td>${service.description || '—'}</td>
<td>${service.durationminutes} мин</td>
<td>${service.price}₽</td>
<td><span class="status-badge status-${statusClass}">${service.isactive ? 'Активна' : 'Неактивна'}</span></td>
<td>
<div class="action-buttons">
<button class="btn-small btn-edit" onclick="editService(${service.id})">
✏️ Редактировать
</button>
<button class="btn-small btn-toggle" onclick="toggleService(${service.id}, ${service.isactive})">
${service.isactive ? '❌ Деактивировать' : '✅ Активировать'}
</button>
<button class="btn-small btn-delete" onclick="showDeleteModal(${service.id}, '${service.name}')">
🗑️ Удалить
</button>
</div>
</td>
`;
tbody.appendChild(row);
}
}
// Добавить новую услугу
async function addService(event) {
event.preventDefault();
const formData = {
name: document.getElementById('serviceName').value,
description: document.getElementById('serviceDescription').value,
durationminutes: parseInt(document.getElementById('serviceDuration').value),
price: parseInt(document.getElementById('servicePrice').value),
isactive: true
};
try {
const response = await fetch('/api/admin/services', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(formData)
});
if (response.ok) {
alert('✅ Услуга создана!');
document.getElementById('addServiceForm').reset();
loadServices(); // Перезагрузить таблицу
} else {
const error = await response.json();
alert('Ошибка: ' + error.error || error.message);
}
} catch (error) {
alert('Ошибка сети');
}
}
// Переключить статус (активна/неактивна)
async function toggleService(id, isActive) {
try {
const response = await fetch(`/api/admin/services/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ isactive: !isActive })
});
if (response.ok) {
alert(`✅ Услуга ${!isActive ? 'активирована' : 'деактивирована'}`);
loadServices();
}
} catch (error) {
alert('Ошибка сети');
}
}
// Показать модальное окно удаления
function showDeleteModal(id, name) {
selectedServiceId = id;
document.getElementById('deleteServiceName').textContent = name;
document.getElementById('deleteModal').style.display = 'block';
}
// Удалить услугу
async function confirmDelete() {
try {
const response = await fetch(`/api/admin/services/${selectedServiceId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
alert('✅ Услуга удалена');
closeModal();
loadServices();
}
} catch (error) {
alert('Ошибка сети');
}
}
// Закрыть модальное окно
function closeModal() {
document.getElementById('deleteModal').style.display = 'none';
}
// Выход
function logout() {
localStorage.removeItem('token');
window.location.href = 'index.html';
}
// Закрытие модального окна по клику вне области
window.onclick = function(event) {
const modal = document.getElementById('deleteModal');
if (event.target === modal) closeModal();
}
</script>
</body>
</html>