Files
hotel777-manager/public/clients.html
2026-05-04 21:09:42 +05:00

197 lines
6.8 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>
</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><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>