hz
This commit is contained in:
543
public/admin-groups.html
Normal file
543
public/admin-groups.html
Normal file
@@ -0,0 +1,543 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Управление группами пользователей</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
.group-color {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.badge-custom {
|
||||
font-size: 0.8em;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.table-hover tbody tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/admin">CRM Админ-панель</a>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin">Главная</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/profiles">Пользователи</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/admin/groups">Группы</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin-doc">Документы</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="navbar-text">
|
||||
<span id="currentUser"></span>
|
||||
<button class="btn btn-sm btn-outline-light ms-2" onclick="logout()">Выйти</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Управление группами пользователей</h5>
|
||||
<button class="btn btn-primary btn-sm" onclick="openCreateGroupModal()">
|
||||
<i class="bi bi-plus-circle"></i> Создать группу
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Описание</th>
|
||||
<th>Участников</th>
|
||||
<th>Может согласовывать</th>
|
||||
<th>Цвет</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="groupsTableBody">
|
||||
<!-- Группы будут загружены сюда -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Пользователи и их группы</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Пользователь</th>
|
||||
<th>Роль</th>
|
||||
<th>Группы</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="usersTableBody">
|
||||
<!-- Пользователи будут загружены сюда -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Модальное окно создания/редактирования группы -->
|
||||
<div class="modal fade" id="groupModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="groupModalTitle">Создать группу</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="groupForm">
|
||||
<input type="hidden" id="groupId">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Название группы *</label>
|
||||
<input type="text" class="form-control" id="groupName" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Описание</label>
|
||||
<textarea class="form-control" id="groupDescription" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Цвет группы</label>
|
||||
<input type="color" class="form-control form-control-color" id="groupColor" value="#3498db">
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="canApproveDocuments">
|
||||
<label class="form-check-label">Может согласовывать документы</label>
|
||||
<small class="form-text text-muted d-block">
|
||||
Пользователи этой группы будут доступны для выбора при создании задач согласования документов
|
||||
</small>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveGroup()">Сохранить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Модальное окно управления группами пользователя -->
|
||||
<div class="modal fade" id="userGroupsModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Группы пользователя: <span id="userName"></span></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="userGroupsList">
|
||||
<!-- Группы пользователя будут загружены сюда -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
let currentUser = null;
|
||||
let groups = [];
|
||||
let users = [];
|
||||
|
||||
// Загрузка данных при загрузке страницы
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await checkAuth();
|
||||
await loadGroups();
|
||||
await loadUsersWithGroups();
|
||||
});
|
||||
|
||||
// Проверка авторизации
|
||||
async function checkAuth() {
|
||||
try {
|
||||
const response = await fetch('/api/user');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
currentUser = data.user;
|
||||
|
||||
if (currentUser.role !== 'admin') {
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('currentUser').textContent =
|
||||
`${currentUser.name} (${currentUser.role})`;
|
||||
} else {
|
||||
window.location.href = '/';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка проверки авторизации:', error);
|
||||
window.location.href = '/';
|
||||
}
|
||||
}
|
||||
|
||||
// Выход из системы
|
||||
function logout() {
|
||||
fetch('/api/logout', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
.then(() => {
|
||||
window.location.href = '/';
|
||||
});
|
||||
}
|
||||
|
||||
// Загрузка групп
|
||||
async function loadGroups() {
|
||||
try {
|
||||
const response = await fetch('/api/groups');
|
||||
if (response.ok) {
|
||||
groups = await response.json();
|
||||
renderGroupsTable();
|
||||
} else {
|
||||
showError('Ошибка загрузки групп');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки групп:', error);
|
||||
showError('Ошибка загрузки групп');
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка пользователей с группами
|
||||
async function loadUsersWithGroups() {
|
||||
try {
|
||||
const response = await fetch('/api/users-with-groups');
|
||||
if (response.ok) {
|
||||
users = await response.json();
|
||||
renderUsersTable();
|
||||
} else {
|
||||
showError('Ошибка загрузки пользователей');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки пользователей:', error);
|
||||
showError('Ошибка загрузки пользователей');
|
||||
}
|
||||
}
|
||||
|
||||
// Отображение таблицы групп
|
||||
function renderGroupsTable() {
|
||||
const tbody = document.getElementById('groupsTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (groups.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted">
|
||||
Нет созданных групп
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
groups.forEach(group => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>
|
||||
<span class="group-color" style="background-color: ${group.color}"></span>
|
||||
<strong>${group.name}</strong>
|
||||
</td>
|
||||
<td>${group.description || '-'}</td>
|
||||
<td>
|
||||
<span class="badge bg-primary">${group.member_count || 0}</span>
|
||||
</td>
|
||||
<td>
|
||||
${group.can_approve_documents
|
||||
? '<span class="badge bg-success">Да</span>'
|
||||
: '<span class="badge bg-secondary">Нет</span>'}
|
||||
</td>
|
||||
<td>
|
||||
<span class="group-color" style="background-color: ${group.color}"></span>
|
||||
${group.color}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary me-1" onclick="editGroup(${group.id})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteGroup(${group.id})">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Отображение таблицы пользователей
|
||||
function renderUsersTable() {
|
||||
const tbody = document.getElementById('usersTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (users.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted">
|
||||
Нет пользователей
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
users.forEach(user => {
|
||||
const groupsHtml = user.group_names.length > 0
|
||||
? user.group_names.map(name =>
|
||||
`<span class="badge bg-info me-1">${name}</span>`
|
||||
).join('')
|
||||
: '<span class="text-muted">Нет групп</span>';
|
||||
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>
|
||||
<div><strong>${user.name}</strong></div>
|
||||
<small class="text-muted">${user.login} • ${user.email}</small>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge ${user.role === 'admin' ? 'bg-danger' : 'bg-secondary'}">
|
||||
${user.role === 'admin' ? 'Администратор' : 'Учитель'}
|
||||
</span>
|
||||
</td>
|
||||
<td>${groupsHtml}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="manageUserGroups(${user.id}, '${user.name}')">
|
||||
<i class="bi bi-person-gear"></i> Управление группами
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Открытие модального окна создания группы
|
||||
function openCreateGroupModal() {
|
||||
document.getElementById('groupModalTitle').textContent = 'Создать группу';
|
||||
document.getElementById('groupId').value = '';
|
||||
document.getElementById('groupName').value = '';
|
||||
document.getElementById('groupDescription').value = '';
|
||||
document.getElementById('groupColor').value = '#3498db';
|
||||
document.getElementById('canApproveDocuments').checked = false;
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('groupModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
// Редактирование группы
|
||||
async function editGroup(groupId) {
|
||||
const group = groups.find(g => g.id === groupId);
|
||||
if (!group) return;
|
||||
|
||||
document.getElementById('groupModalTitle').textContent = 'Редактировать группу';
|
||||
document.getElementById('groupId').value = group.id;
|
||||
document.getElementById('groupName').value = group.name;
|
||||
document.getElementById('groupDescription').value = group.description || '';
|
||||
document.getElementById('groupColor').value = group.color;
|
||||
document.getElementById('canApproveDocuments').checked = !!group.can_approve_documents;
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('groupModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
// Сохранение группы
|
||||
async function saveGroup() {
|
||||
const groupId = document.getElementById('groupId').value;
|
||||
const groupData = {
|
||||
name: document.getElementById('groupName').value.trim(),
|
||||
description: document.getElementById('groupDescription').value.trim(),
|
||||
color: document.getElementById('groupColor').value,
|
||||
can_approve_documents: document.getElementById('canApproveDocuments').checked
|
||||
};
|
||||
|
||||
if (!groupData.name) {
|
||||
showError('Введите название группы');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = groupId
|
||||
? `/api/groups/${groupId}`
|
||||
: '/api/groups';
|
||||
const method = groupId ? 'PUT' : 'POST';
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(groupData)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
showSuccess(data.message || 'Группа сохранена');
|
||||
|
||||
// Закрываем модальное окно
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('groupModal'));
|
||||
modal.hide();
|
||||
|
||||
// Перезагружаем данные
|
||||
await loadGroups();
|
||||
await loadUsersWithGroups();
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
showError(errorData.error || 'Ошибка сохранения группы');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка сохранения группы:', error);
|
||||
showError('Ошибка сохранения группы');
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление группы
|
||||
async function deleteGroup(groupId) {
|
||||
if (!confirm('Вы уверены, что хотите удалить эту группу? Все связи с пользователями будут удалены.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/groups/${groupId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
showSuccess(data.message || 'Группа удалена');
|
||||
|
||||
// Перезагружаем данные
|
||||
await loadGroups();
|
||||
await loadUsersWithGroups();
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
showError(errorData.error || 'Ошибка удаления группы');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления группы:', error);
|
||||
showError('Ошибка удаления группы');
|
||||
}
|
||||
}
|
||||
|
||||
// Управление группами пользователя
|
||||
async function manageUserGroups(userId, userName) {
|
||||
document.getElementById('userName').textContent = userName;
|
||||
|
||||
try {
|
||||
// Получаем доступные группы для пользователя
|
||||
const response = await fetch(`/api/users/${userId}/available-groups`);
|
||||
if (response.ok) {
|
||||
const availableGroups = await response.json();
|
||||
|
||||
// Создаем список чекбоксов
|
||||
let groupsHtml = '';
|
||||
availableGroups.forEach(group => {
|
||||
groupsHtml += `
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="group_${group.id}"
|
||||
${group.is_member ? 'checked' : ''}
|
||||
onchange="toggleUserGroup(${userId}, ${group.id}, this.checked)">
|
||||
<label class="form-check-label" for="group_${group.id}">
|
||||
<span class="group-color" style="background-color: ${group.color}"></span>
|
||||
${group.name}
|
||||
${group.can_approve_documents
|
||||
? '<span class="badge bg-success badge-custom ms-1">Согласование</span>'
|
||||
: ''}
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
document.getElementById('userGroupsList').innerHTML = groupsHtml ||
|
||||
'<p class="text-muted">Нет доступных групп</p>';
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('userGroupsModal'));
|
||||
modal.show();
|
||||
} else {
|
||||
showError('Ошибка загрузки групп пользователя');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки групп:', error);
|
||||
showError('Ошибка загрузки групп');
|
||||
}
|
||||
}
|
||||
|
||||
// Переключение группы пользователя
|
||||
async function toggleUserGroup(userId, groupId, isChecked) {
|
||||
try {
|
||||
const response = await fetch(`/api/users/${userId}/groups`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
groupIds: isChecked
|
||||
? [...document.querySelectorAll('#userGroupsList input:checked')]
|
||||
.map(input => parseInt(input.id.split('_')[1]))
|
||||
: [...document.querySelectorAll('#userGroupsList input:checked')]
|
||||
.filter(input => parseInt(input.id.split('_')[1]) !== groupId)
|
||||
.map(input => parseInt(input.id.split('_')[1]))
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
showError(errorData.error || 'Ошибка обновления групп');
|
||||
// Возвращаем чекбокс в предыдущее состояние
|
||||
const checkbox = document.getElementById(`group_${groupId}`);
|
||||
checkbox.checked = !isChecked;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка обновления групп:', error);
|
||||
showError('Ошибка обновления групп');
|
||||
// Возвращаем чекбокс в предыдущее состояние
|
||||
const checkbox = document.getElementById(`group_${groupId}`);
|
||||
checkbox.checked = !isChecked;
|
||||
}
|
||||
}
|
||||
|
||||
// Вспомогательные функции для уведомлений
|
||||
function showSuccess(message) {
|
||||
alert(message); // Можно заменить на красивый toast
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
alert('Ошибка: ' + message); // Можно заменить на красивый toast
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user