Статистика

This commit is contained in:
2026-02-06 17:11:56 +05:00
parent 3a2b47c791
commit fff495d88c
5 changed files with 1664 additions and 368 deletions

View File

@@ -1,3 +1,4 @@
// admin-script.js (обновленный)
let currentUser = null;
let users = [];
let filteredUsers = [];
@@ -45,12 +46,31 @@ function showAdminInterface() {
document.getElementById('current-user').textContent = userInfo;
loadUsers();
loadDashboardStats();
// Если дашборд активен, рендерим его
if (document.getElementById('admin-dashboard').classList.contains('active')) {
if (typeof renderDashboard === 'function') {
renderDashboard();
}
}
// Если статистика активна, рендерим ее
if (document.getElementById('admin-stats-section').classList.contains('active')) {
if (typeof renderStatsSection === 'function') {
renderStatsSection();
}
}
}
function setupEventListeners() {
document.getElementById('login-form').addEventListener('submit', login);
document.getElementById('edit-user-form').addEventListener('submit', updateUser);
const loginForm = document.getElementById('login-form');
const editUserForm = document.getElementById('edit-user-form');
if (loginForm) {
loginForm.addEventListener('submit', login);
}
if (editUserForm) {
editUserForm.addEventListener('submit', updateUser);
}
}
async function login(event) {
@@ -99,26 +119,68 @@ async function logout() {
}
function showAdminSection(sectionName) {
// Убираем активный класс у всех вкладок
document.querySelectorAll('.admin-tab').forEach(tab => {
tab.classList.remove('active');
});
// Убираем активный класс у всех секций
document.querySelectorAll('.admin-section').forEach(section => {
section.classList.remove('active');
});
document.querySelector(`.admin-tab[onclick="showAdminSection('${sectionName}')"]`).classList.add('active');
document.getElementById(`admin-${sectionName}`).classList.add('active');
// Находим и активируем соответствующую вкладку
const tab = document.querySelector(`.admin-tab[onclick*="showAdminSection('${sectionName}')"]`);
if (tab) {
tab.classList.add('active');
} else {
// Альтернативный поиск если выше не сработал
const tabs = document.querySelectorAll('.admin-tab');
tabs.forEach(t => {
if (t.textContent.toLowerCase().includes(sectionName)) {
t.classList.add('active');
}
});
}
// Активируем соответствующую секцию
const section = document.getElementById(`admin-${sectionName}`);
if (section) {
section.classList.add('active');
} else {
// Если секция не найдена по ID, ищем по другому шаблону
const sections = document.querySelectorAll('.admin-section');
sections.forEach(s => {
if (s.id.includes(sectionName)) {
s.classList.add('active');
}
});
}
// Загружаем данные для активной секции
if (sectionName === 'users') {
loadUsers();
} else if (sectionName === 'dashboard') {
loadDashboardStats();
if (typeof renderDashboard === 'function') {
renderDashboard();
}
} else if (sectionName === 'stats') {
if (typeof renderStatsSection === 'function') {
renderStatsSection();
} else if (typeof checkAndRenderStats === 'function') {
// Альтернативный вызов если функция переименована
checkAndRenderStats();
}
}
}
async function loadUsers() {
try {
const tbody = document.getElementById('users-table-body');
if (tbody) {
tbody.innerHTML = '<tr><td colspan="9" class="loading">Загрузка пользователей...</td></tr>';
}
const response = await fetch('/admin/users');
if (!response.ok) {
throw new Error('Ошибка загрузки пользователей');
@@ -132,69 +194,24 @@ async function loadUsers() {
}
}
async function loadDashboardStats() {
try {
const response = await fetch('/admin/stats');
if (!response.ok) {
throw new Error('Ошибка загрузки статистики');
}
const stats = await response.json();
updateStatsUI(stats);
} catch (error) {
console.error('Ошибка загрузки статистики:', error);
}
}
function updateStatsUI(stats) {
// Задачи
document.getElementById('total-tasks').textContent = stats.totalTasks;
document.getElementById('active-tasks').textContent = stats.activeTasks;
document.getElementById('closed-tasks').textContent = stats.closedTasks;
document.getElementById('deleted-tasks').textContent = stats.deletedTasks;
// Процент активных задач
if (stats.totalTasks > 0) {
const activePercentage = Math.round((stats.activeTasks / stats.totalTasks) * 100);
document.getElementById('active-tasks-bar').style.width = `${activePercentage}%`;
}
// Назначения
document.getElementById('total-assignments').textContent = stats.totalAssignments;
document.getElementById('assigned-count').textContent = stats.assignedCount;
document.getElementById('in-progress-count').textContent = stats.inProgressCount;
document.getElementById('completed-count').textContent = stats.completedCount;
document.getElementById('overdue-count').textContent = stats.overdueCount;
document.getElementById('rework-count').textContent = stats.reworkCount;
// Пользователи
document.getElementById('total-users').textContent = stats.totalUsers;
document.getElementById('admin-users').textContent = stats.adminUsers;
document.getElementById('teacher-users').textContent = stats.teacherUsers;
document.getElementById('ldap-users').textContent = stats.ldapUsers;
document.getElementById('local-users').textContent = stats.localUsers;
// Файлы
document.getElementById('total-files').textContent = stats.totalFiles;
const fileSizeMB = (stats.totalFilesSize / 1024 / 1024).toFixed(2);
document.getElementById('total-files-size').textContent = `${fileSizeMB} MB`;
}
function searchUsers() {
const search = document.getElementById('user-search').value.toLowerCase();
const searchInput = document.getElementById('user-search');
if (!searchInput) return;
const search = searchInput.value.toLowerCase();
filteredUsers = users.filter(user =>
user.login.toLowerCase().includes(search) ||
user.name.toLowerCase().includes(search) ||
user.email.toLowerCase().includes(search) ||
user.role.toLowerCase().includes(search) ||
user.auth_type.toLowerCase().includes(search)
(user.login && user.login.toLowerCase().includes(search)) ||
(user.name && user.name.toLowerCase().includes(search)) ||
(user.email && user.email.toLowerCase().includes(search)) ||
(user.role && user.role.toLowerCase().includes(search)) ||
(user.auth_type && user.auth_type.toLowerCase().includes(search))
);
renderUsersTable();
}
function renderUsersTable() {
const tbody = document.getElementById('users-table-body');
if (!tbody) return;
if (!filteredUsers || filteredUsers.length === 0) {
tbody.innerHTML = '<tr><td colspan="9" class="loading">Пользователи не найдены</td></tr>';
@@ -205,11 +222,11 @@ function renderUsersTable() {
<tr>
<td>${user.id}</td>
<td>
${user.login}
${user.login || 'Нет логина'}
${user.auth_type === 'ldap' ? '<span class="ldap-badge">LDAP</span>' : ''}
</td>
<td>${user.name}</td>
<td>${user.email}</td>
<td>${user.name || 'Не указано'}</td>
<td>${user.email || 'Нет email'}</td>
<td>
${user.role === 'admin' ? 'Администратор' : 'Учитель'}
${user.role === 'admin' ? '<span class="admin-badge">ADMIN</span>' : ''}
@@ -219,7 +236,7 @@ function renderUsersTable() {
<td>${user.last_login ? formatDateTime(user.last_login) : 'Никогда'}</td>
<td class="user-actions">
<button class="edit-btn" onclick="openEditUserModal(${user.id})" title="Редактировать">✏️</button>
<button class="delete-btn" onclick="deleteUser(${user.id})" title="Удалить" ${user.id === currentUser.id ? 'disabled' : ''}>🗑️</button>
<button class="delete-btn" onclick="deleteUser(${user.id})" title="Удалить" ${user.id === currentUser?.id ? 'disabled' : ''}>🗑️</button>
</td>
</tr>
`).join('');
@@ -243,7 +260,10 @@ async function openEditUserModal(userId) {
document.getElementById('edit-groups').value = user.groups || '[]';
document.getElementById('edit-description').value = user.description || '';
document.getElementById('edit-user-modal').style.display = 'block';
const modal = document.getElementById('edit-user-modal');
if (modal) {
modal.style.display = 'block';
}
} catch (error) {
console.error('Ошибка:', error);
alert('Ошибка загрузки пользователя');
@@ -251,7 +271,10 @@ async function openEditUserModal(userId) {
}
function closeEditUserModal() {
document.getElementById('edit-user-modal').style.display = 'none';
const modal = document.getElementById('edit-user-modal');
if (modal) {
modal.style.display = 'none';
}
}
async function updateUser(event) {
@@ -294,7 +317,21 @@ async function updateUser(event) {
alert('Пользователь успешно обновлен!');
closeEditUserModal();
loadUsers();
loadDashboardStats();
// Обновляем статистику если она видна
if (document.getElementById('admin-dashboard')?.classList.contains('active')) {
if (typeof loadDashboardStats === 'function') {
loadDashboardStats();
}
}
if (document.getElementById('admin-stats-section')?.classList.contains('active')) {
if (typeof loadUsersStats === 'function') {
loadUsersStats();
}
if (typeof loadOverallStats === 'function') {
loadOverallStats();
}
}
} else {
const error = await response.json();
alert(error.error || 'Ошибка обновления пользователя');
@@ -306,7 +343,7 @@ async function updateUser(event) {
}
async function deleteUser(userId) {
if (userId === currentUser.id) {
if (userId === currentUser?.id) {
alert('Нельзя удалить самого себя');
return;
}
@@ -323,7 +360,21 @@ async function deleteUser(userId) {
if (response.ok) {
alert('Пользователь успешно удален!');
loadUsers();
loadDashboardStats();
// Обновляем статистику если она видна
if (document.getElementById('admin-dashboard')?.classList.contains('active')) {
if (typeof loadDashboardStats === 'function') {
loadDashboardStats();
}
}
if (document.getElementById('admin-stats-section')?.classList.contains('active')) {
if (typeof loadUsersStats === 'function') {
loadUsersStats();
}
if (typeof loadOverallStats === 'function') {
loadOverallStats();
}
}
} else {
const error = await response.json();
alert(error.error || 'Ошибка удаления пользователя');
@@ -336,14 +387,22 @@ async function deleteUser(userId) {
function formatDateTime(dateTimeString) {
if (!dateTimeString) return '';
const date = new Date(dateTimeString);
return date.toLocaleString('ru-RU');
try {
const date = new Date(dateTimeString);
return date.toLocaleString('ru-RU');
} catch (e) {
return dateTimeString;
}
}
function formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
return date.toLocaleDateString('ru-RU');
try {
const date = new Date(dateString);
return date.toLocaleDateString('ru-RU');
} catch (e) {
return dateString;
}
}
function showError(elementId, message) {
@@ -353,9 +412,12 @@ function showError(elementId, message) {
}
}
// Автоматическое обновление статистики каждые 30 секунд
setInterval(() => {
if (document.getElementById('admin-dashboard').classList.contains('active')) {
loadDashboardStats();
}
}, 30000);
// Делаем функции глобально доступными
window.logout = logout;
window.showAdminSection = showAdminSection;
window.searchUsers = searchUsers;
window.loadUsers = loadUsers;
window.openEditUserModal = openEditUserModal;
window.closeEditUserModal = closeEditUserModal;
window.updateUser = updateUser;
window.deleteUser = deleteUser;