список пользователя
This commit is contained in:
179
api-user-lists.js
Normal file
179
api-user-lists.js
Normal file
@@ -0,0 +1,179 @@
|
||||
// api-user-lists.js - API для управления пользовательскими списками
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
module.exports = function(app, db) {
|
||||
// Middleware для проверки аутентификации
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session || !req.session.user) {
|
||||
return res.status(401).json({ error: 'Требуется аутентификация' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// GET /api/user/lists – получить все списки текущего пользователя
|
||||
router.get('/api/user/lists', requireAuth, (req, res) => {
|
||||
const userId = req.session.user.id;
|
||||
|
||||
db.all(
|
||||
'SELECT id, name, user_ids, created_at, updated_at FROM user_lists WHERE user_id = ? ORDER BY created_at DESC',
|
||||
[userId],
|
||||
(err, rows) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка получения списков:', err);
|
||||
return res.status(500).json({ error: 'Ошибка получения списков' });
|
||||
}
|
||||
|
||||
// Преобразуем user_ids из JSON в массив
|
||||
const lists = (rows || []).map(row => ({
|
||||
...row,
|
||||
user_ids: JSON.parse(row.user_ids || '[]')
|
||||
}));
|
||||
|
||||
res.json(lists);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// POST /api/user/lists – создать новый список
|
||||
router.post('/api/user/lists', requireAuth, (req, res) => {
|
||||
const userId = req.session.user.id;
|
||||
const { name, userIds } = req.body;
|
||||
|
||||
if (!name || name.trim() === '') {
|
||||
return res.status(400).json({ error: 'Название списка обязательно' });
|
||||
}
|
||||
if (name.length > 35) {
|
||||
return res.status(400).json({ error: 'Название не должно превышать 35 символов' });
|
||||
}
|
||||
if (!Array.isArray(userIds)) {
|
||||
return res.status(400).json({ error: 'userIds должен быть массивом' });
|
||||
}
|
||||
|
||||
const user_ids_json = JSON.stringify(userIds);
|
||||
|
||||
db.run(
|
||||
'INSERT INTO user_lists (user_id, name, user_ids) VALUES (?, ?, ?)',
|
||||
[userId, name.trim(), user_ids_json],
|
||||
function(err) {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка создания списка:', err);
|
||||
return res.status(500).json({ error: 'Ошибка создания списка' });
|
||||
}
|
||||
|
||||
// Возвращаем созданный список
|
||||
db.get(
|
||||
'SELECT id, name, user_ids, created_at, updated_at FROM user_lists WHERE id = ?',
|
||||
[this.lastID],
|
||||
(err, row) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Список создан, но ошибка получения' });
|
||||
}
|
||||
const newList = {
|
||||
...row,
|
||||
user_ids: JSON.parse(row.user_ids || '[]')
|
||||
};
|
||||
res.status(201).json(newList);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// PUT /api/user/lists/:id – обновить список
|
||||
router.put('/api/user/lists/:id', requireAuth, (req, res) => {
|
||||
const listId = req.params.id;
|
||||
const userId = req.session.user.id;
|
||||
const { name, userIds } = req.body;
|
||||
|
||||
// Проверяем, принадлежит ли список пользователю
|
||||
db.get('SELECT id FROM user_lists WHERE id = ? AND user_id = ?', [listId, userId], (err, list) => {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка проверки списка:', err);
|
||||
return res.status(500).json({ error: 'Ошибка доступа' });
|
||||
}
|
||||
if (!list) {
|
||||
return res.status(404).json({ error: 'Список не найден' });
|
||||
}
|
||||
|
||||
const updates = [];
|
||||
const params = [];
|
||||
|
||||
if (name !== undefined) {
|
||||
if (name.trim() === '') {
|
||||
return res.status(400).json({ error: 'Название не может быть пустым' });
|
||||
}
|
||||
if (name.length > 35) {
|
||||
return res.status(400).json({ error: 'Название не должно превышать 35 символов' });
|
||||
}
|
||||
updates.push('name = ?');
|
||||
params.push(name.trim());
|
||||
}
|
||||
|
||||
if (userIds !== undefined) {
|
||||
if (!Array.isArray(userIds)) {
|
||||
return res.status(400).json({ error: 'userIds должен быть массивом' });
|
||||
}
|
||||
updates.push('user_ids = ?');
|
||||
params.push(JSON.stringify(userIds));
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return res.status(400).json({ error: 'Нет данных для обновления' });
|
||||
}
|
||||
|
||||
updates.push('updated_at = CURRENT_TIMESTAMP');
|
||||
params.push(listId);
|
||||
|
||||
const query = `UPDATE user_lists SET ${updates.join(', ')} WHERE id = ?`;
|
||||
|
||||
db.run(query, params, function(err) {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка обновления списка:', err);
|
||||
return res.status(500).json({ error: 'Ошибка обновления списка' });
|
||||
}
|
||||
|
||||
// Возвращаем обновлённый список
|
||||
db.get(
|
||||
'SELECT id, name, user_ids, created_at, updated_at FROM user_lists WHERE id = ?',
|
||||
[listId],
|
||||
(err, row) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Список обновлён, но ошибка получения' });
|
||||
}
|
||||
const updatedList = {
|
||||
...row,
|
||||
user_ids: JSON.parse(row.user_ids || '[]')
|
||||
};
|
||||
res.json(updatedList);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// DELETE /api/user/lists/:id – удалить список
|
||||
router.delete('/api/user/lists/:id', requireAuth, (req, res) => {
|
||||
const listId = req.params.id;
|
||||
const userId = req.session.user.id;
|
||||
|
||||
db.run(
|
||||
'DELETE FROM user_lists WHERE id = ? AND user_id = ?',
|
||||
[listId, userId],
|
||||
function(err) {
|
||||
if (err) {
|
||||
console.error('❌ Ошибка удаления списка:', err);
|
||||
return res.status(500).json({ error: 'Ошибка удаления списка' });
|
||||
}
|
||||
if (this.changes === 0) {
|
||||
return res.status(404).json({ error: 'Список не найден' });
|
||||
}
|
||||
res.json({ success: true, message: 'Список удалён' });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Подключаем роутер к приложению
|
||||
app.use(router);
|
||||
console.log('✅ API для пользовательских списков подключено');
|
||||
};
|
||||
20
database.js
20
database.js
@@ -540,6 +540,16 @@ db.run(`CREATE TABLE IF NOT EXISTS task_chat_reads (
|
||||
UNIQUE(message_id, user_id),
|
||||
FOREIGN KEY (message_id) REFERENCES task_chat_messages (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)`);
|
||||
// Таблица для пользовательских списков
|
||||
db.run(`CREATE TABLE IF NOT EXISTS user_lists (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
user_ids TEXT NOT NULL, -- JSON массив ID пользователей
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)`);
|
||||
console.log('✅ Таблица для сообщений чата задач созданы');
|
||||
// Создаем индексы для улучшения производительности
|
||||
@@ -1323,6 +1333,16 @@ async function createPostgresTables() {
|
||||
refusal_reason TEXT
|
||||
)
|
||||
`);
|
||||
await client.query(`
|
||||
CREATE TABLE IF NOT EXISTS user_lists (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(35) NOT NULL,
|
||||
user_ids TEXT NOT NULL, -- JSON массив
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
console.log('✅ Все таблицы PostgreSQL созданы/проверены');
|
||||
|
||||
|
||||
120
public/style.css
120
public/style.css
@@ -4949,4 +4949,124 @@ button.btn-primary {
|
||||
color: #aaa;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* Стили для панели пользовательских списков */
|
||||
.user-lists-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.user-lists-header h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.btn-create-list {
|
||||
background: #27ae60;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 4px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn-create-list:hover {
|
||||
background: #219a52;
|
||||
}
|
||||
|
||||
.user-lists-container {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.user-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 5px;
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.user-list-item:hover {
|
||||
background: #f1f3f5;
|
||||
}
|
||||
|
||||
.list-info {
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-name {
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.list-count {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
background: #e9ecef;
|
||||
padding: 2px 6px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.list-actions {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.list-edit-btn, .list-delete-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding: 2px;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.list-edit-btn:hover {
|
||||
opacity: 1;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.list-delete-btn:hover {
|
||||
opacity: 1;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.no-lists {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Модальное окно для списка */
|
||||
#list-modal .users-checklist-scroll {
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
797
public/users.js
797
public/users.js
@@ -1,45 +1,63 @@
|
||||
// users.js - Управление пользователями
|
||||
// users.js - Управление пользователями и пользовательскими списками
|
||||
|
||||
let users = [];
|
||||
let allUsers = [];
|
||||
let usersLoadingPromise = null;
|
||||
let filteredUsers = [];
|
||||
let selectedUsers = [];
|
||||
let editSelectedUsers = [];
|
||||
let copySelectedUsers = [];
|
||||
// добавить переменную для отслеживания загрузки
|
||||
let isUsersLoading = false;
|
||||
|
||||
// Добавьте переменную для хранения групп пользователей
|
||||
// Переменные для пользовательских списков
|
||||
let userLists = [];
|
||||
let isUserListsLoading = false;
|
||||
let currentEditingListId = null;
|
||||
|
||||
// Кэш групп пользователей
|
||||
let userGroupsCache = {};
|
||||
|
||||
let isUsersLoading = false;
|
||||
|
||||
// ==================== ЗАГРУЗКА ПОЛЬЗОВАТЕЛЕЙ ====================
|
||||
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const response = await fetch('/api/users');
|
||||
const allUsersData = await response.json();
|
||||
//users = await response.json();
|
||||
// Сохраняем всех пользователей
|
||||
allUsers = allUsersData;
|
||||
// Фильтруем пользователей в зависимости от прав текущего пользователя
|
||||
users = filterAssignableUsers(allUsersData);
|
||||
filteredUsers = [...users];
|
||||
renderUsersChecklist();
|
||||
renderEditUsersChecklist();
|
||||
renderCopyUsersChecklist();
|
||||
populateFilterDropdowns();
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки пользователей:', error);
|
||||
}
|
||||
// Если загрузка уже идёт, возвращаем существующий промис
|
||||
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];
|
||||
@@ -49,47 +67,16 @@ async function getUserGroups(userId) {
|
||||
}
|
||||
}
|
||||
|
||||
// Обновите функцию filterAssignableUsers
|
||||
// ==================== ФИЛЬТРАЦИЯ ПОЛЬЗОВАТЕЛЕЙ ПО ПРАВАМ ====================
|
||||
|
||||
function filterAssignableUsers(allUsers, taskType = 'regular') {
|
||||
if (!currentUser) return [];
|
||||
|
||||
// Для задач типа "document" - только пользователи из группы "Секретарь"
|
||||
if (taskType === 'document') {
|
||||
return allUsers.filter(async (user) => {
|
||||
if (user.id === currentUser.id) return false;
|
||||
|
||||
// Получаем группы пользователя
|
||||
const groups = await getUserGroups(user.id);
|
||||
|
||||
// Проверяем, есть ли группа "Секретарь"
|
||||
const hasSecretaryGroup = groups.some(group =>
|
||||
group.name === 'Секретарь' ||
|
||||
(typeof group === 'string' && group.includes('Секретарь'))
|
||||
);
|
||||
|
||||
return hasSecretaryGroup;
|
||||
});
|
||||
}
|
||||
// Для задач типа "it" - только пользователи из группы "ИТ специалист"
|
||||
if (taskType === 'it') {
|
||||
return allUsers.filter(async (user) => {
|
||||
if (user.id === currentUser.id) return false;
|
||||
|
||||
// Получаем группы пользователя
|
||||
const groups = await getUserGroups(user.id);
|
||||
|
||||
// Проверяем, есть ли группа "Секретарь"
|
||||
const hasSecretaryGroup = groups.some(group =>
|
||||
group.name === 'ИТ специалист' ||
|
||||
(typeof group === 'string' && group.includes('ИТ специалист'))
|
||||
);
|
||||
|
||||
return hasSecretaryGroup;
|
||||
});
|
||||
}
|
||||
|
||||
// Для других типов задач - обычная фильтрация
|
||||
// Администратор видит всех пользователей
|
||||
|
||||
// Для задач типа "document" – только секретари (асинхронно не получится здесь, но мы фильтруем позже)
|
||||
// В текущей реализации эта функция вызывается синхронно, поэтому для специальных типов фильтрация будет в filterUsers
|
||||
// Здесь оставляем базовую фильтрацию по ролям
|
||||
|
||||
// Администратор видит всех, кроме себя
|
||||
if (currentUser.role === 'admin') {
|
||||
return allUsers.filter(user => user.id !== currentUser.id);
|
||||
}
|
||||
@@ -97,81 +84,82 @@ function filterAssignableUsers(allUsers, taskType = 'regular') {
|
||||
return allUsers.filter(user => user.id !== currentUser.id);
|
||||
}
|
||||
if (currentUser.role === 'ithelp') {
|
||||
return allUsers.filter(user =>
|
||||
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 =>
|
||||
return allUsers.filter(user =>
|
||||
(user.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') &&
|
||||
user.id !== currentUser.id
|
||||
);
|
||||
}
|
||||
// tasks видит учителей и других tasks
|
||||
if (currentUser.role === 'help') {
|
||||
return allUsers.filter(user =>
|
||||
return allUsers.filter(user =>
|
||||
(user.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') &&
|
||||
user.id !== currentUser.id
|
||||
);
|
||||
}
|
||||
// tasks видит учителей и других tasks
|
||||
if (currentUser.role === 'tasks') {
|
||||
return allUsers.filter(user =>
|
||||
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') &&
|
||||
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');
|
||||
|
||||
creatorFilter.innerHTML = '<option value="">Все заказчики</option>';
|
||||
assigneeFilter.innerHTML = '<option value="">Все исполнители</option>';
|
||||
|
||||
|
||||
if (creatorFilter) {
|
||||
creatorFilter.innerHTML = '<option value="">Все заказчики</option>';
|
||||
}
|
||||
if (assigneeFilter) {
|
||||
assigneeFilter.innerHTML = '<option value="">Все исполнители</option>';
|
||||
}
|
||||
|
||||
users.forEach(user => {
|
||||
const creatorOption = document.createElement('option');
|
||||
creatorOption.value = user.id;
|
||||
creatorOption.textContent = `${user.name} (${user.login})`;
|
||||
creatorFilter.appendChild(creatorOption.cloneNode(true));
|
||||
|
||||
const assigneeOption = creatorOption.cloneNode(true);
|
||||
assigneeFilter.appendChild(assigneeOption);
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
// Обновите функцию filterUsers с учетом типа задачи
|
||||
// ==================== ФИЛЬТРАЦИЯ ПРИ ПОИСКЕ (С УЧЁТОМ ТИПА ЗАДАЧИ) ====================
|
||||
|
||||
async function filterUsers() {
|
||||
const search = document.getElementById('user-search')?.value.toLowerCase() || '';
|
||||
const taskType = document.getElementById('task-type')?.value || 'regular';
|
||||
|
||||
|
||||
isUsersLoading = true;
|
||||
renderUsersChecklist(); // Показываем загрузку
|
||||
|
||||
|
||||
try {
|
||||
if (taskType === 'document' || taskType === 'it' || taskType === 'ahch' ||
|
||||
taskType === 'psychologist' || taskType === 'speech_therapist' || taskType === 'Social_educator' ||
|
||||
taskType === 'hr' || taskType === 'certificate' || taskType === 'e_journal') {
|
||||
|
||||
// Фильтруем по поиску
|
||||
let tempFiltered = users.filter(user =>
|
||||
user.name.toLowerCase().includes(search) ||
|
||||
user.login.toLowerCase().includes(search) ||
|
||||
user.email.toLowerCase().includes(search)
|
||||
);
|
||||
|
||||
// Затем проверяем группы
|
||||
filteredUsers = [];
|
||||
// Сначала фильтруем по поиску
|
||||
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': 'ИТ специалист',
|
||||
@@ -183,27 +171,21 @@ async function filterUsers() {
|
||||
'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 ||
|
||||
const hasTargetGroup = groups.some(group =>
|
||||
group.name === targetGroup ||
|
||||
(typeof group === 'string' && group.includes(targetGroup))
|
||||
);
|
||||
|
||||
if (hasTargetGroup) {
|
||||
filteredUsers.push(user);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Обычная фильтрация
|
||||
filteredUsers = users.filter(user =>
|
||||
user.name.toLowerCase().includes(search) ||
|
||||
user.login.toLowerCase().includes(search) ||
|
||||
user.email.toLowerCase().includes(search)
|
||||
);
|
||||
filteredUsers = tempFiltered;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка фильтрации пользователей:', error);
|
||||
@@ -215,110 +197,483 @@ async function filterUsers() {
|
||||
}
|
||||
|
||||
async function filterEditUsers() {
|
||||
const search = document.getElementById('edit-user-search').value.toLowerCase();
|
||||
const task = tasks.find(t => t.id === document.getElementById('edit-task-id').value);
|
||||
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 = [];
|
||||
|
||||
|
||||
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') {
|
||||
// Для задач типа "document" - только секретари
|
||||
let tempFiltered = users.filter(user =>
|
||||
user.name.toLowerCase().includes(search) ||
|
||||
user.login.toLowerCase().includes(search) ||
|
||||
user.email.toLowerCase().includes(search)
|
||||
);
|
||||
|
||||
filtered = [];
|
||||
for (const user of tempFiltered) {
|
||||
const filteredByGroup = [];
|
||||
for (const user of filtered) {
|
||||
const groups = await getUserGroups(user.id);
|
||||
const hasSecretaryGroup = groups.some(group =>
|
||||
group.name === 'Секретарь' ||
|
||||
const hasSecretaryGroup = groups.some(group =>
|
||||
group.name === 'Секретарь' ||
|
||||
(typeof group === 'string' && group.includes('Секретарь'))
|
||||
);
|
||||
|
||||
if (hasSecretaryGroup) {
|
||||
filtered.push(user);
|
||||
}
|
||||
if (hasSecretaryGroup) filteredByGroup.push(user);
|
||||
}
|
||||
} else {
|
||||
filtered = users.filter(user =>
|
||||
user.name.toLowerCase().includes(search) ||
|
||||
user.login.toLowerCase().includes(search) ||
|
||||
user.email.toLowerCase().includes(search)
|
||||
);
|
||||
filtered = filteredByGroup;
|
||||
}
|
||||
|
||||
|
||||
renderEditUsersChecklist(filtered);
|
||||
}
|
||||
|
||||
async function filterCopyUsers() {
|
||||
const search = document.getElementById('copy-user-search').value.toLowerCase();
|
||||
const taskId = document.getElementById('copy-task-id').value;
|
||||
const task = tasks.find(t => t.id === taskId);
|
||||
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 = [];
|
||||
|
||||
|
||||
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') {
|
||||
// Для задач типа "document" - только секретари
|
||||
let tempFiltered = users.filter(user =>
|
||||
user.name.toLowerCase().includes(search) ||
|
||||
user.login.toLowerCase().includes(search) ||
|
||||
user.email.toLowerCase().includes(search)
|
||||
);
|
||||
|
||||
filtered = [];
|
||||
for (const user of tempFiltered) {
|
||||
const filteredByGroup = [];
|
||||
for (const user of filtered) {
|
||||
const groups = await getUserGroups(user.id);
|
||||
const hasSecretaryGroup = groups.some(group =>
|
||||
group.name === 'Секретарь' ||
|
||||
const hasSecretaryGroup = groups.some(group =>
|
||||
group.name === 'Секретарь' ||
|
||||
(typeof group === 'string' && group.includes('Секретарь'))
|
||||
);
|
||||
|
||||
if (hasSecretaryGroup) {
|
||||
filtered.push(user);
|
||||
}
|
||||
if (hasSecretaryGroup) filteredByGroup.push(user);
|
||||
}
|
||||
} else {
|
||||
filtered = users.filter(user =>
|
||||
user.name.toLowerCase().includes(search) ||
|
||||
user.login.toLowerCase().includes(search) ||
|
||||
user.email.toLowerCase().includes(search)
|
||||
);
|
||||
filtered = filteredByGroup;
|
||||
}
|
||||
|
||||
|
||||
renderCopyUsersChecklist(filtered);
|
||||
}
|
||||
|
||||
// ==================== РЕНДЕРИНГ ЧЕКБОКСОВ ПОЛЬЗОВАТЕЛЕЙ ====================
|
||||
|
||||
function renderUsersChecklist() {
|
||||
const container = document.getElementById('users-checklist');
|
||||
|
||||
if (!container) return;
|
||||
|
||||
// Создаём структуру с двумя колонками, если её ещё нет
|
||||
if (!container.querySelector('.users-two-columns')) {
|
||||
container.innerHTML = `
|
||||
<div class="users-two-columns" style="display: flex; gap: 20px;">
|
||||
<div class="left-column" style="flex: 1; min-width: 0;"></div>
|
||||
<div class="right-column" style="flex: 1; min-width: 0;"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const leftCol = container.querySelector('.left-column');
|
||||
const rightCol = container.querySelector('.right-column');
|
||||
|
||||
// Левая колонка – чекбоксы пользователей
|
||||
if (isUsersLoading) {
|
||||
container.innerHTML = '<div class="loading-spinner">⏳ Загрузка пользователей...</div>';
|
||||
return;
|
||||
leftCol.innerHTML = '<div class="loading-spinner">⏳ Загрузка пользователей...</div>';
|
||||
} else if (!filteredUsers || filteredUsers.length === 0) {
|
||||
leftCol.innerHTML = '<div class="no-users">Нет доступных пользователей</div>';
|
||||
} else {
|
||||
leftCol.innerHTML = filteredUsers
|
||||
.filter(user => user.id !== currentUser?.id)
|
||||
.map(user => `
|
||||
<div class="checkbox-item">
|
||||
<label>
|
||||
<input type="checkbox" name="assignedUsers" value="${user.id}"
|
||||
onchange="toggleUserSelection(this, ${user.id})"
|
||||
${selectedUsers.includes(user.id) ? 'checked' : ''}>
|
||||
${escapeHtml(user.name)}
|
||||
${getUserTypeLabel(user, document.getElementById('task-type')?.value)}
|
||||
</label>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
if (!filteredUsers || filteredUsers.length === 0) {
|
||||
container.innerHTML = '<div class="no-users">Нет доступных пользователей</div>';
|
||||
return;
|
||||
|
||||
// Правая колонка – панель списков пользователя
|
||||
if (!document.getElementById('user-lists-panel')) {
|
||||
const panel = document.createElement('div');
|
||||
panel.id = 'user-lists-panel';
|
||||
rightCol.appendChild(panel);
|
||||
}
|
||||
|
||||
container.innerHTML = filteredUsers
|
||||
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 => `
|
||||
<div class="checkbox-item">
|
||||
<label>
|
||||
<input type="checkbox" name="assignedUsers" value="${user.id}"
|
||||
onchange="toggleUserSelection(this, ${user.id})"
|
||||
${selectedUsers.includes(user.id) ? 'checked' : ''}>
|
||||
${user.name}
|
||||
${getUserTypeLabel(user, document.getElementById('task-type')?.value)}
|
||||
</label>
|
||||
</div>
|
||||
`).join('');
|
||||
<div class="checkbox-item">
|
||||
<label>
|
||||
<input type="checkbox" name="assignedUsers" value="${user.id}"
|
||||
onchange="toggleEditUserSelection(this, ${user.id})"
|
||||
${editSelectedUsers.includes(user.id) ? 'checked' : ''}>
|
||||
${escapeHtml(user.name)} (${user.email})
|
||||
${user.auth_type === 'ldap' ? '<small style="color: #666;"> - LDAP</small>' : ''}
|
||||
</label>
|
||||
</div>
|
||||
`).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 => `
|
||||
<div class="checkbox-item">
|
||||
<label>
|
||||
<input type="checkbox" name="assignedUsers" value="${user.id}"
|
||||
onchange="toggleCopyUserSelection(this, ${user.id})"
|
||||
${copySelectedUsers.includes(user.id) ? 'checked' : ''}>
|
||||
${escapeHtml(user.name)} (${user.email})
|
||||
${user.auth_type === 'ldap' ? '<small style="color: #666;"> - LDAP</small>' : ''}
|
||||
</label>
|
||||
</div>
|
||||
`).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 = '<div class="loading-spinner">⏳ Загрузка списков...</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<div class="user-lists-header">
|
||||
<h4>Мои списки</h4>
|
||||
<button class="btn-create-list" onclick="openCreateListModal()">➕ Создать</button>
|
||||
</div>
|
||||
<div class="user-lists-container">
|
||||
`;
|
||||
|
||||
if (userLists.length === 0) {
|
||||
html += '<p class="no-lists">У вас пока нет списков</p>';
|
||||
} else {
|
||||
userLists.forEach(list => {
|
||||
const memberCount = list.userIds ? list.userIds.length : 0;
|
||||
// Экранируем название для безопасного использования в onclick
|
||||
const listJson = JSON.stringify(list).replace(/"/g, '"');
|
||||
html += `
|
||||
<div class="user-list-item" data-list-id="${list.id}">
|
||||
<div class="list-info" onclick="applyUserList(${listJson})">
|
||||
<span class="list-name">${escapeHtml(list.name)}</span>
|
||||
<span class="list-count">(${memberCount})</span>
|
||||
</div>
|
||||
<div class="list-actions">
|
||||
<button class="list-edit-btn" onclick="event.stopPropagation(); openEditListModal(${list.id})" title="Редактировать">✏️</button>
|
||||
<button class="list-delete-btn" onclick="event.stopPropagation(); deleteUserList(${list.id})" title="Удалить">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
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 `
|
||||
<div class="checkbox-item">
|
||||
<label>
|
||||
<input type="checkbox" class="list-user-checkbox" value="${user.id}" ${checked}>
|
||||
${escapeHtml(user.name)} (${escapeHtml(user.login)})
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h3>${title}</h3>
|
||||
<span class="close" onclick="closeListModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="list-name">Название списка (до 35 символов):</label>
|
||||
<input type="text" id="list-name" maxlength="35" value="${escapeHtml(listName)}" placeholder="Введите название">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Выберите пользователей:</label>
|
||||
<div class="user-search-box" style="margin-bottom: 10px;">
|
||||
<input type="text" id="list-user-search" placeholder="Поиск пользователей..." oninput="filterListUsers()" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
</div>
|
||||
<div class="users-checklist-scroll" id="list-users-container" style="max-height: 300px; overflow-y: auto; border: 1px solid #ddd; padding: 10px;">
|
||||
${usersCheckboxes}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn-cancel" onclick="closeListModal()">Отмена</button>
|
||||
<button type="button" class="btn-primary" onclick="saveListFromModal()">Сохранить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function getUserTypeLabel(user, taskType) {
|
||||
const labels = {
|
||||
'document': '(Секретарь)',
|
||||
@@ -333,58 +688,24 @@ function getUserTypeLabel(user, taskType) {
|
||||
};
|
||||
return labels[taskType] || '';
|
||||
}
|
||||
function renderEditUsersChecklist(filtered = users) {
|
||||
const container = document.getElementById('edit-users-checklist');
|
||||
container.innerHTML = filtered
|
||||
.filter(user => user.id !== currentUser.id)
|
||||
.map(user => `
|
||||
<div class="checkbox-item">
|
||||
<label>
|
||||
<input type="checkbox" name="assignedUsers" value="${user.id}"
|
||||
onchange="toggleEditUserSelection(this, ${user.id})">
|
||||
${user.name} (${user.email})
|
||||
${user.auth_type === 'ldap' ? '<small style="color: #666;"> - LDAP</small>' : ''}
|
||||
</label>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderCopyUsersChecklist(filtered = users) {
|
||||
const container = document.getElementById('copy-users-checklist');
|
||||
container.innerHTML = filtered
|
||||
.filter(user => user.id !== currentUser.id)
|
||||
.map(user => `
|
||||
<div class="checkbox-item">
|
||||
<label>
|
||||
<input type="checkbox" name="assignedUsers" value="${user.id}"
|
||||
onchange="toggleCopyUserSelection(this, ${user.id})">
|
||||
${user.name} (${user.email})
|
||||
${user.auth_type === 'ldap' ? '<small style="color: #666;"> - LDAP</small>' : ''}
|
||||
</label>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
// Экспорт функций в глобальную область (для вызова из 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;
|
||||
|
||||
function toggleUserSelection(checkbox, userId) {
|
||||
if (checkbox.checked) {
|
||||
selectedUsers.push(userId);
|
||||
} else {
|
||||
selectedUsers = selectedUsers.filter(id => id !== userId);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEditUserSelection(checkbox, userId) {
|
||||
if (checkbox.checked) {
|
||||
editSelectedUsers.push(userId);
|
||||
} else {
|
||||
editSelectedUsers = editSelectedUsers.filter(id => id !== userId);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCopyUserSelection(checkbox, userId) {
|
||||
if (checkbox.checked) {
|
||||
copySelectedUsers.push(userId);
|
||||
} else {
|
||||
copySelectedUsers = copySelectedUsers.filter(id => id !== userId);
|
||||
}
|
||||
}
|
||||
// Также экспортируем переменные, которые могут понадобиться в других скриптах
|
||||
window.selectedUsers = selectedUsers;
|
||||
window.editSelectedUsers = editSelectedUsers;
|
||||
window.copySelectedUsers = copySelectedUsers;
|
||||
@@ -7,6 +7,7 @@ const session = require('express-session');
|
||||
require('dotenv').config();
|
||||
|
||||
const cronJobs = require('./cron-jobs');
|
||||
const userListsAPI = require('./api-user-lists');
|
||||
|
||||
// Импортируем модули
|
||||
const { initializeDatabase, getDb, isInitialized } = require('./database');
|
||||
@@ -1580,6 +1581,8 @@ initializeServer().then(() => {
|
||||
// Подключаем API для чата
|
||||
chatAPI(app, db, upload);
|
||||
console.log('✅ API для чата задач подключено');
|
||||
userListsAPI(app, db);
|
||||
console.log('✅ API для списков пользователей в задачах');
|
||||
|
||||
// Запускаем фоновые задачи
|
||||
setInterval(checkOverdueTasks, 60000);
|
||||
|
||||
Reference in New Issue
Block a user