// users.js - Управление пользователями и пользовательскими списками
let users = [];
let allUsers = [];
let usersLoadingPromise = null;
let filteredUsers = [];
let selectedUsers = [];
let editSelectedUsers = [];
let copySelectedUsers = [];
let acquaintanceSelectedUsers = []; // исполнители для ознакомления
let acquaintanceSelectedAuthor = null; // выбранный автор для ознакомления
// Переменные для пользовательских списков
let userLists = [];
let isUserListsLoading = false;
let currentEditingListId = null;
// Кэш групп пользователей
let userGroupsCache = {};
let isUsersLoading = false;
// ==================== ЗАГРУЗКА ПОЛЬЗОВАТЕЛЕЙ ====================
async function loadUsers() {
// Если загрузка уже идёт, возвращаем существующий промис
if (usersLoadingPromise) return usersLoadingPromise;
usersLoadingPromise = (async () => {
try {
const response = await fetch('/api/users');
const allUsersData = await response.json();
allUsers = allUsersData;
users = filterAssignableUsers(allUsersData);
filteredUsers = [...users];
renderUsersChecklist();
renderEditUsersChecklist();
renderCopyUsersChecklist();
populateFilterDropdowns();
// Загружаем пользовательские списки (не ждём)
loadUserLists();
} catch (error) {
console.error('Ошибка загрузки пользователей:', error);
} finally {
usersLoadingPromise = null;
}
})();
return usersLoadingPromise;
}
// ==================== ПОЛУЧЕНИЕ ГРУПП ПОЛЬЗОВАТЕЛЯ ====================
async function getUserGroups(userId) {
if (userGroupsCache[userId]) {
return userGroupsCache[userId];
}
try {
const response = await fetch(`/api2/idusers/user/${userId}/groups`);
if (!response.ok) return [];
const groups = await response.json();
userGroupsCache[userId] = groups || [];
return userGroupsCache[userId];
} catch (error) {
console.error('Ошибка получения групп пользователя:', error);
return [];
}
}
// ==================== ФИЛЬТРАЦИЯ ПОЛЬЗОВАТЕЛЕЙ ПО ПРАВАМ ====================
function filterAssignableUsers(allUsers, taskType = 'regular') {
if (!currentUser) return [];
// Для задач типа "document" – только секретари (асинхронно не получится здесь, но мы фильтруем позже)
// В текущей реализации эта функция вызывается синхронно, поэтому для специальных типов фильтрация будет в filterUsers
// Здесь оставляем базовую фильтрацию по ролям
// Администратор видит всех, кроме себя
if (currentUser.role === 'admin') {
return allUsers.filter(user => user.id !== currentUser.id);
}
if (currentUser.role === 'secretary') {
return allUsers.filter(user => user.id !== currentUser.id);
}
if (currentUser.role === 'ithelp') {
return allUsers.filter(user =>
(user.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') &&
user.id !== currentUser.id
);
}
if (currentUser.role === 'request') {
return allUsers.filter(user =>
(user.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') &&
user.id !== currentUser.id
);
}
if (currentUser.role === 'help') {
return allUsers.filter(user =>
(user.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') &&
user.id !== currentUser.id
);
}
if (currentUser.role === 'tasks') {
return allUsers.filter(user =>
(user.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') &&
user.id !== currentUser.id
);
}
if (currentUser.role === 'teacher') {
return allUsers.filter(user =>
(user.role === 'help' || user.role === 'request' || user.role === 'ithelp') &&
user.id !== currentUser.id
);
}
return [];
}
// ==================== ЗАПОЛНЕНИЕ ВЫПАДАЮЩИХ СПИСКОВ ФИЛЬТРОВ ====================
function populateFilterDropdowns() {
const creatorFilter = document.getElementById('creator-filter');
const assigneeFilter = document.getElementById('assignee-filter');
if (creatorFilter) {
creatorFilter.innerHTML = '';
}
if (assigneeFilter) {
assigneeFilter.innerHTML = '';
}
users.forEach(user => {
const option = document.createElement('option');
option.value = user.id;
option.textContent = `${user.name} (${user.login})`;
if (creatorFilter) creatorFilter.appendChild(option.cloneNode(true));
if (assigneeFilter) assigneeFilter.appendChild(option.cloneNode(true));
});
}
// ==================== ФИЛЬТРАЦИЯ ПРИ ПОИСКЕ (С УЧЁТОМ ТИПА ЗАДАЧИ) ====================
async function filterUsers() {
const search = document.getElementById('user-search')?.value.toLowerCase() || '';
const taskType = document.getElementById('task-type')?.value || 'regular';
isUsersLoading = true;
renderUsersChecklist(); // Показываем загрузку
try {
// Сначала фильтруем по поиску
let tempFiltered = users.filter(user =>
user.name.toLowerCase().includes(search) ||
user.login.toLowerCase().includes(search) ||
(user.email && user.email.toLowerCase().includes(search))
);
// Если тип задачи требует специальной группы, фильтруем по группам
const specialTypes = ['document', 'it', 'ahch', 'psychologist', 'speech_therapist', 'Social_educator', 'hr', 'certificate', 'e_journal'];
if (specialTypes.includes(taskType)) {
const groupNames = {
'document': 'Секретарь',
'it': 'ИТ специалист',
'ahch': 'АХЧ',
'psychologist': 'психолог',
'speech_therapist': 'логопед',
'Social_educator': 'Социальный педагог',
'hr': 'Диспетчер',
'certificate': 'Администрация',
'e_journal': 'Админ ЭЖ'
};
const targetGroup = groupNames[taskType];
filteredUsers = [];
for (const user of tempFiltered) {
const groups = await getUserGroups(user.id);
const hasTargetGroup = groups.some(group =>
group.name === targetGroup ||
(typeof group === 'string' && group.includes(targetGroup))
);
if (hasTargetGroup) {
filteredUsers.push(user);
}
}
} else {
filteredUsers = tempFiltered;
}
} catch (error) {
console.error('Ошибка фильтрации пользователей:', error);
filteredUsers = [];
} finally {
isUsersLoading = false;
renderUsersChecklist();
}
}
async function filterEditUsers() {
const search = document.getElementById('edit-user-search')?.value.toLowerCase() || '';
const taskId = document.getElementById('edit-task-id')?.value;
if (!taskId) return;
const task = window.tasks?.find(t => t.id == taskId);
const taskType = task ? task.task_type : 'regular';
let filtered = users.filter(user =>
user.name.toLowerCase().includes(search) ||
user.login.toLowerCase().includes(search) ||
(user.email && user.email.toLowerCase().includes(search))
);
if (taskType === 'document') {
const filteredByGroup = [];
for (const user of filtered) {
const groups = await getUserGroups(user.id);
const hasSecretaryGroup = groups.some(group =>
group.name === 'Секретарь' ||
(typeof group === 'string' && group.includes('Секретарь'))
);
if (hasSecretaryGroup) filteredByGroup.push(user);
}
filtered = filteredByGroup;
}
renderEditUsersChecklist(filtered);
}
async function filterCopyUsers() {
const search = document.getElementById('copy-user-search')?.value.toLowerCase() || '';
const taskId = document.getElementById('copy-task-id')?.value;
if (!taskId) return;
const task = window.tasks?.find(t => t.id == taskId);
const taskType = task ? task.task_type : 'regular';
let filtered = users.filter(user =>
user.name.toLowerCase().includes(search) ||
user.login.toLowerCase().includes(search) ||
(user.email && user.email.toLowerCase().includes(search))
);
if (taskType === 'document') {
const filteredByGroup = [];
for (const user of filtered) {
const groups = await getUserGroups(user.id);
const hasSecretaryGroup = groups.some(group =>
group.name === 'Секретарь' ||
(typeof group === 'string' && group.includes('Секретарь'))
);
if (hasSecretaryGroup) filteredByGroup.push(user);
}
filtered = filteredByGroup;
}
renderCopyUsersChecklist(filtered);
}
// ==================== РЕНДЕРИНГ ЧЕКБОКСОВ ПОЛЬЗОВАТЕЛЕЙ ====================
function renderUsersChecklist() {
const container = document.getElementById('users-checklist');
if (!container) return;
// Создаём структуру с двумя колонками, если её ещё нет
if (!container.querySelector('.users-two-columns')) {
container.innerHTML = `
`;
}
const leftCol = container.querySelector('.left-column');
const rightCol = container.querySelector('.right-column');
// Левая колонка – чекбоксы пользователей
if (isUsersLoading) {
leftCol.innerHTML = '⏳ Загрузка пользователей...
';
} else if (!filteredUsers || filteredUsers.length === 0) {
leftCol.innerHTML = 'Нет доступных пользователей
';
} else {
leftCol.innerHTML = filteredUsers
.filter(user => user.id !== currentUser?.id)
.map(user => `
`).join('');
}
// Правая колонка – панель списков пользователя
if (!document.getElementById('user-lists-panel')) {
const panel = document.createElement('div');
panel.id = 'user-lists-panel';
rightCol.appendChild(panel);
}
renderUserListsPanel();
}
function renderEditUsersChecklist(filtered = users) {
const container = document.getElementById('edit-users-checklist');
if (!container) return;
container.innerHTML = filtered
.filter(user => user.id !== currentUser?.id)
.map(user => `
`).join('');
}
function renderCopyUsersChecklist(filtered = users) {
const container = document.getElementById('copy-users-checklist');
if (!container) return;
container.innerHTML = filtered
.filter(user => user.id !== currentUser?.id)
.map(user => `
`).join('');
}
// ==================== УПРАВЛЕНИЕ ВЫБРАННЫМИ ПОЛЬЗОВАТЕЛЯМИ ====================
function toggleUserSelection(checkbox, userId) {
if (checkbox.checked) {
if (!selectedUsers.includes(userId)) selectedUsers.push(userId);
} else {
selectedUsers = selectedUsers.filter(id => id !== userId);
}
}
function toggleEditUserSelection(checkbox, userId) {
if (checkbox.checked) {
if (!editSelectedUsers.includes(userId)) editSelectedUsers.push(userId);
} else {
editSelectedUsers = editSelectedUsers.filter(id => id !== userId);
}
}
function toggleCopyUserSelection(checkbox, userId) {
if (checkbox.checked) {
if (!copySelectedUsers.includes(userId)) copySelectedUsers.push(userId);
} else {
copySelectedUsers = copySelectedUsers.filter(id => id !== userId);
}
}
// ==================== ФУНКЦИИ ДЛЯ РАБОТЫ СО СПИСКАМИ ПОЛЬЗОВАТЕЛЕЙ ====================
async function loadUserLists() {
if (!currentUser) return;
isUserListsLoading = true;
renderUserListsPanel();
try {
const response = await fetch('/api/user/lists');
if (response.ok) {
const lists = await response.json();
// Сервер уже отдаёт user_ids как массив, просто копируем в userIds
userLists = lists.map(list => ({
...list,
userIds: list.user_ids || [] // предполагаем, что это массив
}));
} else {
console.error('Ошибка загрузки списков:', response.status);
userLists = [];
}
} catch (error) {
console.error('Сетевая ошибка при загрузке списков:', error);
userLists = [];
} finally {
isUserListsLoading = false;
renderUserListsPanel();
}
}
async function saveUserList(listData) {
try {
const response = await fetch('/api/user/lists', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(listData)
});
if (response.ok) {
const newList = await response.json();
const transformed = {
...newList,
userIds: newList.user_ids || []
};
userLists.push(transformed);
renderUserListsPanel();
return transformed;
} else {
const err = await response.json();
alert('Ошибка создания списка: ' + (err.error || 'Неизвестная ошибка'));
return null;
}
} catch (error) {
console.error('Сетевая ошибка:', error);
alert('Не удалось сохранить список. Проверьте соединение.');
return null;
}
}
async function updateUserList(listId, listData) {
try {
const response = await fetch(`/api/user/lists/${listId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(listData)
});
if (response.ok) {
const updatedList = await response.json();
const transformed = {
...updatedList,
userIds: updatedList.user_ids || []
};
const index = userLists.findIndex(l => l.id === listId);
if (index !== -1) userLists[index] = transformed;
renderUserListsPanel();
return transformed;
} else {
const err = await response.json();
alert('Ошибка обновления списка: ' + (err.error || 'Неизвестная ошибка'));
return null;
}
} catch (error) {
console.error('Сетевая ошибка:', error);
alert('Не удалось обновить список.');
return null;
}
}
async function deleteUserList(listId) {
if (!confirm('Вы уверены, что хотите удалить этот список?')) return;
try {
const response = await fetch(`/api/user/lists/${listId}`, {
method: 'DELETE'
});
if (response.ok) {
userLists = userLists.filter(l => l.id !== listId);
renderUserListsPanel();
} else {
const err = await response.json();
alert('Ошибка удаления списка: ' + (err.error || 'Неизвестная ошибка'));
}
} catch (error) {
console.error('Сетевая ошибка:', error);
alert('Не удалось удалить список.');
}
}
function applyUserList(list) {
if (!list || !list.userIds || list.userIds.length === 0) return;
// Очищаем текущий выбор
selectedUsers = [];
// Добавляем всех пользователей из списка
list.userIds.forEach(userId => {
if (!selectedUsers.includes(userId)) {
selectedUsers.push(userId);
}
});
// Обновляем состояние чекбоксов в левой колонке
const checkboxes = document.querySelectorAll('#users-checklist .left-column input[type="checkbox"]');
checkboxes.forEach(cb => {
const userId = parseInt(cb.value);
cb.checked = list.userIds.includes(userId);
});
}
function renderUserListsPanel() {
const panel = document.getElementById('user-lists-panel');
if (!panel) return;
if (isUserListsLoading) {
panel.innerHTML = '⏳ Загрузка списков...
';
return;
}
let html = `
`;
if (userLists.length === 0) {
html += '
У вас пока нет списков
';
} else {
userLists.forEach(list => {
const memberCount = list.userIds ? list.userIds.length : 0;
// Экранируем название для безопасного использования в onclick
const listJson = JSON.stringify(list).replace(/"/g, '"');
html += `
${escapeHtml(list.name)}
(${memberCount})
`;
});
}
html += '
';
panel.innerHTML = html;
}
function openCreateListModal() {
currentEditingListId = null;
showListModal(null);
}
function openEditListModal(listId) {
const list = userLists.find(l => l.id === listId);
if (list) {
currentEditingListId = listId;
showListModal(list);
}
}
function showListModal(list) {
// Удаляем предыдущее модальное окно, если есть
const existingModal = document.getElementById('list-modal');
if (existingModal) existingModal.remove();
const modal = document.createElement('div');
modal.id = 'list-modal';
modal.className = 'modal';
modal.style.display = 'block';
const title = list ? 'Редактировать список' : 'Создать список';
const listName = list ? list.name : '';
const selectedUserIds = list ? list.userIds || [] : [];
// Генерируем чекбоксы всех пользователей (allUsers)
const usersCheckboxes = allUsers
.filter(user => user.id !== currentUser?.id)
.map(user => {
const checked = selectedUserIds.includes(user.id) ? 'checked' : '';
return `
`;
}).join('');
modal.innerHTML = `
`;
document.body.appendChild(modal);
}
function filterListUsers() {
const searchInput = document.getElementById('list-user-search');
if (!searchInput) return;
const searchTerm = searchInput.value.toLowerCase();
const container = document.getElementById('list-users-container');
if (!container) return;
const items = container.querySelectorAll('.checkbox-item');
items.forEach(item => {
const label = item.querySelector('label')?.innerText.toLowerCase() || '';
if (label.includes(searchTerm) || searchTerm === '') {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
}
function closeListModal() {
const modal = document.getElementById('list-modal');
if (modal) {
modal.style.display = 'none';
setTimeout(() => modal.remove(), 300);
}
currentEditingListId = null;
}
async function saveListFromModal() {
const nameInput = document.getElementById('list-name');
const name = nameInput.value.trim();
if (!name) {
alert('Введите название списка');
return;
}
if (name.length > 35) {
alert('Название не должно превышать 35 символов');
return;
}
// Собираем выбранные ID пользователей
const checkboxes = document.querySelectorAll('#list-modal .list-user-checkbox:checked');
const userIds = Array.from(checkboxes).map(cb => parseInt(cb.value));
const listData = { name, userIds };
if (currentEditingListId) {
await updateUserList(currentEditingListId, listData);
} else {
await saveUserList(listData);
}
closeListModal();
}
// ==================== ФУНКЦИИ ДЛЯ ОЗНАКОМЛЕНИЯ (выбор исполнителей) ====================
function renderAcquaintanceUsersChecklist(filtered = users) {
const container = document.getElementById('acquaintance-users-checklist');
if (!container) return;
container.innerHTML = filtered
.filter(user => user.id !== currentUser?.id)
.map(user => `
`).join('');
}
function toggleAcquaintanceUserSelection(checkbox, userId) {
if (checkbox.checked) {
if (!acquaintanceSelectedUsers.includes(userId)) acquaintanceSelectedUsers.push(userId);
} else {
acquaintanceSelectedUsers = acquaintanceSelectedUsers.filter(id => id !== userId);
}
}
async function filterAcquaintanceUsers() {
const search = document.getElementById('acquaintance-user-search')?.value.toLowerCase() || '';
let filtered = users.filter(user =>
user.name.toLowerCase().includes(search) ||
user.login.toLowerCase().includes(search) ||
(user.email && user.email.toLowerCase().includes(search))
);
renderAcquaintanceUsersChecklist(filtered);
}
// ==================== ФУНКЦИИ ДЛЯ ВЫБОРА АВТОРА В ЗАДАЧЕ ОЗНАКОМЛЕНИЯ ====================
function renderAcquaintanceAuthorsChecklist(filtered = users) {
const container = document.getElementById('acquaintance-authors-checklist');
if (!container) return;
container.innerHTML = filtered
.filter(user => user.id !== currentUser?.id) // можно разрешить выбирать себя, если нужно
.map(user => `
`).join('');
}
function toggleAcquaintanceAuthorSelection(radio, userId) {
acquaintanceSelectedAuthor = radio.checked ? userId : null;
}
async function filterAcquaintanceAuthors() {
const search = document.getElementById('acquaintance-author-search')?.value.toLowerCase() || '';
let filtered = users.filter(user =>
user.name.toLowerCase().includes(search) ||
user.login.toLowerCase().includes(search) ||
(user.email && user.email.toLowerCase().includes(search))
);
renderAcquaintanceAuthorsChecklist(filtered);
}
// ==================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ====================
function escapeHtml(text) {
if (!text) return '';
return String(text)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function getUserTypeLabel(user, taskType) {
const labels = {
'document': '(Секретарь)',
'it': '(ИТ специалист)',
'ahch': '(АХЧ)',
'psychologist': '(Психолог)',
'speech_therapist': '(Логопед)',
'Social_educator': '(Социальный педагог)',
'hr': '(Диспетчер)',
'certificate': '(Администрация)',
'e_journal': '(Админ ЭЖ)'
};
return labels[taskType] || '';
}
// Экспорт функций в глобальную область (для вызова из HTML)
window.loadUsers = loadUsers;
window.filterUsers = filterUsers;
window.filterEditUsers = filterEditUsers;
window.filterCopyUsers = filterCopyUsers;
window.toggleUserSelection = toggleUserSelection;
window.toggleEditUserSelection = toggleEditUserSelection;
window.toggleCopyUserSelection = toggleCopyUserSelection;
window.openCreateListModal = openCreateListModal;
window.openEditListModal = openEditListModal;
window.deleteUserList = deleteUserList;
window.applyUserList = applyUserList;
window.closeListModal = closeListModal;
window.saveListFromModal = saveListFromModal;
window.filterListUsers = filterListUsers;
// Экспорт функций для ознакомления (исполнители)
window.renderAcquaintanceUsersChecklist = renderAcquaintanceUsersChecklist;
window.toggleAcquaintanceUserSelection = toggleAcquaintanceUserSelection;
window.filterAcquaintanceUsers = filterAcquaintanceUsers;
window.acquaintanceSelectedUsers = acquaintanceSelectedUsers;
// Экспорт функций для выбора автора
window.renderAcquaintanceAuthorsChecklist = renderAcquaintanceAuthorsChecklist;
window.toggleAcquaintanceAuthorSelection = toggleAcquaintanceAuthorSelection;
window.filterAcquaintanceAuthors = filterAcquaintanceAuthors;
window.acquaintanceSelectedAuthor = acquaintanceSelectedAuthor;
// Также экспортируем переменные, которые могут понадобиться в других скриптах
window.selectedUsers = selectedUsers;
window.editSelectedUsers = editSelectedUsers;
window.copySelectedUsers = copySelectedUsers;