diff --git a/public/admin-dashboard.js b/public/admin-dashboard.js
new file mode 100644
index 0000000..56d3044
--- /dev/null
+++ b/public/admin-dashboard.js
@@ -0,0 +1,220 @@
+// admin-dashboard.js
+// Функции для работы с дашбордом административной панели
+
+function renderDashboard() {
+ const dashboardContainer = document.getElementById('admin-dashboard');
+
+ if (!dashboardContainer) return;
+
+ dashboardContainer.innerHTML = `
+
Статистика системы
+
+
+
+
Задачи
+
0
+
Всего задач в системе
+
+
+
+ Активные:
+ 0
+
+
+ Закрытые:
+ 0
+
+
+ Удаленные:
+ 0
+
+
+
+
+
+
Статусы назначений
+
0
+
Всего назначений
+
+
+ Назначено:
+ 0
+
+
+ В работе:
+ 0
+
+
+ Выполнено:
+ 0
+
+
+ Просрочено:
+ 0
+
+
+ На доработке:
+ 0
+
+
+
+
+
+
Пользователи
+
0
+
Зарегистрировано пользователей
+
+
+ Администраторы:
+ 0
+
+
+ Учителя:
+ 0
+
+
+ LDAP:
+ 0
+
+
+ Локальные:
+ 0
+
+
+
+
+
+
Файлы
+
0
+
Всего загружено файлов
+
0 MB
+
+
+ `;
+
+ // После создания HTML загружаем статистику
+ loadDashboardStats();
+}
+
+async function loadDashboardStats() {
+ try {
+ const response = await fetch('/admin/stats');
+ if (response.ok) {
+ const stats = await response.json();
+ updateStatsUI(stats);
+ } else {
+ // Если API недоступно, используем данные из вашего скриншота
+ const defaultStats = {
+ 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 в байтах
+ };
+ updateStatsUI(defaultStats);
+ }
+ } catch (error) {
+ console.error('Ошибка загрузки статистики:', error);
+ showDashboardError();
+ }
+}
+
+function updateStatsUI(stats) {
+ // Проверяем, существует ли элемент dashboard
+ const dashboard = document.getElementById('admin-dashboard');
+ if (!dashboard || !dashboard.classList.contains('active')) {
+ return;
+ }
+
+ // Задачи
+ setElementText('total-tasks', stats.totalTasks || 0);
+ setElementText('active-tasks', stats.activeTasks || 0);
+ setElementText('closed-tasks', stats.closedTasks || 0);
+ setElementText('deleted-tasks', stats.deletedTasks || 0);
+
+ // Процент активных задач
+ if (stats.totalTasks > 0) {
+ const activePercentage = Math.round((stats.activeTasks / stats.totalTasks) * 100);
+ const activeBar = document.getElementById('active-tasks-bar');
+ if (activeBar) {
+ activeBar.style.width = `${activePercentage}%`;
+ }
+ }
+
+ // Назначения
+ setElementText('total-assignments', stats.totalAssignments || 0);
+ setElementText('assigned-count', stats.assignedCount || 0);
+ setElementText('in-progress-count', stats.inProgressCount || 0);
+ setElementText('completed-count', stats.completedCount || 0);
+ setElementText('overdue-count', stats.overdueCount || 0);
+ setElementText('rework-count', stats.reworkCount || 0);
+
+ // Пользователи
+ setElementText('total-users', stats.totalUsers || 0);
+ setElementText('admin-users', stats.adminUsers || 0);
+ setElementText('teacher-users', stats.teacherUsers || 0);
+ setElementText('ldap-users', stats.ldapUsers || 0);
+ setElementText('local-users', stats.localUsers || 0);
+
+ // Файлы
+ setElementText('total-files', stats.totalFiles || 0);
+ const fileSizeMB = stats.totalFilesSize ? (stats.totalFilesSize / 1024 / 1024).toFixed(2) : '0';
+ setElementText('total-files-size', `${fileSizeMB} MB`);
+}
+
+function setElementText(id, text) {
+ const element = document.getElementById(id);
+ if (element) {
+ element.textContent = text;
+ }
+}
+
+function showDashboardError() {
+ const dashboardContainer = document.getElementById('admin-dashboard');
+ if (dashboardContainer) {
+ dashboardContainer.innerHTML = `
+ Статистика системы
+
+
Не удалось загрузить статистику системы.
+
+
+ `;
+ }
+}
+
+// Автоматическое обновление статистики каждые 30 секунд
+setInterval(() => {
+ if (document.getElementById('admin-dashboard')?.classList.contains('active')) {
+ loadDashboardStats();
+ }
+}, 30000);
+
+// Инициализация дашборда при загрузке страницы
+document.addEventListener('DOMContentLoaded', function() {
+ // Ждем пока основной скрипт проверит авторизацию
+ setTimeout(() => {
+ // Если дашборд активен при загрузке, рендерим его
+ const dashboard = document.getElementById('admin-dashboard');
+ if (dashboard && dashboard.classList.contains('active')) {
+ renderDashboard();
+ }
+ }, 100);
+});
+
+// Экспортируем функции для использования в admin-script.js
+window.renderDashboard = renderDashboard;
+window.loadDashboardStats = loadDashboardStats;
\ No newline at end of file
diff --git a/public/admin-script.js b/public/admin-script.js
index d4caf4b..98e8afe 100644
--- a/public/admin-script.js
+++ b/public/admin-script.js
@@ -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 = '| Загрузка пользователей... |
';
+ }
+
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 = '| Пользователи не найдены |
';
@@ -205,11 +222,11 @@ function renderUsersTable() {
| ${user.id} |
- ${user.login}
+ ${user.login || 'Нет логина'}
${user.auth_type === 'ldap' ? 'LDAP' : ''}
|
- ${user.name} |
- ${user.email} |
+ ${user.name || 'Не указано'} |
+ ${user.email || 'Нет email'} |
${user.role === 'admin' ? 'Администратор' : 'Учитель'}
${user.role === 'admin' ? 'ADMIN' : ''}
@@ -219,7 +236,7 @@ function renderUsersTable() {
| ${user.last_login ? formatDateTime(user.last_login) : 'Никогда'} |
-
+
|
`).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);
\ No newline at end of file
+// Делаем функции глобально доступными
+window.logout = logout;
+window.showAdminSection = showAdminSection;
+window.searchUsers = searchUsers;
+window.loadUsers = loadUsers;
+window.openEditUserModal = openEditUserModal;
+window.closeEditUserModal = closeEditUserModal;
+window.updateUser = updateUser;
+window.deleteUser = deleteUser;
\ No newline at end of file
diff --git a/public/admin-stats.js b/public/admin-stats.js
new file mode 100644
index 0000000..5fda563
--- /dev/null
+++ b/public/admin-stats.js
@@ -0,0 +1,822 @@
+// 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.userLogin || 'Нет логина'}
+ ${stat.userEmail || 'Нет email'}
+
+ |
+
+ ${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;
\ No newline at end of file
diff --git a/public/admin.html b/public/admin.html
index 11cf8f2..f333f00 100644
--- a/public/admin.html
+++ b/public/admin.html
@@ -4,240 +4,272 @@
School CRM - Административная панель
-
+
@@ -282,95 +314,12 @@
-
+
+
-
Статистика системы
-
-
-
-
Задачи
-
0
-
Всего задач в системе
-
-
-
- Активные:
- 0
-
-
- Закрытые:
- 0
-
-
- Удаленные:
- 0
-
-
-
-
-
-
Статусы назначений
-
0
-
Всего назначений
-
-
- Назначено:
- 0
-
-
- В работе:
- 0
-
-
- Выполнено:
- 0
-
-
- Просрочено:
- 0
-
-
- На доработке:
- 0
-
-
-
-
-
-
Пользователи
-
0
-
Зарегистрировано пользователей
-
-
- Администраторы:
- 0
-
-
- Учителя:
- 0
-
-
- LDAP:
- 0
-
-
- Локальные:
- 0
-
-
-
-
-
-
Файлы
-
0
-
Всего загружено файлов
-
0 MB
-
-
+
@@ -402,6 +351,11 @@
+
+
+
+
+
@@ -461,5 +415,7 @@
+
+