список пользователя
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),
|
UNIQUE(message_id, user_id),
|
||||||
FOREIGN KEY (message_id) REFERENCES task_chat_messages (id) ON DELETE CASCADE,
|
FOREIGN KEY (message_id) REFERENCES task_chat_messages (id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (user_id) REFERENCES users (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('✅ Таблица для сообщений чата задач созданы');
|
console.log('✅ Таблица для сообщений чата задач созданы');
|
||||||
// Создаем индексы для улучшения производительности
|
// Создаем индексы для улучшения производительности
|
||||||
@@ -1323,6 +1333,16 @@ async function createPostgresTables() {
|
|||||||
refusal_reason TEXT
|
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 созданы/проверены');
|
console.log('✅ Все таблицы PostgreSQL созданы/проверены');
|
||||||
|
|
||||||
|
|||||||
120
public/style.css
120
public/style.css
@@ -4950,3 +4950,123 @@ button.btn-primary {
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
pointer-events: none;
|
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;
|
||||||
|
}
|
||||||
675
public/users.js
675
public/users.js
@@ -1,36 +1,54 @@
|
|||||||
// users.js - Управление пользователями
|
// users.js - Управление пользователями и пользовательскими списками
|
||||||
|
|
||||||
let users = [];
|
let users = [];
|
||||||
let allUsers = [];
|
let allUsers = [];
|
||||||
|
let usersLoadingPromise = null;
|
||||||
let filteredUsers = [];
|
let filteredUsers = [];
|
||||||
let selectedUsers = [];
|
let selectedUsers = [];
|
||||||
let editSelectedUsers = [];
|
let editSelectedUsers = [];
|
||||||
let copySelectedUsers = [];
|
let copySelectedUsers = [];
|
||||||
// добавить переменную для отслеживания загрузки
|
|
||||||
let isUsersLoading = false;
|
|
||||||
|
|
||||||
// Добавьте переменную для хранения групп пользователей
|
// Переменные для пользовательских списков
|
||||||
|
let userLists = [];
|
||||||
|
let isUserListsLoading = false;
|
||||||
|
let currentEditingListId = null;
|
||||||
|
|
||||||
|
// Кэш групп пользователей
|
||||||
let userGroupsCache = {};
|
let userGroupsCache = {};
|
||||||
|
|
||||||
|
let isUsersLoading = false;
|
||||||
|
|
||||||
|
// ==================== ЗАГРУЗКА ПОЛЬЗОВАТЕЛЕЙ ====================
|
||||||
|
|
||||||
async function loadUsers() {
|
async function loadUsers() {
|
||||||
|
// Если загрузка уже идёт, возвращаем существующий промис
|
||||||
|
if (usersLoadingPromise) return usersLoadingPromise;
|
||||||
|
|
||||||
|
usersLoadingPromise = (async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/users');
|
const response = await fetch('/api/users');
|
||||||
const allUsersData = await response.json();
|
const allUsersData = await response.json();
|
||||||
//users = await response.json();
|
|
||||||
// Сохраняем всех пользователей
|
|
||||||
allUsers = allUsersData;
|
allUsers = allUsersData;
|
||||||
// Фильтруем пользователей в зависимости от прав текущего пользователя
|
|
||||||
users = filterAssignableUsers(allUsersData);
|
users = filterAssignableUsers(allUsersData);
|
||||||
filteredUsers = [...users];
|
filteredUsers = [...users];
|
||||||
renderUsersChecklist();
|
renderUsersChecklist();
|
||||||
renderEditUsersChecklist();
|
renderEditUsersChecklist();
|
||||||
renderCopyUsersChecklist();
|
renderCopyUsersChecklist();
|
||||||
populateFilterDropdowns();
|
populateFilterDropdowns();
|
||||||
|
// Загружаем пользовательские списки (не ждём)
|
||||||
|
loadUserLists();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка загрузки пользователей:', error);
|
console.error('Ошибка загрузки пользователей:', error);
|
||||||
|
} finally {
|
||||||
|
usersLoadingPromise = null;
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return usersLoadingPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавьте функцию для получения групп пользователя
|
// ==================== ПОЛУЧЕНИЕ ГРУПП ПОЛЬЗОВАТЕЛЯ ====================
|
||||||
|
|
||||||
async function getUserGroups(userId) {
|
async function getUserGroups(userId) {
|
||||||
if (userGroupsCache[userId]) {
|
if (userGroupsCache[userId]) {
|
||||||
return userGroupsCache[userId];
|
return userGroupsCache[userId];
|
||||||
@@ -49,47 +67,16 @@ async function getUserGroups(userId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновите функцию filterAssignableUsers
|
// ==================== ФИЛЬТРАЦИЯ ПОЛЬЗОВАТЕЛЕЙ ПО ПРАВАМ ====================
|
||||||
|
|
||||||
function filterAssignableUsers(allUsers, taskType = 'regular') {
|
function filterAssignableUsers(allUsers, taskType = 'regular') {
|
||||||
if (!currentUser) return [];
|
if (!currentUser) return [];
|
||||||
|
|
||||||
// Для задач типа "document" - только пользователи из группы "Секретарь"
|
// Для задач типа "document" – только секретари (асинхронно не получится здесь, но мы фильтруем позже)
|
||||||
if (taskType === 'document') {
|
// В текущей реализации эта функция вызывается синхронно, поэтому для специальных типов фильтрация будет в filterUsers
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Для других типов задач - обычная фильтрация
|
|
||||||
// Администратор видит всех пользователей
|
|
||||||
if (currentUser.role === 'admin') {
|
if (currentUser.role === 'admin') {
|
||||||
return allUsers.filter(user => user.id !== currentUser.id);
|
return allUsers.filter(user => user.id !== currentUser.id);
|
||||||
}
|
}
|
||||||
@@ -108,21 +95,18 @@ function filterAssignableUsers(allUsers, taskType = 'regular') {
|
|||||||
user.id !== currentUser.id
|
user.id !== currentUser.id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// tasks видит учителей и других tasks
|
|
||||||
if (currentUser.role === 'help') {
|
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.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') &&
|
||||||
user.id !== currentUser.id
|
user.id !== currentUser.id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// tasks видит учителей и других tasks
|
|
||||||
if (currentUser.role === '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.role === 'teacher' || user.role === 'tasks' || user.role === 'help' || user.role === 'request' || user.role === 'ithelp') &&
|
||||||
user.id !== currentUser.id
|
user.id !== currentUser.id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Учитель видит только учителей
|
|
||||||
if (currentUser.role === 'teacher') {
|
if (currentUser.role === 'teacher') {
|
||||||
return allUsers.filter(user =>
|
return allUsers.filter(user =>
|
||||||
(user.role === 'help' || user.role === 'request' || user.role === 'ithelp') &&
|
(user.role === 'help' || user.role === 'request' || user.role === 'ithelp') &&
|
||||||
@@ -132,25 +116,32 @@ function filterAssignableUsers(allUsers, taskType = 'regular') {
|
|||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== ЗАПОЛНЕНИЕ ВЫПАДАЮЩИХ СПИСКОВ ФИЛЬТРОВ ====================
|
||||||
|
|
||||||
function populateFilterDropdowns() {
|
function populateFilterDropdowns() {
|
||||||
const creatorFilter = document.getElementById('creator-filter');
|
const creatorFilter = document.getElementById('creator-filter');
|
||||||
const assigneeFilter = document.getElementById('assignee-filter');
|
const assigneeFilter = document.getElementById('assignee-filter');
|
||||||
|
|
||||||
|
if (creatorFilter) {
|
||||||
creatorFilter.innerHTML = '<option value="">Все заказчики</option>';
|
creatorFilter.innerHTML = '<option value="">Все заказчики</option>';
|
||||||
|
}
|
||||||
|
if (assigneeFilter) {
|
||||||
assigneeFilter.innerHTML = '<option value="">Все исполнители</option>';
|
assigneeFilter.innerHTML = '<option value="">Все исполнители</option>';
|
||||||
|
}
|
||||||
|
|
||||||
users.forEach(user => {
|
users.forEach(user => {
|
||||||
const creatorOption = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
creatorOption.value = user.id;
|
option.value = user.id;
|
||||||
creatorOption.textContent = `${user.name} (${user.login})`;
|
option.textContent = `${user.name} (${user.login})`;
|
||||||
creatorFilter.appendChild(creatorOption.cloneNode(true));
|
|
||||||
|
|
||||||
const assigneeOption = creatorOption.cloneNode(true);
|
if (creatorFilter) creatorFilter.appendChild(option.cloneNode(true));
|
||||||
assigneeFilter.appendChild(assigneeOption);
|
if (assigneeFilter) assigneeFilter.appendChild(option.cloneNode(true));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновите функцию filterUsers с учетом типа задачи
|
// ==================== ФИЛЬТРАЦИЯ ПРИ ПОИСКЕ (С УЧЁТОМ ТИПА ЗАДАЧИ) ====================
|
||||||
|
|
||||||
async function filterUsers() {
|
async function filterUsers() {
|
||||||
const search = document.getElementById('user-search')?.value.toLowerCase() || '';
|
const search = document.getElementById('user-search')?.value.toLowerCase() || '';
|
||||||
const taskType = document.getElementById('task-type')?.value || 'regular';
|
const taskType = document.getElementById('task-type')?.value || 'regular';
|
||||||
@@ -159,19 +150,16 @@ async function filterUsers() {
|
|||||||
renderUsersChecklist(); // Показываем загрузку
|
renderUsersChecklist(); // Показываем загрузку
|
||||||
|
|
||||||
try {
|
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 =>
|
let tempFiltered = users.filter(user =>
|
||||||
user.name.toLowerCase().includes(search) ||
|
user.name.toLowerCase().includes(search) ||
|
||||||
user.login.toLowerCase().includes(search) ||
|
user.login.toLowerCase().includes(search) ||
|
||||||
user.email.toLowerCase().includes(search)
|
(user.email && user.email.toLowerCase().includes(search))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Затем проверяем группы
|
// Если тип задачи требует специальной группы, фильтруем по группам
|
||||||
filteredUsers = [];
|
const specialTypes = ['document', 'it', 'ahch', 'psychologist', 'speech_therapist', 'Social_educator', 'hr', 'certificate', 'e_journal'];
|
||||||
|
if (specialTypes.includes(taskType)) {
|
||||||
const groupNames = {
|
const groupNames = {
|
||||||
'document': 'Секретарь',
|
'document': 'Секретарь',
|
||||||
'it': 'ИТ специалист',
|
'it': 'ИТ специалист',
|
||||||
@@ -183,27 +171,21 @@ async function filterUsers() {
|
|||||||
'certificate': 'Администрация',
|
'certificate': 'Администрация',
|
||||||
'e_journal': 'Админ ЭЖ'
|
'e_journal': 'Админ ЭЖ'
|
||||||
};
|
};
|
||||||
|
|
||||||
const targetGroup = groupNames[taskType];
|
const targetGroup = groupNames[taskType];
|
||||||
|
|
||||||
|
filteredUsers = [];
|
||||||
for (const user of tempFiltered) {
|
for (const user of tempFiltered) {
|
||||||
const groups = await getUserGroups(user.id);
|
const groups = await getUserGroups(user.id);
|
||||||
const hasTargetGroup = groups.some(group =>
|
const hasTargetGroup = groups.some(group =>
|
||||||
group.name === targetGroup ||
|
group.name === targetGroup ||
|
||||||
(typeof group === 'string' && group.includes(targetGroup))
|
(typeof group === 'string' && group.includes(targetGroup))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasTargetGroup) {
|
if (hasTargetGroup) {
|
||||||
filteredUsers.push(user);
|
filteredUsers.push(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Обычная фильтрация
|
filteredUsers = tempFiltered;
|
||||||
filteredUsers = users.filter(user =>
|
|
||||||
user.name.toLowerCase().includes(search) ||
|
|
||||||
user.login.toLowerCase().includes(search) ||
|
|
||||||
user.email.toLowerCase().includes(search)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка фильтрации пользователей:', error);
|
console.error('Ошибка фильтрации пользователей:', error);
|
||||||
@@ -215,96 +197,91 @@ async function filterUsers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function filterEditUsers() {
|
async function filterEditUsers() {
|
||||||
const search = document.getElementById('edit-user-search').value.toLowerCase();
|
const search = document.getElementById('edit-user-search')?.value.toLowerCase() || '';
|
||||||
const task = tasks.find(t => t.id === document.getElementById('edit-task-id').value);
|
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';
|
const taskType = task ? task.task_type : 'regular';
|
||||||
|
|
||||||
let filtered = [];
|
let filtered = users.filter(user =>
|
||||||
|
|
||||||
if (taskType === 'document') {
|
|
||||||
// Для задач типа "document" - только секретари
|
|
||||||
let tempFiltered = users.filter(user =>
|
|
||||||
user.name.toLowerCase().includes(search) ||
|
user.name.toLowerCase().includes(search) ||
|
||||||
user.login.toLowerCase().includes(search) ||
|
user.login.toLowerCase().includes(search) ||
|
||||||
user.email.toLowerCase().includes(search)
|
(user.email && user.email.toLowerCase().includes(search))
|
||||||
);
|
);
|
||||||
|
|
||||||
filtered = [];
|
if (taskType === 'document') {
|
||||||
for (const user of tempFiltered) {
|
const filteredByGroup = [];
|
||||||
|
for (const user of filtered) {
|
||||||
const groups = await getUserGroups(user.id);
|
const groups = await getUserGroups(user.id);
|
||||||
const hasSecretaryGroup = groups.some(group =>
|
const hasSecretaryGroup = groups.some(group =>
|
||||||
group.name === 'Секретарь' ||
|
group.name === 'Секретарь' ||
|
||||||
(typeof group === 'string' && group.includes('Секретарь'))
|
(typeof group === 'string' && group.includes('Секретарь'))
|
||||||
);
|
);
|
||||||
|
if (hasSecretaryGroup) filteredByGroup.push(user);
|
||||||
if (hasSecretaryGroup) {
|
|
||||||
filtered.push(user);
|
|
||||||
}
|
}
|
||||||
}
|
filtered = filteredByGroup;
|
||||||
} else {
|
|
||||||
filtered = users.filter(user =>
|
|
||||||
user.name.toLowerCase().includes(search) ||
|
|
||||||
user.login.toLowerCase().includes(search) ||
|
|
||||||
user.email.toLowerCase().includes(search)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEditUsersChecklist(filtered);
|
renderEditUsersChecklist(filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function filterCopyUsers() {
|
async function filterCopyUsers() {
|
||||||
const search = document.getElementById('copy-user-search').value.toLowerCase();
|
const search = document.getElementById('copy-user-search')?.value.toLowerCase() || '';
|
||||||
const taskId = document.getElementById('copy-task-id').value;
|
const taskId = document.getElementById('copy-task-id')?.value;
|
||||||
const task = tasks.find(t => t.id === taskId);
|
if (!taskId) return;
|
||||||
|
|
||||||
|
const task = window.tasks?.find(t => t.id == taskId);
|
||||||
const taskType = task ? task.task_type : 'regular';
|
const taskType = task ? task.task_type : 'regular';
|
||||||
|
|
||||||
let filtered = [];
|
let filtered = users.filter(user =>
|
||||||
|
|
||||||
if (taskType === 'document') {
|
|
||||||
// Для задач типа "document" - только секретари
|
|
||||||
let tempFiltered = users.filter(user =>
|
|
||||||
user.name.toLowerCase().includes(search) ||
|
user.name.toLowerCase().includes(search) ||
|
||||||
user.login.toLowerCase().includes(search) ||
|
user.login.toLowerCase().includes(search) ||
|
||||||
user.email.toLowerCase().includes(search)
|
(user.email && user.email.toLowerCase().includes(search))
|
||||||
);
|
);
|
||||||
|
|
||||||
filtered = [];
|
if (taskType === 'document') {
|
||||||
for (const user of tempFiltered) {
|
const filteredByGroup = [];
|
||||||
|
for (const user of filtered) {
|
||||||
const groups = await getUserGroups(user.id);
|
const groups = await getUserGroups(user.id);
|
||||||
const hasSecretaryGroup = groups.some(group =>
|
const hasSecretaryGroup = groups.some(group =>
|
||||||
group.name === 'Секретарь' ||
|
group.name === 'Секретарь' ||
|
||||||
(typeof group === 'string' && group.includes('Секретарь'))
|
(typeof group === 'string' && group.includes('Секретарь'))
|
||||||
);
|
);
|
||||||
|
if (hasSecretaryGroup) filteredByGroup.push(user);
|
||||||
if (hasSecretaryGroup) {
|
|
||||||
filtered.push(user);
|
|
||||||
}
|
}
|
||||||
}
|
filtered = filteredByGroup;
|
||||||
} else {
|
|
||||||
filtered = users.filter(user =>
|
|
||||||
user.name.toLowerCase().includes(search) ||
|
|
||||||
user.login.toLowerCase().includes(search) ||
|
|
||||||
user.email.toLowerCase().includes(search)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCopyUsersChecklist(filtered);
|
renderCopyUsersChecklist(filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== РЕНДЕРИНГ ЧЕКБОКСОВ ПОЛЬЗОВАТЕЛЕЙ ====================
|
||||||
|
|
||||||
function renderUsersChecklist() {
|
function renderUsersChecklist() {
|
||||||
const container = document.getElementById('users-checklist');
|
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) {
|
if (isUsersLoading) {
|
||||||
container.innerHTML = '<div class="loading-spinner">⏳ Загрузка пользователей...</div>';
|
leftCol.innerHTML = '<div class="loading-spinner">⏳ Загрузка пользователей...</div>';
|
||||||
return;
|
} else if (!filteredUsers || filteredUsers.length === 0) {
|
||||||
}
|
leftCol.innerHTML = '<div class="no-users">Нет доступных пользователей</div>';
|
||||||
|
} else {
|
||||||
if (!filteredUsers || filteredUsers.length === 0) {
|
leftCol.innerHTML = filteredUsers
|
||||||
container.innerHTML = '<div class="no-users">Нет доступных пользователей</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
container.innerHTML = filteredUsers
|
|
||||||
.filter(user => user.id !== currentUser?.id)
|
.filter(user => user.id !== currentUser?.id)
|
||||||
.map(user => `
|
.map(user => `
|
||||||
<div class="checkbox-item">
|
<div class="checkbox-item">
|
||||||
@@ -312,13 +289,391 @@ function renderUsersChecklist() {
|
|||||||
<input type="checkbox" name="assignedUsers" value="${user.id}"
|
<input type="checkbox" name="assignedUsers" value="${user.id}"
|
||||||
onchange="toggleUserSelection(this, ${user.id})"
|
onchange="toggleUserSelection(this, ${user.id})"
|
||||||
${selectedUsers.includes(user.id) ? 'checked' : ''}>
|
${selectedUsers.includes(user.id) ? 'checked' : ''}>
|
||||||
${user.name}
|
${escapeHtml(user.name)}
|
||||||
${getUserTypeLabel(user, document.getElementById('task-type')?.value)}
|
${getUserTypeLabel(user, document.getElementById('task-type')?.value)}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).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 => `
|
||||||
|
<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) {
|
function getUserTypeLabel(user, taskType) {
|
||||||
const labels = {
|
const labels = {
|
||||||
'document': '(Секретарь)',
|
'document': '(Секретарь)',
|
||||||
@@ -333,58 +688,24 @@ function getUserTypeLabel(user, taskType) {
|
|||||||
};
|
};
|
||||||
return labels[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) {
|
// Экспорт функций в глобальную область (для вызова из HTML)
|
||||||
const container = document.getElementById('copy-users-checklist');
|
window.loadUsers = loadUsers;
|
||||||
container.innerHTML = filtered
|
window.filterUsers = filterUsers;
|
||||||
.filter(user => user.id !== currentUser.id)
|
window.filterEditUsers = filterEditUsers;
|
||||||
.map(user => `
|
window.filterCopyUsers = filterCopyUsers;
|
||||||
<div class="checkbox-item">
|
window.toggleUserSelection = toggleUserSelection;
|
||||||
<label>
|
window.toggleEditUserSelection = toggleEditUserSelection;
|
||||||
<input type="checkbox" name="assignedUsers" value="${user.id}"
|
window.toggleCopyUserSelection = toggleCopyUserSelection;
|
||||||
onchange="toggleCopyUserSelection(this, ${user.id})">
|
window.openCreateListModal = openCreateListModal;
|
||||||
${user.name} (${user.email})
|
window.openEditListModal = openEditListModal;
|
||||||
${user.auth_type === 'ldap' ? '<small style="color: #666;"> - LDAP</small>' : ''}
|
window.deleteUserList = deleteUserList;
|
||||||
</label>
|
window.applyUserList = applyUserList;
|
||||||
</div>
|
window.closeListModal = closeListModal;
|
||||||
`).join('');
|
window.saveListFromModal = saveListFromModal;
|
||||||
}
|
window.filterListUsers = filterListUsers;
|
||||||
|
|
||||||
function toggleUserSelection(checkbox, userId) {
|
// Также экспортируем переменные, которые могут понадобиться в других скриптах
|
||||||
if (checkbox.checked) {
|
window.selectedUsers = selectedUsers;
|
||||||
selectedUsers.push(userId);
|
window.editSelectedUsers = editSelectedUsers;
|
||||||
} else {
|
window.copySelectedUsers = copySelectedUsers;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ const session = require('express-session');
|
|||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
const cronJobs = require('./cron-jobs');
|
const cronJobs = require('./cron-jobs');
|
||||||
|
const userListsAPI = require('./api-user-lists');
|
||||||
|
|
||||||
// Импортируем модули
|
// Импортируем модули
|
||||||
const { initializeDatabase, getDb, isInitialized } = require('./database');
|
const { initializeDatabase, getDb, isInitialized } = require('./database');
|
||||||
@@ -1580,6 +1581,8 @@ initializeServer().then(() => {
|
|||||||
// Подключаем API для чата
|
// Подключаем API для чата
|
||||||
chatAPI(app, db, upload);
|
chatAPI(app, db, upload);
|
||||||
console.log('✅ API для чата задач подключено');
|
console.log('✅ API для чата задач подключено');
|
||||||
|
userListsAPI(app, db);
|
||||||
|
console.log('✅ API для списков пользователей в задачах');
|
||||||
|
|
||||||
// Запускаем фоновые задачи
|
// Запускаем фоновые задачи
|
||||||
setInterval(checkOverdueTasks, 60000);
|
setInterval(checkOverdueTasks, 60000);
|
||||||
|
|||||||
Reference in New Issue
Block a user