199 lines
6.9 KiB
HTML
199 lines
6.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru" data-title="Клиенты" data-description="Список клиентов гостиницы">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<link rel="stylesheet" href="style.css">
|
|
<title>Клиенты</title>
|
|
<style>
|
|
.client-cards { display: none; }
|
|
|
|
.client-card {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
padding: 1.25rem;
|
|
margin-bottom: 0.75rem;
|
|
box-shadow: var(--shadow-sm);
|
|
transition: all var(--transition-base);
|
|
animation: cardSlideIn 0.3s ease-out backwards;
|
|
}
|
|
|
|
.client-card:hover { box-shadow: var(--shadow-md); }
|
|
|
|
.client-card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.client-card-name {
|
|
font-weight: 600;
|
|
font-size: 1rem;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.client-card-phone {
|
|
font-size: 0.875rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.client-card-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
padding-top: 0.75rem;
|
|
border-top: 1px solid var(--border-light);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.table-container { display: none !important; }
|
|
.client-cards { display: block; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header></header>
|
|
|
|
<div class="toast-container" id="toastContainer"></div>
|
|
|
|
<main>
|
|
<h1>Клиенты</h1>
|
|
|
|
<div class="filter-bar">
|
|
<div class="search-input-wrapper">
|
|
<input type="text" id="searchClient" placeholder="Поиск по имени или телефону">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-container">
|
|
<table id="clientsTable">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Телефон</th>
|
|
<th>Имя</th>
|
|
<th>Статус</th>
|
|
<th>Действия</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="clientsBody"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div id="clientCards" class="client-cards"></div>
|
|
|
|
<div id="emptyState" class="empty-state" style="display:none;">
|
|
<div class="empty-state-icon">
|
|
<svg width="64" height="64" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"/></svg>
|
|
</div>
|
|
<h3>Клиенты не найдены</h3>
|
|
<p>Попробуйте изменить параметры поиска</p>
|
|
</div>
|
|
</main>
|
|
|
|
<script src="nav.js"></script>
|
|
<script src="seo.js"></script>
|
|
<script>
|
|
function escapeHtml(str) {
|
|
if (!str) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = str;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function showToast(message, type = 'info') {
|
|
const container = document.getElementById('toastContainer');
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${type}`;
|
|
toast.innerHTML = `<span>${escapeHtml(message)}</span>`;
|
|
container.appendChild(toast);
|
|
setTimeout(() => {
|
|
toast.classList.add('toast-exit');
|
|
setTimeout(() => toast.remove(), 300);
|
|
}, 3000);
|
|
}
|
|
|
|
fetch('/api/me').then(r => r.json()).then(data => {
|
|
if (!data.isAdmin) window.location.href = '/login.html';
|
|
});
|
|
|
|
const tbody = document.getElementById('clientsBody');
|
|
const clientCards = document.getElementById('clientCards');
|
|
const emptyState = document.getElementById('emptyState');
|
|
const tableContainer = document.querySelector('.table-container');
|
|
|
|
async function loadClients() {
|
|
const search = document.getElementById('searchClient').value;
|
|
let url = '/api/clients?';
|
|
if (search) url += `search=${encodeURIComponent(search)}`;
|
|
|
|
tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;padding:2rem;"><div class="loading-spinner"></div></td></tr>';
|
|
clientCards.innerHTML = '<div style="text-align:center;padding:2rem;"><div class="loading-spinner"></div></div>';
|
|
|
|
try {
|
|
const res = await fetch(url);
|
|
if (!res.ok) throw new Error('Ошибка загрузки');
|
|
const clients = await res.json();
|
|
|
|
tbody.innerHTML = '';
|
|
clientCards.innerHTML = '';
|
|
|
|
if (clients.length === 0) {
|
|
tableContainer.style.display = 'none';
|
|
emptyState.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
tableContainer.style.display = 'block';
|
|
emptyState.style.display = 'none';
|
|
|
|
clients.forEach((c, i) => {
|
|
const tr = document.createElement('tr');
|
|
tr.style.animationDelay = `${i * 0.03}s`;
|
|
tr.innerHTML = `
|
|
<td>${c.id}</td>
|
|
<td>${escapeHtml(c.phone)}</td>
|
|
<td>${escapeHtml(c.name) || '<span style="color:var(--text-muted);">—</span>'}</td>
|
|
<td>${escapeHtml(c.status)}</td>
|
|
<td><a href="client.html?id=${c.id}" class="btn btn-secondary btn-sm" style="text-decoration:none;display:inline-flex;align-items:center;">Профиль</a></td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
|
|
const card = document.createElement('div');
|
|
card.className = 'client-card';
|
|
card.style.animationDelay = `${i * 0.03}s`;
|
|
card.innerHTML = `
|
|
<div class="client-card-header">
|
|
<div>
|
|
<div class="client-card-name">${escapeHtml(c.name) || 'Без имени'}</div>
|
|
<div class="client-card-phone">${escapeHtml(c.phone)}</div>
|
|
</div>
|
|
</div>
|
|
<div class="client-card-footer">
|
|
<a href="client.html?id=${c.id}" class="btn btn-secondary btn-sm" style="text-decoration:none;display:inline-flex;align-items:center;">Профиль</a>
|
|
</div>
|
|
`;
|
|
clientCards.appendChild(card);
|
|
});
|
|
} catch (err) {
|
|
showToast('Ошибка загрузки', 'error');
|
|
tbody.innerHTML = '';
|
|
clientCards.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
let searchTimeout;
|
|
document.getElementById('searchClient').addEventListener('input', (e) => {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(loadClients, 400);
|
|
});
|
|
document.getElementById('searchClient').addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') { clearTimeout(searchTimeout); loadClients(); }
|
|
});
|
|
|
|
loadClients();
|
|
</script>
|
|
</body>
|
|
</html>
|