// users.js - Управление пользователями и пользовательскими списками let users = []; let allUsers = []; let usersLoadingPromise = null; let filteredUsers = []; let selectedUsers = []; let editSelectedUsers = []; let copySelectedUsers = []; // Переменные для пользовательских списков 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 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.selectedUsers = selectedUsers; window.editSelectedUsers = editSelectedUsers; window.copySelectedUsers = copySelectedUsers;