From 04db1718aa477bc2e5e36cbbc0b4685bd68a0e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B0=D0=BB=D1=83=D0=B3=D0=B8=D0=BD=20=D0=9E=D0=BB?= =?UTF-8?q?=D0=B5=D0=B3=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= <134477kalugin66@users.no-reply.gitverse.ru> Date: Fri, 19 Dec 2025 06:42:04 +0000 Subject: [PATCH] upload files --- public/admin-script.js | 720 ++++++++++++++++---------------- public/admin.html | 922 ++++++++++++++++++++--------------------- public/script.js | 62 ++- public/style.css | 88 ++++ 4 files changed, 967 insertions(+), 825 deletions(-) diff --git a/public/admin-script.js b/public/admin-script.js index d4caf4b..cd389b2 100644 --- a/public/admin-script.js +++ b/public/admin-script.js @@ -1,361 +1,361 @@ -let currentUser = null; -let users = []; -let filteredUsers = []; - -document.addEventListener('DOMContentLoaded', function() { - checkAuth(); - setupEventListeners(); -}); - -async function checkAuth() { - try { - const response = await fetch('/api/user'); - if (response.ok) { - const data = await response.json(); - currentUser = data.user; - - if (currentUser.role !== 'admin') { - window.location.href = '/'; - return; - } - - showAdminInterface(); - } else { - showLoginInterface(); - } - } catch (error) { - showLoginInterface(); - } -} - -function showLoginInterface() { - document.getElementById('login-modal').style.display = 'block'; - document.querySelector('.admin-container').style.display = 'none'; -} - -function showAdminInterface() { - document.getElementById('login-modal').style.display = 'none'; - document.querySelector('.admin-container').style.display = 'block'; - - let userInfo = `Администратор: ${currentUser.name}`; - if (currentUser.auth_type === 'ldap') { - userInfo += ` (LDAP)`; - } - - document.getElementById('current-user').textContent = userInfo; - - loadUsers(); - loadDashboardStats(); -} - -function setupEventListeners() { - document.getElementById('login-form').addEventListener('submit', login); - document.getElementById('edit-user-form').addEventListener('submit', updateUser); -} - -async function login(event) { - event.preventDefault(); - - const login = document.getElementById('login').value; - const password = document.getElementById('password').value; - - try { - const response = await fetch('/api/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ login, password }) - }); - - if (response.ok) { - const data = await response.json(); - currentUser = data.user; - - if (currentUser.role !== 'admin') { - window.location.href = '/'; - return; - } - - showAdminInterface(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка входа'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка подключения к серверу'); - } -} - -async function logout() { - try { - await fetch('/api/logout', { method: 'POST' }); - currentUser = null; - showLoginInterface(); - } catch (error) { - console.error('Ошибка выхода:', error); - } -} - -function showAdminSection(sectionName) { - document.querySelectorAll('.admin-tab').forEach(tab => { - tab.classList.remove('active'); - }); - - document.querySelectorAll('.admin-section').forEach(section => { - section.classList.remove('active'); - }); - - document.querySelector(`.admin-tab[onclick="showAdminSection('${sectionName}')"]`).classList.add('active'); - document.getElementById(`admin-${sectionName}`).classList.add('active'); - - if (sectionName === 'users') { - loadUsers(); - } else if (sectionName === 'dashboard') { - loadDashboardStats(); - } -} - -async function loadUsers() { - try { - const response = await fetch('/admin/users'); - if (!response.ok) { - throw new Error('Ошибка загрузки пользователей'); - } - users = await response.json(); - filteredUsers = [...users]; - renderUsersTable(); - } catch (error) { - console.error('Ошибка загрузки пользователей:', error); - showError('users-table-body', 'Ошибка загрузки пользователей'); - } -} - -async function loadDashboardStats() { - try { - const response = await fetch('/admin/stats'); - if (!response.ok) { - throw new Error('Ошибка загрузки статистики'); - } - - const stats = await response.json(); - updateStatsUI(stats); - } catch (error) { - console.error('Ошибка загрузки статистики:', error); - } -} - -function updateStatsUI(stats) { - // Задачи - document.getElementById('total-tasks').textContent = stats.totalTasks; - document.getElementById('active-tasks').textContent = stats.activeTasks; - document.getElementById('closed-tasks').textContent = stats.closedTasks; - document.getElementById('deleted-tasks').textContent = stats.deletedTasks; - - // Процент активных задач - if (stats.totalTasks > 0) { - const activePercentage = Math.round((stats.activeTasks / stats.totalTasks) * 100); - document.getElementById('active-tasks-bar').style.width = `${activePercentage}%`; - } - - // Назначения - document.getElementById('total-assignments').textContent = stats.totalAssignments; - document.getElementById('assigned-count').textContent = stats.assignedCount; - document.getElementById('in-progress-count').textContent = stats.inProgressCount; - document.getElementById('completed-count').textContent = stats.completedCount; - document.getElementById('overdue-count').textContent = stats.overdueCount; - document.getElementById('rework-count').textContent = stats.reworkCount; - - // Пользователи - document.getElementById('total-users').textContent = stats.totalUsers; - document.getElementById('admin-users').textContent = stats.adminUsers; - document.getElementById('teacher-users').textContent = stats.teacherUsers; - document.getElementById('ldap-users').textContent = stats.ldapUsers; - document.getElementById('local-users').textContent = stats.localUsers; - - // Файлы - document.getElementById('total-files').textContent = stats.totalFiles; - const fileSizeMB = (stats.totalFilesSize / 1024 / 1024).toFixed(2); - document.getElementById('total-files-size').textContent = `${fileSizeMB} MB`; - -} - -function searchUsers() { - const search = document.getElementById('user-search').value.toLowerCase(); - filteredUsers = users.filter(user => - user.login.toLowerCase().includes(search) || - user.name.toLowerCase().includes(search) || - user.email.toLowerCase().includes(search) || - user.role.toLowerCase().includes(search) || - user.auth_type.toLowerCase().includes(search) - ); - renderUsersTable(); -} - -function renderUsersTable() { - const tbody = document.getElementById('users-table-body'); - - if (!filteredUsers || filteredUsers.length === 0) { - tbody.innerHTML = 'Пользователи не найдены'; - return; - } - - tbody.innerHTML = filteredUsers.map(user => ` - - ${user.id} - - ${user.login} - ${user.auth_type === 'ldap' ? 'LDAP' : ''} - - ${user.name} - ${user.email} - - ${user.role === 'admin' ? 'Администратор' : 'Учитель'} - ${user.role === 'admin' ? 'ADMIN' : ''} - - ${user.auth_type === 'ldap' ? 'LDAP' : 'Локальная'} - ${formatDate(user.created_at)} - ${user.last_login ? formatDateTime(user.last_login) : 'Никогда'} - - - - - - `).join(''); -} - -async function openEditUserModal(userId) { - try { - const response = await fetch(`/admin/users/${userId}`); - if (!response.ok) { - throw new Error('Ошибка загрузки пользователя'); - } - - const user = await response.json(); - - document.getElementById('edit-user-id').value = user.id; - document.getElementById('edit-login').value = user.login; - document.getElementById('edit-name').value = user.name; - document.getElementById('edit-email').value = user.email; - document.getElementById('edit-role').value = user.role; - document.getElementById('edit-auth-type').value = user.auth_type; - document.getElementById('edit-groups').value = user.groups || '[]'; - document.getElementById('edit-description').value = user.description || ''; - - document.getElementById('edit-user-modal').style.display = 'block'; - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка загрузки пользователя'); - } -} - -function closeEditUserModal() { - document.getElementById('edit-user-modal').style.display = 'none'; -} - -async function updateUser(event) { - event.preventDefault(); - - const userId = document.getElementById('edit-user-id').value; - const login = document.getElementById('edit-login').value; - const name = document.getElementById('edit-name').value; - const email = document.getElementById('edit-email').value; - const role = document.getElementById('edit-role').value; - const auth_type = document.getElementById('edit-auth-type').value; - const groups = document.getElementById('edit-groups').value; - const description = document.getElementById('edit-description').value; - - if (!login || !name || !email) { - alert('Заполните обязательные поля'); - return; - } - - const userData = { - login, - name, - email, - role, - auth_type, - groups: groups || '[]', - description - }; - - try { - const response = await fetch(`/admin/users/${userId}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(userData) - }); - - if (response.ok) { - alert('Пользователь успешно обновлен!'); - closeEditUserModal(); - loadUsers(); - loadDashboardStats(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка обновления пользователя'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка обновления пользователя'); - } -} - -async function deleteUser(userId) { - if (userId === currentUser.id) { - alert('Нельзя удалить самого себя'); - return; - } - - if (!confirm('Вы уверены, что хотите удалить этого пользователя?')) { - return; - } - - try { - const response = await fetch(`/admin/users/${userId}`, { - method: 'DELETE' - }); - - if (response.ok) { - alert('Пользователь успешно удален!'); - loadUsers(); - loadDashboardStats(); - } else { - const error = await response.json(); - alert(error.error || 'Ошибка удаления пользователя'); - } - } catch (error) { - console.error('Ошибка:', error); - alert('Ошибка удаления пользователя'); - } -} - -function formatDateTime(dateTimeString) { - if (!dateTimeString) return ''; - const date = new Date(dateTimeString); - return date.toLocaleString('ru-RU'); -} - -function formatDate(dateString) { - if (!dateString) return ''; - const date = new Date(dateString); - return date.toLocaleDateString('ru-RU'); -} - -function showError(elementId, message) { - const element = document.getElementById(elementId); - if (element) { - element.innerHTML = `${message}`; - } -} - -// Автоматическое обновление статистики каждые 30 секунд -setInterval(() => { - if (document.getElementById('admin-dashboard').classList.contains('active')) { - loadDashboardStats(); - } +let currentUser = null; +let users = []; +let filteredUsers = []; + +document.addEventListener('DOMContentLoaded', function() { + checkAuth(); + setupEventListeners(); +}); + +async function checkAuth() { + try { + const response = await fetch('/api/user'); + if (response.ok) { + const data = await response.json(); + currentUser = data.user; + + if (currentUser.role !== 'admin') { + window.location.href = '/'; + return; + } + + showAdminInterface(); + } else { + showLoginInterface(); + } + } catch (error) { + showLoginInterface(); + } +} + +function showLoginInterface() { + document.getElementById('login-modal').style.display = 'block'; + document.querySelector('.admin-container').style.display = 'none'; +} + +function showAdminInterface() { + document.getElementById('login-modal').style.display = 'none'; + document.querySelector('.admin-container').style.display = 'block'; + + let userInfo = `Администратор: ${currentUser.name}`; + if (currentUser.auth_type === 'ldap') { + userInfo += ` (LDAP)`; + } + + document.getElementById('current-user').textContent = userInfo; + + loadUsers(); + loadDashboardStats(); +} + +function setupEventListeners() { + document.getElementById('login-form').addEventListener('submit', login); + document.getElementById('edit-user-form').addEventListener('submit', updateUser); +} + +async function login(event) { + event.preventDefault(); + + const login = document.getElementById('login').value; + const password = document.getElementById('password').value; + + try { + const response = await fetch('/api/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ login, password }) + }); + + if (response.ok) { + const data = await response.json(); + currentUser = data.user; + + if (currentUser.role !== 'admin') { + window.location.href = '/'; + return; + } + + showAdminInterface(); + } else { + const error = await response.json(); + alert(error.error || 'Ошибка входа'); + } + } catch (error) { + console.error('Ошибка:', error); + alert('Ошибка подключения к серверу'); + } +} + +async function logout() { + try { + await fetch('/api/logout', { method: 'POST' }); + currentUser = null; + showLoginInterface(); + } catch (error) { + console.error('Ошибка выхода:', error); + } +} + +function showAdminSection(sectionName) { + document.querySelectorAll('.admin-tab').forEach(tab => { + tab.classList.remove('active'); + }); + + document.querySelectorAll('.admin-section').forEach(section => { + section.classList.remove('active'); + }); + + document.querySelector(`.admin-tab[onclick="showAdminSection('${sectionName}')"]`).classList.add('active'); + document.getElementById(`admin-${sectionName}`).classList.add('active'); + + if (sectionName === 'users') { + loadUsers(); + } else if (sectionName === 'dashboard') { + loadDashboardStats(); + } +} + +async function loadUsers() { + try { + const response = await fetch('/admin/users'); + if (!response.ok) { + throw new Error('Ошибка загрузки пользователей'); + } + users = await response.json(); + filteredUsers = [...users]; + renderUsersTable(); + } catch (error) { + console.error('Ошибка загрузки пользователей:', error); + showError('users-table-body', 'Ошибка загрузки пользователей'); + } +} + +async function loadDashboardStats() { + try { + const response = await fetch('/admin/stats'); + if (!response.ok) { + throw new Error('Ошибка загрузки статистики'); + } + + const stats = await response.json(); + updateStatsUI(stats); + } catch (error) { + console.error('Ошибка загрузки статистики:', error); + } +} + +function updateStatsUI(stats) { + // Задачи + document.getElementById('total-tasks').textContent = stats.totalTasks; + document.getElementById('active-tasks').textContent = stats.activeTasks; + document.getElementById('closed-tasks').textContent = stats.closedTasks; + document.getElementById('deleted-tasks').textContent = stats.deletedTasks; + + // Процент активных задач + if (stats.totalTasks > 0) { + const activePercentage = Math.round((stats.activeTasks / stats.totalTasks) * 100); + document.getElementById('active-tasks-bar').style.width = `${activePercentage}%`; + } + + // Назначения + document.getElementById('total-assignments').textContent = stats.totalAssignments; + document.getElementById('assigned-count').textContent = stats.assignedCount; + document.getElementById('in-progress-count').textContent = stats.inProgressCount; + document.getElementById('completed-count').textContent = stats.completedCount; + document.getElementById('overdue-count').textContent = stats.overdueCount; + document.getElementById('rework-count').textContent = stats.reworkCount; + + // Пользователи + document.getElementById('total-users').textContent = stats.totalUsers; + document.getElementById('admin-users').textContent = stats.adminUsers; + document.getElementById('teacher-users').textContent = stats.teacherUsers; + document.getElementById('ldap-users').textContent = stats.ldapUsers; + document.getElementById('local-users').textContent = stats.localUsers; + + // Файлы + document.getElementById('total-files').textContent = stats.totalFiles; + const fileSizeMB = (stats.totalFilesSize / 1024 / 1024).toFixed(2); + document.getElementById('total-files-size').textContent = `${fileSizeMB} MB`; + +} + +function searchUsers() { + const search = document.getElementById('user-search').value.toLowerCase(); + filteredUsers = users.filter(user => + user.login.toLowerCase().includes(search) || + user.name.toLowerCase().includes(search) || + user.email.toLowerCase().includes(search) || + user.role.toLowerCase().includes(search) || + user.auth_type.toLowerCase().includes(search) + ); + renderUsersTable(); +} + +function renderUsersTable() { + const tbody = document.getElementById('users-table-body'); + + if (!filteredUsers || filteredUsers.length === 0) { + tbody.innerHTML = 'Пользователи не найдены'; + return; + } + + tbody.innerHTML = filteredUsers.map(user => ` + + ${user.id} + + ${user.login} + ${user.auth_type === 'ldap' ? 'LDAP' : ''} + + ${user.name} + ${user.email} + + ${user.role === 'admin' ? 'Администратор' : 'Учитель'} + ${user.role === 'admin' ? 'ADMIN' : ''} + + ${user.auth_type === 'ldap' ? 'LDAP' : 'Локальная'} + ${formatDate(user.created_at)} + ${user.last_login ? formatDateTime(user.last_login) : 'Никогда'} + + + + + + `).join(''); +} + +async function openEditUserModal(userId) { + try { + const response = await fetch(`/admin/users/${userId}`); + if (!response.ok) { + throw new Error('Ошибка загрузки пользователя'); + } + + const user = await response.json(); + + document.getElementById('edit-user-id').value = user.id; + document.getElementById('edit-login').value = user.login; + document.getElementById('edit-name').value = user.name; + document.getElementById('edit-email').value = user.email; + document.getElementById('edit-role').value = user.role; + document.getElementById('edit-auth-type').value = user.auth_type; + document.getElementById('edit-groups').value = user.groups || '[]'; + document.getElementById('edit-description').value = user.description || ''; + + document.getElementById('edit-user-modal').style.display = 'block'; + } catch (error) { + console.error('Ошибка:', error); + alert('Ошибка загрузки пользователя'); + } +} + +function closeEditUserModal() { + document.getElementById('edit-user-modal').style.display = 'none'; +} + +async function updateUser(event) { + event.preventDefault(); + + const userId = document.getElementById('edit-user-id').value; + const login = document.getElementById('edit-login').value; + const name = document.getElementById('edit-name').value; + const email = document.getElementById('edit-email').value; + const role = document.getElementById('edit-role').value; + const auth_type = document.getElementById('edit-auth-type').value; + const groups = document.getElementById('edit-groups').value; + const description = document.getElementById('edit-description').value; + + if (!login || !name || !email) { + alert('Заполните обязательные поля'); + return; + } + + const userData = { + login, + name, + email, + role, + auth_type, + groups: groups || '[]', + description + }; + + try { + const response = await fetch(`/admin/users/${userId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(userData) + }); + + if (response.ok) { + alert('Пользователь успешно обновлен!'); + closeEditUserModal(); + loadUsers(); + loadDashboardStats(); + } else { + const error = await response.json(); + alert(error.error || 'Ошибка обновления пользователя'); + } + } catch (error) { + console.error('Ошибка:', error); + alert('Ошибка обновления пользователя'); + } +} + +async function deleteUser(userId) { + if (userId === currentUser.id) { + alert('Нельзя удалить самого себя'); + return; + } + + if (!confirm('Вы уверены, что хотите удалить этого пользователя?')) { + return; + } + + try { + const response = await fetch(`/admin/users/${userId}`, { + method: 'DELETE' + }); + + if (response.ok) { + alert('Пользователь успешно удален!'); + loadUsers(); + loadDashboardStats(); + } else { + const error = await response.json(); + alert(error.error || 'Ошибка удаления пользователя'); + } + } catch (error) { + console.error('Ошибка:', error); + alert('Ошибка удаления пользователя'); + } +} + +function formatDateTime(dateTimeString) { + if (!dateTimeString) return ''; + const date = new Date(dateTimeString); + return date.toLocaleString('ru-RU'); +} + +function formatDate(dateString) { + if (!dateString) return ''; + const date = new Date(dateString); + return date.toLocaleDateString('ru-RU'); +} + +function showError(elementId, message) { + const element = document.getElementById(elementId); + if (element) { + element.innerHTML = `${message}`; + } +} + +// Автоматическое обновление статистики каждые 30 секунд +setInterval(() => { + if (document.getElementById('admin-dashboard').classList.contains('active')) { + loadDashboardStats(); + } }, 30000); \ No newline at end of file diff --git a/public/admin.html b/public/admin.html index e467589..357a137 100644 --- a/public/admin.html +++ b/public/admin.html @@ -1,462 +1,462 @@ - - - - - - School CRM - Административная панель - - - - - - -
-
-

