// Система управления пользователями class UserManager { constructor() { this.users = []; this.filteredUsers = []; this.currentUser = null; this.init(); } init() { // Проверяем авторизацию и права доступа if (!auth.isUserAuthenticated()) { window.location.href = '../login.html'; return; } if (!auth.hasRole('admin')) { auth.showNotification('У вас нет прав для доступа к этой странице', 'danger'); window.location.href = '../index.html'; return; } this.currentUser = auth.getCurrentUser(); this.bindEvents(); this.loadUsers(); this.loadCategoriesNav(); } bindEvents() { // Поиск и фильтрация $('#searchInput').on('input', (e) => this.handleSearch(e.target.value)); $('#roleFilter').on('change', (e) => this.handleRoleFilter(e.target.value)); // Формы $('#addUserForm').on('submit', (e) => this.handleAddUser(e)); $('#editUserForm').on('submit', (e) => this.handleEditUser(e)); // Удаление $('#confirmDeleteBtn').on('click', () => this.handleDeleteUser()); // Автоматическое обновление разрешений при изменении ролей $('input[type="checkbox"][value^="role"]').on('change', () => this.updatePermissionsFromRoles()); $('input[type="checkbox"][value^="editRole"]').on('change', () => this.updateEditPermissionsFromRoles()); } // Загрузка пользователей async loadUsers() { try { this.showLoading(); const token = localStorage.getItem('token'); if (!token) { throw new Error('Токен не найден'); } const response = await fetch('/api/users', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } this.users = await response.json(); this.filteredUsers = [...this.users]; this.renderUsers(); this.updateStatistics(); this.hideLoading(); } catch (error) { // Если API недоступен, используем имитацию данных this.users = this.getMockUsers(); this.filteredUsers = [...this.users]; this.renderUsers(); this.updateStatistics(); this.hideLoading(); // Показываем уведомление только если это не демо-режим if (!error.message.includes('Failed to fetch')) { auth.showNotification('Ошибка при загрузке пользователей', 'danger'); } } } // Получение имитационных данных пользователей getMockUsers() { return [ { id: 1, username: 'admin', name: 'Администратор', email: 'admin@company.com', department: 'IT', position: 'Системный администратор', roles: ['admin'], permissions: ['read', 'write', 'delete', 'admin'], active: true, createdAt: '2025-01-01T00:00:00Z', lastLogin: '2025-08-15T10:30:00Z' }, { id: 2, username: 'manager', name: 'Менеджер склада', email: 'manager@company.com', department: 'Склад', position: 'Менеджер склада', roles: ['manager'], permissions: ['read', 'write'], active: true, createdAt: '2025-01-15T00:00:00Z', lastLogin: '2025-08-15T09:15:00Z' }, { id: 3, username: 'operator', name: 'Оператор', email: 'operator@company.com', department: 'Склад', position: 'Оператор склада', roles: ['operator'], permissions: ['read'], active: true, createdAt: '2025-02-01T00:00:00Z', lastLogin: '2025-08-14T16:45:00Z' }, { id: 4, username: 'viewer', name: 'Наблюдатель', email: 'viewer@company.com', department: 'Бухгалтерия', position: 'Бухгалтер', roles: ['viewer'], permissions: ['read'], active: false, createdAt: '2025-02-15T00:00:00Z', lastLogin: '2025-08-10T14:20:00Z' } ]; } // Отрисовка пользователей renderUsers() { const grid = $('#usersGrid'); grid.empty(); if (this.filteredUsers.length === 0) { $('#emptyState').show(); return; } $('#emptyState').hide(); this.filteredUsers.forEach(user => { const userCard = this.createUserCard(user); grid.append(userCard); }); } // Создание карточки пользователя createUserCard(user) { const roleBadges = user.roles.map(role => { const roleConfig = this.getRoleConfig(role); return `${roleConfig.icon} ${roleConfig.name}`; }).join(''); const permissionChips = user.permissions.map(perm => { const permConfig = this.getPermissionConfig(perm); return `${permConfig.icon} ${permConfig.name}`; }).join(''); const statusClass = user.active ? 'status-active' : 'status-inactive'; const statusIcon = user.active ? 'fa-user-check' : 'fa-user-times'; const statusText = user.active ? 'Активен' : 'Неактивен'; return `
${this.highlightSearch(user.email)}
${user.department || 'Не указан'}
${user.position || 'Не указана'}
${statusText}
${roleBadges}
${permissionChips}
Создан: ${this.formatDate(user.createdAt)}
Последний вход: ${this.formatDate(user.lastLogin)}
`; } // Конфигурация ролей getRoleConfig(role) { const configs = { admin: { name: 'Администратор', class: 'bg-danger', icon: 'fas fa-user-shield' }, manager: { name: 'Менеджер', class: 'bg-warning', icon: 'fas fa-user-tie' }, operator: { name: 'Оператор', class: 'bg-info', icon: 'fas fa-user-cog' }, viewer: { name: 'Наблюдатель', class: 'bg-secondary', icon: 'fas fa-user' } }; return configs[role] || { name: role, class: 'bg-secondary', icon: 'fas fa-user' }; } // Конфигурация разрешений getPermissionConfig(permission) { const configs = { read: { name: 'Чтение', icon: 'fas fa-eye' }, write: { name: 'Запись', icon: 'fas fa-edit' }, delete: { name: 'Удаление', icon: 'fas fa-trash' }, admin: { name: 'Администрирование', icon: 'fas fa-cogs' } }; return configs[permission] || { name: permission, icon: 'fas fa-check' }; } // Подсветка поиска highlightSearch(text) { const searchTerm = $('#searchInput').val().toLowerCase(); if (!searchTerm || !text) return text; const regex = new RegExp(`(${searchTerm})`, 'gi'); return text.toString().replace(regex, '$1'); } // Обработка поиска handleSearch(query) { const searchTerm = query.toLowerCase(); const roleFilter = $('#roleFilter').val(); this.filteredUsers = this.users.filter(user => { const matchesSearch = !searchTerm || user.name.toLowerCase().includes(searchTerm) || user.username.toLowerCase().includes(searchTerm) || user.email.toLowerCase().includes(searchTerm) || (user.department && user.department.toLowerCase().includes(searchTerm)); const matchesRole = !roleFilter || user.roles.includes(roleFilter); return matchesSearch && matchesRole; }); this.renderUsers(); } // Обработка фильтра по ролям handleRoleFilter(role) { this.handleSearch($('#searchInput').val()); } // Обновление статистики updateStatistics() { const totalUsers = this.users.length; const activeUsers = this.users.filter(u => u.active).length; const adminUsers = this.users.filter(u => u.roles.includes('admin')).length; const managerUsers = this.users.filter(u => u.roles.includes('manager')).length; $('#totalUsers').text(totalUsers); $('#activeUsers').text(activeUsers); $('#adminUsers').text(adminUsers); $('#managerUsers').text(managerUsers); } // Добавление пользователя async handleAddUser(event) { event.preventDefault(); const formData = this.getFormData('#addUserForm'); if (!this.validateUserData(formData)) { return; } try { const token = localStorage.getItem('token'); if (!token) { throw new Error('Токен не найден'); } const response = await fetch('/api/users', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Ошибка создания пользователя'); } const newUser = await response.json(); this.users.push(newUser); this.filteredUsers = [...this.users]; this.renderUsers(); this.updateStatistics(); $('#addUserModal').modal('hide'); $('#addUserForm')[0].reset(); auth.showNotification('Пользователь успешно создан', 'success'); } catch (error) { // Если API недоступен, используем имитацию if (error.message.includes('Failed to fetch')) { const newUser = { id: this.users.length + 1, ...formData, active: true, createdAt: new Date().toISOString(), lastLogin: null }; this.users.push(newUser); this.filteredUsers = [...this.users]; this.renderUsers(); this.updateStatistics(); $('#addUserModal').modal('hide'); $('#addUserForm')[0].reset(); auth.showNotification('Пользователь успешно создан (демо-режим)', 'success'); } else { auth.showNotification(error.message || 'Ошибка при создании пользователя', 'danger'); } } } // Редактирование пользователя async handleEditUser(event) { event.preventDefault(); const formData = this.getFormData('#editUserForm'); const userId = parseInt($('#editUserId').val()); if (!this.validateUserData(formData)) { return; } try { const token = localStorage.getItem('token'); if (!token) { throw new Error('Токен не найден'); } const response = await fetch(`/api/users/${userId}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Ошибка обновления пользователя'); } const updatedUser = await response.json(); const userIndex = this.users.findIndex(u => u.id === userId); if (userIndex !== -1) { this.users[userIndex] = updatedUser; this.filteredUsers = [...this.users]; this.renderUsers(); this.updateStatistics(); $('#editUserModal').modal('hide'); auth.showNotification('Пользователь успешно обновлен', 'success'); } } catch (error) { // Если API недоступен, используем имитацию if (error.message.includes('Failed to fetch')) { const userIndex = this.users.findIndex(u => u.id === userId); if (userIndex !== -1) { this.users[userIndex] = { ...this.users[userIndex], ...formData }; this.filteredUsers = [...this.users]; this.renderUsers(); this.updateStatistics(); $('#editUserModal').modal('hide'); auth.showNotification('Пользователь успешно обновлен (демо-режим)', 'success'); } } else { auth.showNotification(error.message || 'Ошибка при обновлении пользователя', 'danger'); } } } // Удаление пользователя deleteUser(userId) { const user = this.users.find(u => u.id === userId); if (!user) return; $('#deleteUserName').text(user.name); $('#deleteUserModal').modal('show'); // Сохраняем ID для подтверждения $('#deleteUserModal').data('userId', userId); } // Подтверждение удаления async handleDeleteUser() { const userId = $('#deleteUserModal').data('userId'); try { const token = localStorage.getItem('token'); if (!token) { throw new Error('Токен не найден'); } const response = await fetch(`/api/users/${userId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Ошибка удаления пользователя'); } this.users = this.users.filter(u => u.id !== userId); this.filteredUsers = this.filteredUsers.filter(u => u.id !== userId); this.renderUsers(); this.updateStatistics(); $('#deleteUserModal').modal('hide'); auth.showNotification('Пользователь успешно удален', 'success'); } catch (error) { // Если API недоступен, используем имитацию if (error.message.includes('Failed to fetch')) { this.users = this.users.filter(u => u.id !== userId); this.filteredUsers = this.filteredUsers.filter(u => u.id !== userId); this.renderUsers(); this.updateStatistics(); $('#deleteUserModal').modal('hide'); auth.showNotification('Пользователь успешно удален (демо-режим)', 'success'); } else { auth.showNotification(error.message || 'Ошибка при удалении пользователя', 'danger'); } } } // Переключение статуса пользователя async toggleUserStatus(userId) { const user = this.users.find(u => u.id === userId); if (!user) return; try { const token = localStorage.getItem('token'); if (!token) { throw new Error('Токен не найден'); } const response = await fetch(`/api/users/${userId}/toggle-status`, { method: 'PATCH', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Ошибка изменения статуса'); } const result = await response.json(); user.active = result.active; this.filteredUsers = [...this.users]; this.renderUsers(); this.updateStatistics(); auth.showNotification(result.message, 'success'); } catch (error) { // Если API недоступен, используем имитацию if (error.message.includes('Failed to fetch')) { user.active = !user.active; this.filteredUsers = [...this.users]; this.renderUsers(); this.updateStatistics(); const status = user.active ? 'активирован' : 'деактивирован'; auth.showNotification(`Пользователь ${status} (демо-режим)`, 'success'); } else { auth.showNotification(error.message || 'Ошибка при изменении статуса', 'danger'); } } } // Открытие формы редактирования editUser(userId) { const user = this.users.find(u => u.id === userId); if (!user) return; // Заполняем форму данными пользователя $('#editUserId').val(user.id); $('#editUsername').val(user.username); $('#editEmail').val(user.email); $('#editFullName').val(user.name); $('#editDepartment').val(user.department || ''); $('#editPosition').val(user.position || ''); $('#editPassword').val(''); // Устанавливаем роли $('#editRoleAdmin').prop('checked', user.roles.includes('admin')); $('#editRoleManager').prop('checked', user.roles.includes('manager')); $('#editRoleOperator').prop('checked', user.roles.includes('operator')); $('#editRoleViewer').prop('checked', user.roles.includes('viewer')); // Устанавливаем разрешения $('#editPermRead').prop('checked', user.permissions.includes('read')); $('#editPermWrite').prop('checked', user.permissions.includes('write')); $('#editPermDelete').prop('checked', user.permissions.includes('delete')); $('#editPermAdmin').prop('checked', user.permissions.includes('admin')); $('#editUserModal').modal('show'); } // Получение данных формы getFormData(formSelector) { const form = $(formSelector); const formData = {}; // Основные поля form.find('input[type="text"], input[type="email"], input[type="password"]').each(function() { const field = $(this); const name = field.attr('id'); const value = field.val(); if (name) { formData[name] = value; } }); // Роли const roles = []; form.find('input[type="checkbox"][value^="admin"], input[type="checkbox"][value^="manager"], input[type="checkbox"][value^="operator"], input[type="checkbox"][value^="viewer"]').each(function() { if ($(this).is(':checked')) { roles.push($(this).val()); } }); formData.roles = roles; // Разрешения const permissions = []; form.find('input[type="checkbox"][value^="read"], input[type="checkbox"][value^="write"], input[type="checkbox"][value^="delete"], input[type="checkbox"][value^="admin"]').each(function() { if ($(this).is(':checked')) { permissions.push($(this).val()); } }); formData.permissions = permissions; return formData; } // Валидация данных пользователя validateUserData(formData) { if (!formData.username || formData.username.trim().length < 3) { auth.showNotification('Имя пользователя должно содержать минимум 3 символа', 'warning'); return false; } if (!formData.fullName || formData.fullName.trim().length < 2) { auth.showNotification('Полное имя должно содержать минимум 2 символа', 'warning'); return false; } if (!formData.email || !this.isValidEmail(formData.email)) { auth.showNotification('Введите корректный email адрес', 'warning'); return false; } if (formData.roles.length === 0) { auth.showNotification('Выберите хотя бы одну роль', 'warning'); return false; } if (formData.permissions.length === 0) { auth.showNotification('Выберите хотя бы одно разрешение', 'warning'); return false; } // Проверка уникальности username const existingUser = this.users.find(u => u.username === formData.username); if (existingUser && existingUser.id !== parseInt($('#editUserId').val() || 0)) { auth.showNotification('Пользователь с таким именем уже существует', 'warning'); return false; } return true; } // Проверка email isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } // Автоматическое обновление разрешений при изменении ролей updatePermissionsFromRoles() { const roles = []; $('input[type="checkbox"][value^="admin"], input[type="checkbox"][value^="manager"], input[type="checkbox"][value^="operator"], input[type="checkbox"][value^="viewer"]').each(function() { if ($(this).is(':checked')) { roles.push($(this).val()); } }); // Сбрасываем все разрешения $('input[type="checkbox"][value^="read"], input[type="checkbox"][value^="write"], input[type="checkbox"][value^="delete"], input[type="checkbox"][value^="admin"]').prop('checked', false); // Устанавливаем разрешения в зависимости от ролей if (roles.includes('admin')) { $('#permRead, #permWrite, #permDelete, #permAdmin').prop('checked', true); } else if (roles.includes('manager')) { $('#permRead, #permWrite').prop('checked', true); } else if (roles.includes('operator')) { $('#permRead').prop('checked', true); } else if (roles.includes('viewer')) { $('#permRead').prop('checked', true); } } // Автоматическое обновление разрешений при изменении ролей (для редактирования) updateEditPermissionsFromRoles() { const roles = []; $('input[type="checkbox"][value^="editRole"]').each(function() { if ($(this).is(':checked')) { roles.push($(this).val().replace('editRole', '')); } }); // Сбрасываем все разрешения $('input[type="checkbox"][value^="editPerm"]').prop('checked', false); // Устанавливаем разрешения в зависимости от ролей if (roles.includes('admin')) { $('#editPermRead, #editPermWrite, #editPermDelete, #editPermAdmin').prop('checked', true); } else if (roles.includes('manager')) { $('#editPermRead, #editPermWrite').prop('checked', true); } else if (roles.includes('operator')) { $('#editPermRead').prop('checked', true); } else if (roles.includes('viewer')) { $('#editPermRead').prop('checked', true); } } // Форматирование даты formatDate(dateString) { if (!dateString) return 'Никогда'; const date = new Date(dateString); return date.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); } // Показать загрузку showLoading() { $('#loadingState').show(); $('#usersGrid').hide(); $('#emptyState').hide(); } // Скрыть загрузку hideLoading() { $('#loadingState').hide(); $('#usersGrid').show(); } // Загрузка категорий в навигацию async loadCategoriesNav() { try { // В реальном приложении здесь был бы запрос к API const categories = [ { id: 1, name: 'Электроника', icon: 'fas fa-laptop', color: '#2c5aa0', active: true }, { id: 2, name: 'Инструменты', icon: 'fas fa-tools', color: '#1a7f37', active: true }, { id: 3, name: 'Офис', icon: 'fas fa-briefcase', color: '#8a2be2', active: true } ]; const dropdown = $('#catalogCategoriesDropdown'); // Очищаем существующие категории (кроме "Все позиции" и разделителя) dropdown.find('li:not(:first-child):not(:nth-child(2))').remove(); // Добавляем активные категории categories.forEach(category => { if (category.active) { const categoryLink = `
  • ${category.name}
  • `; dropdown.append(categoryLink); } }); } catch (error) { // Ошибка загрузки категорий не критична для этой страницы } } } // Инициализация менеджера пользователей const userManager = new UserManager();