feat(admin): pagination для users-таблицы (50/стр)
- adminGetUsers возвращает { users, total, page, limit }
- pagination-controls (← 1 … N →) с ellipsis для длинных списков
- shop/gam search callers адаптированы под новый формат ответа
- helper _renderPgnControls переиспользуем для sessions/shop
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1117,6 +1117,7 @@
|
||||
<tbody id="users-body"><tr><td colspan="7"><div class="spinner"></div></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="users-pagination" class="pgn-bar" style="display:none"></div>
|
||||
<div class="user-panel" id="user-panel">
|
||||
<div class="user-panel-header">
|
||||
<div><div class="user-panel-name" id="up-name"></div><div class="user-panel-email" id="up-email"></div></div>
|
||||
|
||||
@@ -653,11 +653,63 @@
|
||||
/* ════════════════════════════════════════════════
|
||||
ПОЛЬЗОВАТЕЛИ
|
||||
════════════════════════════════════════════════ */
|
||||
async function loadUsers() {
|
||||
let _usersPage = 1;
|
||||
const _USERS_PER_PAGE = 50;
|
||||
|
||||
function _ensurePgnStyles() {
|
||||
if (document.getElementById('pgn-bar-style')) return;
|
||||
const s = document.createElement('style');
|
||||
s.id = 'pgn-bar-style';
|
||||
s.textContent = `
|
||||
.pgn-bar { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:14px 4px 4px; font-size:0.85rem; color:var(--text-3); }
|
||||
.pgn-info { font-weight:600; }
|
||||
.pgn-ctrls { display:flex; align-items:center; gap:4px; }
|
||||
.pgn-btn { min-width:32px; height:32px; padding:0 10px; border:1px solid var(--border); background:var(--surface); border-radius:8px; cursor:pointer; font-weight:600; font-family:inherit; font-size:0.85rem; color:var(--text-2); transition:background .12s, color .12s, border-color .12s; }
|
||||
.pgn-btn:hover:not(:disabled) { background:rgba(155,93,229,.08); color:var(--violet); border-color:rgba(155,93,229,.3); }
|
||||
.pgn-btn.active { background:var(--violet); color:#fff; border-color:var(--violet); }
|
||||
.pgn-btn:disabled { opacity:.4; cursor:not-allowed; }
|
||||
.pgn-ellip { padding:0 6px; color:var(--text-3); }
|
||||
`;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
function _renderPgnControls(elId, page, total, perPage, gotoFn) {
|
||||
const bar = document.getElementById(elId);
|
||||
if (!bar) return;
|
||||
const pages = Math.max(1, Math.ceil(total / perPage));
|
||||
if (pages <= 1) { bar.style.display = 'none'; return; }
|
||||
_ensurePgnStyles();
|
||||
const from = (page - 1) * perPage + 1;
|
||||
const to = Math.min(page * perPage, total);
|
||||
// page buttons with ellipsis: first, current±2, last
|
||||
const nums = new Set([1, pages, page, page - 1, page + 1, page - 2, page + 2]);
|
||||
const sorted = [...nums].filter(n => n >= 1 && n <= pages).sort((a, b) => a - b);
|
||||
const numHtml = sorted.map((n, i) => {
|
||||
const prev = sorted[i - 1];
|
||||
const gap = prev && n - prev > 1 ? '<span class="pgn-ellip">…</span>' : '';
|
||||
return `${gap}<button class="pgn-btn${n === page ? ' active' : ''}" onclick="${gotoFn}(${n})">${n}</button>`;
|
||||
}).join('');
|
||||
bar.innerHTML = `
|
||||
<div class="pgn-info">${from}–${to} из ${total}</div>
|
||||
<div class="pgn-ctrls">
|
||||
<button class="pgn-btn" onclick="${gotoFn}(${page - 1})" ${page <= 1 ? 'disabled' : ''}>←</button>
|
||||
${numHtml}
|
||||
<button class="pgn-btn" onclick="${gotoFn}(${page + 1})" ${page >= pages ? 'disabled' : ''}>→</button>
|
||||
</div>`;
|
||||
bar.style.display = '';
|
||||
}
|
||||
|
||||
async function loadUsers(page) {
|
||||
if (page) _usersPage = page;
|
||||
try {
|
||||
const users = await LS.adminGetUsers();
|
||||
const r = await LS.adminGetUsers({ page: _usersPage, limit: _USERS_PER_PAGE });
|
||||
const users = r.users || [];
|
||||
const tbody = document.getElementById('users-body');
|
||||
if (!users.length) { tbody.innerHTML = '<tr><td colspan="7"><div class="empty">Пользователей нет</div></td></tr>'; return; }
|
||||
if (!users.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="7"><div class="empty">Пользователей нет</div></td></tr>';
|
||||
document.getElementById('users-pagination').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = users.map(u => {
|
||||
const pc = pctClass(u.avg_pct);
|
||||
const initials = (u.name||'?').split(' ').slice(0,2).map(w=>w[0]?.toUpperCase()||'').join('')||'?';
|
||||
@@ -691,12 +743,20 @@
|
||||
<td style="text-align:right;color:var(--text-3);font-size:0.85rem;opacity:0.4">›</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
_renderPgnControls('users-pagination', _usersPage, r.total || users.length, _USERS_PER_PAGE, 'gotoUsersPage');
|
||||
} catch (e) {
|
||||
document.getElementById('users-body').innerHTML = `<tr><td colspan="7"></td></tr>`;
|
||||
LS.state.error(document.getElementById('users-body').querySelector('td'), e, loadUsers);
|
||||
}
|
||||
}
|
||||
|
||||
function gotoUsersPage(n) {
|
||||
_usersPage = n;
|
||||
loadUsers();
|
||||
document.getElementById('tab-users')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
window.gotoUsersPage = gotoUsersPage;
|
||||
|
||||
async function changeRole(select) {
|
||||
select.disabled = true;
|
||||
try { await LS.adminUpdateRole(select.dataset.uid, select.value); LS.toast('Роль изменена', 'success', 2000); }
|
||||
|
||||
@@ -156,9 +156,8 @@ async function adminGetUsers(params = {}) {
|
||||
if (params.limit) p.set('limit', params.limit);
|
||||
if (params.role) p.set('role', params.role);
|
||||
if (params.q) p.set('q', params.q);
|
||||
const data = await req('GET', `/admin/users?${p}`);
|
||||
// API returns { users, total, page, limit } — extract users for compat
|
||||
return Array.isArray(data) ? data : data.users;
|
||||
// Returns { users, total, page, limit } (or { users, nextCursor, limit } if cursor used)
|
||||
return req('GET', `/admin/users?${p}`);
|
||||
}
|
||||
async function adminUpdateRole(id, role) { return req('PATCH', `/admin/users/${id}/role`, { role }); }
|
||||
async function adminGetUserSessions(id) { return req('GET', `/admin/users/${id}/sessions`); }
|
||||
|
||||
Reference in New Issue
Block a user