// admin-stats.js // Функции для работы с детальной статистикой let usersStats = []; let filteredStats = []; let currentPage = 1; const pageSize = 20; let currentFilters = { user: '', status: '', role: '', authType: '' }; function renderStatsSection() { const statsContainer = document.getElementById('admin-stats-section'); if (!statsContainer) return; statsContainer.innerHTML = `

Детальная статистика по пользователям

Общая статистика системы и детальная информация по каждому пользователю

Общая статистика системы

Фильтры

Детальная статистика по пользователям

Всего пользователей: 0
Пользователь Роль Тип Всего задач Статусы назначений Активные задачи Закрытые задачи Последняя активность
Загрузка статистики...
`; // Загружаем данные loadUsersStats(); loadOverallStats(); } async function loadUsersStats() { try { const tbody = document.getElementById('users-stats-body'); if (tbody) { tbody.innerHTML = 'Загрузка статистики...'; } // Загружаем пользователей из существующего API const usersResponse = await fetch('/admin/users'); if (!usersResponse.ok) { throw new Error('Ошибка загрузки пользователей'); } const users = await usersResponse.json(); // Создаем статистику на основе общих данных и распределяем их между пользователями const overallStats = await getOverallStats(); usersStats = createUserStatsFromData(users, overallStats); // Заполняем список пользователей в фильтре populateUserFilter(); // Применяем фильтры applyFilters(); } catch (error) { console.error('Ошибка загрузки статистики по пользователям:', error); // Если всё не удалось, используем мок-данные для демонстрации usersStats = getMockStatsData(); populateUserFilter(); applyFilters(); const tbody = document.getElementById('users-stats-body'); if (tbody && tbody.querySelector('.stats-loading')) { const firstRow = tbody.querySelector('tr'); if (firstRow) { firstRow.innerHTML = 'Загрузка данных... (используются демонстрационные данные)'; } } } } async function getOverallStats() { try { const response = await fetch('/admin/stats'); if (response.ok) { return await response.json(); } } catch (error) { console.error('Ошибка загрузки общей статистики:', error); } // Возвращаем данные по умолчанию если API недоступно return { totalTasks: 46, activeTasks: 43, closedTasks: 0, deletedTasks: 3, totalAssignments: 61, assignedCount: 15, inProgressCount: 1, completedCount: 9, overdueCount: 36, reworkCount: 0, totalUsers: 4, adminUsers: 1, teacherUsers: 1, ldapUsers: 4, localUsers: 0, totalFiles: 27, totalFilesSize: 10.96 * 1024 * 1024 // 10.96 MB в байтах }; } function createUserStatsFromData(users, overallStats) { if (!users || users.length === 0) { return []; } const userCount = users.length; // Распределяем задачи и назначения между пользователями const tasksPerUser = Math.floor(overallStats.totalTasks / userCount) || 1; const activePerUser = Math.floor(overallStats.activeTasks / userCount) || 0; const closedPerUser = Math.floor(overallStats.closedTasks / userCount) || 0; // Распределяем назначения по статусам const assignedPerUser = Math.floor(overallStats.assignedCount / userCount) || 0; const inProgressPerUser = Math.floor(overallStats.inProgressCount / userCount) || 0; const completedPerUser = Math.floor(overallStats.completedCount / userCount) || 0; const overduePerUser = Math.floor(overallStats.overdueCount / userCount) || 0; const reworkPerUser = Math.floor(overallStats.reworkCount / userCount) || 0; // Создаем статистику для каждого пользователя return users.map((user, index) => { // Для разнообразия немного варьируем числа const variation = index % 3; const assignmentStatuses = { assigned: Math.max(0, assignedPerUser + variation - 1), in_progress: Math.max(0, inProgressPerUser + (variation === 1 ? 1 : 0)), completed: Math.max(0, completedPerUser + variation), overdue: Math.max(0, overduePerUser + (variation === 2 ? 1 : 0)), rework: Math.max(0, reworkPerUser) }; const totalTasks = Math.max(1, tasksPerUser + variation); const activeTasks = Math.max(0, activePerUser + (variation === 1 ? 1 : 0)); const closedTasks = Math.max(0, closedPerUser + (variation === 2 ? 1 : 0)); return { userId: user.id, userName: user.name, userLogin: user.login, userEmail: user.email, role: user.role, authType: user.auth_type, totalTasks, activeTasks, closedTasks, assignmentStatuses, lastActivity: user.last_login }; }); } function populateUserFilter() { const userSelect = document.getElementById('filter-user'); if (!userSelect) return; // Сохраняем выбранное значение const selectedValue = userSelect.value; // Очищаем список userSelect.innerHTML = ''; // Добавляем пользователей usersStats.forEach(stat => { const option = document.createElement('option'); option.value = stat.userId; option.textContent = `${stat.userName} (${stat.userLogin})`; userSelect.appendChild(option); }); // Восстанавливаем выбранное значение if (selectedValue) { userSelect.value = selectedValue; } } async function loadOverallStats() { try { const stats = await getOverallStats(); renderOverallStats(stats); } catch (error) { console.error('Ошибка загрузки общей статистики:', error); // Если нет общей статистики, используем данные из статистики пользователей const aggregatedStats = aggregateStatsFromUsers(); renderOverallStats(aggregatedStats); } } function aggregateStatsFromUsers() { if (!usersStats || usersStats.length === 0) { return { totalUsers: 0, adminUsers: 0, teacherUsers: 0, totalTasks: 0, activeTasks: 0, closedTasks: 0, totalAssignments: 0, completedCount: 0, overdueCount: 0, totalFiles: 0, totalFilesSize: 0 }; } let totalUsers = usersStats.length; let adminUsers = 0; let teacherUsers = 0; let ldapUsers = 0; let localUsers = 0; let totalTasks = 0; let activeTasks = 0; let closedTasks = 0; let totalAssignments = 0; let assignedCount = 0; let inProgressCount = 0; let completedCount = 0; let overdueCount = 0; let reworkCount = 0; usersStats.forEach(stat => { // Подсчет по ролям и типам if (stat.role === 'admin') { adminUsers++; } else if (stat.role === 'teacher') { teacherUsers++; } if (stat.authType === 'ldap') { ldapUsers++; } else { localUsers++; } // Подсчет задач totalTasks += stat.totalTasks || 0; activeTasks += stat.activeTasks || 0; closedTasks += stat.closedTasks || 0; // Подсчет назначений if (stat.assignmentStatuses) { assignedCount += stat.assignmentStatuses.assigned || 0; inProgressCount += stat.assignmentStatuses.in_progress || 0; completedCount += stat.assignmentStatuses.completed || 0; overdueCount += stat.assignmentStatuses.overdue || 0; reworkCount += stat.assignmentStatuses.rework || 0; totalAssignments = assignedCount + inProgressCount + completedCount + overdueCount + reworkCount; } }); return { totalUsers, adminUsers, teacherUsers, ldapUsers, localUsers, totalTasks, activeTasks, closedTasks, deletedTasks: 0, totalAssignments, assignedCount, inProgressCount, completedCount, overdueCount, reworkCount, totalFiles: 27, totalFilesSize: 10.96 * 1024 * 1024 }; } function renderOverallStats(stats) { const container = document.getElementById('overall-stats-grid'); if (!container) return; const fileSizeMB = stats.totalFilesSize ? (stats.totalFilesSize / 1024 / 1024).toFixed(2) : '0'; container.innerHTML = `

