Files
sklad_legacy/js/users.js
2025-08-22 19:26:43 +03:00

792 lines
33 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Система управления пользователями
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 `<span class="badge ${roleConfig.class} role-badge">${roleConfig.icon} ${roleConfig.name}</span>`;
}).join('');
const permissionChips = user.permissions.map(perm => {
const permConfig = this.getPermissionConfig(perm);
return `<span class="permission-chip">${permConfig.icon} ${permConfig.name}</span>`;
}).join('');
const statusClass = user.active ? 'status-active' : 'status-inactive';
const statusIcon = user.active ? 'fa-user-check' : 'fa-user-times';
const statusText = user.active ? 'Активен' : 'Неактивен';
return `
<div class="col-lg-4 col-md-6 mb-4">
<div class="card user-card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-0">
<i class="fas fa-user me-2"></i>
${this.highlightSearch(user.name)}
</h6>
<small class="text-muted">@${user.username}</small>
</div>
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="userManager.editUser(${user.id})">
<i class="fas fa-edit me-2"></i>Редактировать
</a></li>
<li><a class="dropdown-item" href="#" onclick="userManager.toggleUserStatus(${user.id})">
<i class="fas fa-toggle-on me-2"></i>${user.active ? 'Деактивировать' : 'Активировать'}
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="userManager.deleteUser(${user.id})">
<i class="fas fa-trash me-2"></i>Удалить
</a></li>
</ul>
</div>
</div>
<div class="card-body">
<div class="mb-3">
<div class="d-flex align-items-center mb-2">
<i class="fas fa-envelope me-2 text-muted"></i>
<small>${this.highlightSearch(user.email)}</small>
</div>
<div class="d-flex align-items-center mb-2">
<i class="fas fa-building me-2 text-muted"></i>
<small>${user.department || 'Не указан'}</small>
</div>
<div class="d-flex align-items-center mb-2">
<i class="fas fa-briefcase me-2 text-muted"></i>
<small>${user.position || 'Не указана'}</small>
</div>
<div class="d-flex align-items-center">
<i class="fas ${statusIcon} me-2 ${statusClass}"></i>
<small class="${statusClass}">${statusText}</small>
</div>
</div>
<div class="mb-3">
<label class="form-label small text-muted">Роли:</label>
<div>${roleBadges}</div>
</div>
<div class="mb-3">
<label class="form-label small text-muted">Разрешения:</label>
<div>${permissionChips}</div>
</div>
<div class="small text-muted">
<div>Создан: ${this.formatDate(user.createdAt)}</div>
<div>Последний вход: ${this.formatDate(user.lastLogin)}</div>
</div>
</div>
</div>
</div>
`;
}
// Конфигурация ролей
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, '<span class="search-highlight">$1</span>');
}
// Обработка поиска
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 = `
<li>
<a class="dropdown-item" href="../catalog.html?category=${category.id}" title="">
<div class="category-info">
<i class="${category.icon}" style="color: ${category.color || '#2c5aa0'}"></i>
<span>${category.name}</span>
</div>
</a>
</li>
`;
dropdown.append(categoryLink);
}
});
} catch (error) {
// Ошибка загрузки категорий не критична для этой страницы
}
}
}
// Инициализация менеджера пользователей
const userManager = new UserManager();