Административная панель

- -
- -
- - - -
- -
-

Статистика системы

- -
-
-

Задачи

-
0
-
Всего задач в системе
-
-
-
-
-
- Активные: - 0 -
-
- Закрытые: - 0 -
-
- Удаленные: - 0 -
-
-
- -
-

Статусы назначений

-
0
-
Всего назначений
-
-
- Назначено: - 0 -
-
- В работе: - 0 -
-
- Выполнено: - 0 -
-
- Просрочено: - 0 -
-
- На доработке: - 0 -
-
-
- -
-

Пользователи

-
0
-
Зарегистрировано пользователей
-
-
- Администраторы: - 0 -
-
- Учителя: - 0 -
-
- LDAP: - 0 -
-
- Локальные: - 0 -
-
-
- -
-

Файлы

-
0
-
Всего загружено файлов
-
0 MB
-
-
-
- -
-

Управление пользователями

- -
- - -
- - - - - - - - - - - - - - - - - - - - -
IDЛогинИмяEmailРольТипДата созданияПоследний входДействия
Загрузка пользователей...
-
-
- - - - - + + + + + + School CRM - Административная панель + + + + + + +
+
+

Административная панель

+ +
+ +
+ + + +
+ +
+

Статистика системы

+ +
+
+

Задачи