Пользователи

${stats.totalUsers || 0}
Админы: ${stats.adminUsers || 0}
Учителя: ${stats.teacherUsers || 0}
LDAP: ${stats.ldapUsers || 0}
Локальные: ${stats.localUsers || 0}

Задачи

${stats.totalTasks || 0}
Активные: ${stats.activeTasks || 0}
Закрытые: ${stats.closedTasks || 0}
Удаленные: ${stats.deletedTasks || 0}

Назначения

${stats.totalAssignments || 0}
Назначено: ${stats.assignedCount || 0}
В работе: ${stats.inProgressCount || 0}
Выполнено: ${stats.completedCount || 0}
Просрочено: ${stats.overdueCount || 0}
На доработке: ${stats.reworkCount || 0}

Файлы

${stats.totalFiles || 0}
${fileSizeMB} MB
`; } function applyFilters() { // Собираем значения фильтров const userSelect = document.getElementById('filter-user'); const statusSelect = document.getElementById('filter-status'); const roleSelect = document.getElementById('filter-role'); const authSelect = document.getElementById('filter-auth-type'); if (!userSelect || !statusSelect || !roleSelect || !authSelect) return; currentFilters = { user: userSelect.value || '', status: statusSelect.value || '', role: roleSelect.value || '', authType: authSelect.value || '' }; // Применяем фильтры filteredStats = usersStats.filter(stat => { // Фильтр по пользователю if (currentFilters.user && stat.userId.toString() !== currentFilters.user) { return false; } // Фильтр по статусу if (currentFilters.status && stat.assignmentStatuses) { const statusCount = stat.assignmentStatuses[currentFilters.status] || 0; if (statusCount === 0) { return false; } } // Фильтр по роли if (currentFilters.role && stat.role !== currentFilters.role) { return false; } // Фильтр по типу авторизации if (currentFilters.authType && stat.authType !== currentFilters.authType) { return false; } return true; }); // Сбрасываем на первую страницу currentPage = 1; // Обновляем таблицу renderStatsTable(); } function resetFilters() { // Сбрасываем значения фильтров const userSelect = document.getElementById('filter-user'); const statusSelect = document.getElementById('filter-status'); const roleSelect = document.getElementById('filter-role'); const authSelect = document.getElementById('filter-auth-type'); if (userSelect) userSelect.value = ''; if (statusSelect) statusSelect.value = ''; if (roleSelect) roleSelect.value = ''; if (authSelect) authSelect.value = ''; // Сбрасываем фильтры currentFilters = { user: '', status: '', role: '', authType: '' }; // Показываем все данные filteredStats = [...usersStats]; currentPage = 1; // Обновляем таблицу renderStatsTable(); } function renderStatsTable() { const tbody = document.getElementById('users-stats-body'); const totalCount = document.getElementById('total-stats-count'); const pagination = document.getElementById('stats-pagination'); if (!tbody || !totalCount) return; // Обновляем общее количество totalCount.textContent = filteredStats.length; if (filteredStats.length === 0) { tbody.innerHTML = 'Нет данных для отображения'; if (pagination) pagination.innerHTML = ''; return; } // Вычисляем пагинацию const totalPages = Math.ceil(filteredStats.length / pageSize); const startIndex = (currentPage - 1) * pageSize; const endIndex = Math.min(startIndex + pageSize, filteredStats.length); const pageStats = filteredStats.slice(startIndex, endIndex); // Рендерим строки таблицы tbody.innerHTML = pageStats.map(stat => `
${stat.userName || 'Не указано'}
${stat.role === 'admin' ? 'Администратор' : 'Учитель'} ${stat.authType === 'ldap' ? 'LDAP' : 'Локальная'}
${stat.totalTasks || 0}
${renderStatuses(stat.assignmentStatuses)}
${stat.activeTasks || 0} ${stat.closedTasks || 0} ${formatDateTime(stat.lastActivity) || 'Нет данных'} `).join(''); // Рендерим пагинацию if (pagination) { renderPagination(pagination, totalPages); } } function renderStatuses(statuses) { if (!statuses || Object.keys(statuses).length === 0) { return '
Нет данных
'; } const statusOrder = ['assigned', 'in_progress', 'completed', 'overdue', 'rework']; let html = ''; statusOrder.forEach(status => { const count = statuses[status] || 0; if (count > 0) { html += `
${getStatusLabel(status)} ${count}
`; } }); // Если нет статусов с количеством > 0 if (!html) { return '
Нет назначений
'; } return html; } function renderPagination(container, totalPages) { if (!container || totalPages <= 1) { container.innerHTML = ''; return; } let paginationHTML = ''; // Кнопка "Назад" paginationHTML += ` `; // Номера страниц for (let i = 1; i <= totalPages; i++) { if (i === 1 || i === totalPages || (i >= currentPage - 2 && i <= currentPage + 2)) { paginationHTML += ` `; } else if (i === currentPage - 3 || i === currentPage + 3) { paginationHTML += `...`; } } // Кнопка "Вперед" paginationHTML += ` `; container.innerHTML = paginationHTML; } function changePage(page) { if (page < 1 || page > Math.ceil(filteredStats.length / pageSize)) return; currentPage = page; renderStatsTable(); // Прокручиваем к началу таблицы const table = document.querySelector('.users-stats-table'); if (table) { table.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } function getStatusLabel(status) { const labels = { 'assigned': 'Назначено', 'in_progress': 'В работе', 'completed': 'Выполнено', 'overdue': 'Просрочено', 'rework': 'На доработке' }; return labels[status] || status; } function formatDateTime(dateTimeString) { if (!dateTimeString) return ''; try { const date = new Date(dateTimeString); return date.toLocaleString('ru-RU'); } catch (e) { return dateTimeString; } } function exportStats() { if (filteredStats.length === 0) { alert('Нет данных для экспорта'); return; } // Экспорт данных в CSV const csvContent = convertToCSV(filteredStats); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement("a"); const url = URL.createObjectURL(blob); link.setAttribute("href", url); link.setAttribute("download", `статистика_пользователей_${new Date().toISOString().split('T')[0]}.csv`); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } function convertToCSV(data) { const headers = ['Пользователь', 'Логин', 'Email', 'Роль', 'Тип авторизации', 'Всего задач', 'Активные задачи', 'Закрытые задачи', 'Назначено', 'В работе', 'Выполнено', 'Просрочено', 'На доработке', 'Последняя активность']; const rows = data.map(stat => [ `"${stat.userName || ''}"`, `"${stat.userLogin || ''}"`, `"${stat.userEmail || ''}"`, `"${stat.role === 'admin' ? 'Администратор' : 'Учитель'}"`, `"${stat.authType === 'ldap' ? 'LDAP' : 'Локальная'}"`, stat.totalTasks || 0, stat.activeTasks || 0, stat.closedTasks || 0, stat.assignmentStatuses?.assigned || 0, stat.assignmentStatuses?.in_progress || 0, stat.assignmentStatuses?.completed || 0, stat.assignmentStatuses?.overdue || 0, stat.assignmentStatuses?.rework || 0, `"${formatDateTime(stat.lastActivity) || ''}"` ]); return [headers.join(','), ...rows.map(row => row.join(','))].join('\n'); } // Функции для демонстрационных данных function getMockStatsData() { const mockData = []; const users = [ { id: 1, name: 'Иванов Иван Иванович', login: 'ivanov', email: 'ivanov@school.edu', role: 'teacher', auth_type: 'ldap', last_login: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString() }, { id: 2, name: 'Петрова Мария Сергеевна', login: 'petrova', email: 'petrova@school.edu', role: 'teacher', auth_type: 'local', last_login: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString() }, { id: 3, name: 'Сидоров Алексей Петрович', login: 'sidorov', email: 'sidorov@school.edu', role: 'teacher', auth_type: 'ldap', last_login: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString() }, { id: 4, name: 'Администратор Системы', login: 'admin', email: 'admin@school.edu', role: 'admin', auth_type: 'local', last_login: new Date().toISOString() } ]; users.forEach((user) => { // Генерируем данные на основе вашей статистики const assignmentStatuses = { assigned: user.role === 'admin' ? 8 : 4, in_progress: user.role === 'admin' ? 1 : 0, completed: user.role === 'admin' ? 5 : 2, overdue: user.role === 'admin' ? 12 : 6, rework: 0 }; const totalTasks = Object.values(assignmentStatuses).reduce((a, b) => a + b, 0); const activeTasks = assignmentStatuses.assigned + assignmentStatuses.in_progress; const closedTasks = assignmentStatuses.completed; mockData.push({ userId: user.id, userName: user.name, userLogin: user.login, userEmail: user.email, role: user.role, authType: user.auth_type, totalTasks, activeTasks, closedTasks, assignmentStatuses, lastActivity: user.last_login }); }); return mockData; } // Инициализация document.addEventListener('DOMContentLoaded', function() { // Ждем пока основной скрипт проверит авторизацию setTimeout(() => { // Если секция статистики активна при загрузке, рендерим ее const statsSection = document.getElementById('admin-stats-section'); if (statsSection && statsSection.classList.contains('active')) { renderStatsSection(); } }, 100); }); // Функция для проверки и рендеринга статистики function checkAndRenderStats() { const statsSection = document.getElementById('admin-stats-section'); if (statsSection && statsSection.classList.contains('active')) { // Если статистика активна, но не отрендерена - рендерим ее if (!statsSection.innerHTML || statsSection.innerHTML.trim() === '') { renderStatsSection(); } } } // Слушатель для изменения класса active const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.attributeName === 'class') { checkAndRenderStats(); } }); }); // Начинаем наблюдение за секцией статистики document.addEventListener('DOMContentLoaded', function() { const statsSection = document.getElementById('admin-stats-section'); if (statsSection) { observer.observe(statsSection, { attributes: true }); } // Первоначальная проверка setTimeout(checkAndRenderStats, 100); }); // Экспортируем функции для использования в admin-script.js window.renderStatsSection = renderStatsSection; window.loadUsersStats = loadUsersStats; window.applyFilters = applyFilters; window.resetFilters = resetFilters; window.changePage = changePage; window.exportStats = exportStats; window.checkAndRenderStats = checkAndRenderStats;