+
0
+
Всего задач в системе
+
+
+
+
+
+ Активные: + 0 +
+
+ Закрытые: + 0 +
+
+ Удаленные: + 0 +
+
+
+ +
+

Статусы назначений

+
0
+
Всего назначений
+
+
+ Назначено: + 0 +
+
+ В работе: + 0 +
+
+ Выполнено: + 0 +
+
+ Просрочено: + 0 +
+
+ На доработке: + 0 +
+
+
+ +
+

Пользователи

+
0
+
Зарегистрировано пользователей
+
+
+ Администраторы: + 0 +
+
+ Учителя: + 0 +
+
+ LDAP: + 0 +
+
+ Локальные: + 0 +
+
+
+ +
+

Файлы

+
0
+
Всего загружено файлов
+
0 MB
+
+
+
+ +
+

Управление пользователями

+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + +
IDЛогинИмяEmailРольТипДата созданияПоследний входДействия
Загрузка пользователей...
+
+
+ + + + + \ No newline at end of file diff --git a/public/script.js b/public/script.js index e6893df..9d0470c 100644 --- a/public/script.js +++ b/public/script.js @@ -427,10 +427,10 @@ function renderTasks() {
Исполнители: - ${task.assignments && task.assignments.length > 0 ? - task.assignments.map(assignment => renderAssignment(assignment, task.id, canEdit)).join('') : - '
Не назначены
' - } +${task.assignments && task.assignments.length > 0 ? + renderAssignmentList(task.assignments, task.id, canEdit) : + '
Не назначены
' +}
@@ -443,7 +443,61 @@ function renderTasks() { `; }).join(''); } +function renderAssignmentList(assignments, taskId, canEdit) { + if (!assignments || assignments.length === 0) { + return '
Не назначены
'; + } + // Создаем контейнер с возможностью фильтрации + return ` +
+
+ + ${assignments.length} исполнителей +
+
+ ${assignments.map(assignment => renderAssignment(assignment, taskId, canEdit)).join('')} +
+
+ `; +} + +// Функция для фильтрации исполнителей в конкретной задаче +function filterAssignments(taskId) { + const filterInput = document.querySelector(`.assignment-filter-input[data-task-id="${taskId}"]`); + const scrollContainer = document.getElementById(`assignments-${taskId}`); + const filterCount = document.getElementById(`filter-count-${taskId}`); + + if (!filterInput || !scrollContainer) return; + + const searchTerm = filterInput.value.toLowerCase(); + const assignments = scrollContainer.querySelectorAll('.assignment'); + + let visibleCount = 0; + + assignments.forEach(assignment => { + const userName = assignment.querySelector('strong')?.textContent?.toLowerCase() || ''; + const userLogin = assignment.querySelector('small')?.textContent?.toLowerCase() || ''; + + const isVisible = userName.includes(searchTerm) || + userLogin.includes(searchTerm) || + searchTerm === ''; + + assignment.style.display = isVisible ? '' : 'none'; + + if (isVisible) { + visibleCount++; + } + }); + + if (filterCount) { + filterCount.textContent = `${visibleCount} из ${assignments.length} исполнителей`; + } +} function toggleTask(taskId) { if (expandedTasks.has(taskId)) { expandedTasks.delete(taskId); diff --git a/public/style.css b/public/style.css index f2e7179..491b2b8 100644 --- a/public/style.css +++ b/public/style.css @@ -1734,4 +1734,92 @@ button.reopen-btn:hover { .file-name { font-size: 0.65rem; } +} +/* Добавьте эти стили в style.css */ + +.assignments-container { + margin-top: 10px; + border: 1px solid #e1e5e9; + border-radius: 8px; + padding: 10px; + background: #f8fafc; +} + +.assignments-filter { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; + padding-bottom: 8px; + border-bottom: 1px solid #e1e5e9; +} + +.assignment-filter-input { + flex: 1; + padding: 6px 10px; + border: 1px solid #d1d5db; + border-radius: 4px; + font-size: 14px; + margin-right: 10px; +} + +.assignment-filter-input:focus { + outline: none; + border-color: #3498db; + box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2); +} + +.filter-count { + font-size: 12px; + color: #6c757d; + background: #e9ecef; + padding: 2px 8px; + border-radius: 12px; + white-space: nowrap; +} + +.assignments-scroll-container { + max-height: 300px; + overflow-y: auto; + padding-right: 5px; +} + +/* Стили для скроллбара */ +.assignments-scroll-container::-webkit-scrollbar { + width: 8px; +} + +.assignments-scroll-container::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; +} + +.assignments-scroll-container::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; +} + +.assignments-scroll-container::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +/* Стили для отдельных исполнителей */ +.assignment { + display: flex; + align-items: flex-start; + padding: 8px; + margin-bottom: 8px; + background: white; + border: 1px solid #e9ecef; + border-radius: 6px; + transition: all 0.2s; +} + +.assignment:hover { + background: #f8f9fa; + border-color: #dee2e6; +} + +.assignment:last-child { + margin-bottom: 0; } \ No newline at end of file