+++
This commit is contained in:
@@ -28,7 +28,8 @@ module.exports = function(app, db) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isAdmin = user.role === 'admin';
|
const isAdmin = user.role === 'admin';
|
||||||
callback(null, isAdmin);
|
const isTasks = user.role === 'tasks'; // Пользователи с ролью tasks тоже имеют доступ
|
||||||
|
callback(null, isAdmin || isTasks);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,12 @@ function showMainInterface() {
|
|||||||
|
|
||||||
document.getElementById('current-user').textContent = userInfo;
|
document.getElementById('current-user').textContent = userInfo;
|
||||||
|
|
||||||
|
// 👇 СОЗДАЕМ НАВИГАЦИЮ ПОСЛЕ УСТАНОВКИ ПОЛЬЗОВАТЕЛЯ 👇
|
||||||
|
if (typeof createNavigation === 'function') {
|
||||||
|
console.log('🔄 Создание навигации для пользователя:', currentUser.role);
|
||||||
|
createNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('tasks-controls').style.display = 'block';
|
document.getElementById('tasks-controls').style.display = 'block';
|
||||||
|
|
||||||
const showDeletedLabel = document.querySelector('.show-deleted-label');
|
const showDeletedLabel = document.querySelector('.show-deleted-label');
|
||||||
|
|||||||
@@ -203,7 +203,7 @@
|
|||||||
|
|
||||||
<section id="profile-section" class="section">
|
<section id="profile-section" class="section">
|
||||||
<h2><i class="fas fa-user-circle"></i> Личный кабинет</h2>
|
<h2><i class="fas fa-user-circle"></i> Личный кабинет</h2>
|
||||||
|
<div id="user-profile-info"></div>
|
||||||
<div class="notification-settings">
|
<div class="notification-settings">
|
||||||
<h3><i class="fas fa-bell"></i> Настройки уведомлений</h3>
|
<h3><i class="fas fa-bell"></i> Настройки уведомлений</h3>
|
||||||
<form id="notification-settings-form">
|
<form id="notification-settings-form">
|
||||||
@@ -373,7 +373,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="navbar.js"></script>
|
|
||||||
<script src="auth.js"></script>
|
<script src="auth.js"></script>
|
||||||
<script src="users.js"></script>
|
<script src="users.js"></script>
|
||||||
<script src="tasks.js"></script>
|
<script src="tasks.js"></script>
|
||||||
@@ -385,6 +384,7 @@
|
|||||||
<script src="openTaskChat.js"></script>
|
<script src="openTaskChat.js"></script>
|
||||||
<script src="main.js"></script>
|
<script src="main.js"></script>
|
||||||
<script src="tasks_files.js"></script>
|
<script src="tasks_files.js"></script>
|
||||||
|
<script src="navbar.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -2,7 +2,15 @@
|
|||||||
function createNavigation() {
|
function createNavigation() {
|
||||||
const navbar = document.getElementById('navbar-container');
|
const navbar = document.getElementById('navbar-container');
|
||||||
if (!navbar) return;
|
if (!navbar) return;
|
||||||
|
// 👇 ДОБАВЛЯЕМ ПОДРОБНЫЕ ЛОГИ 👇
|
||||||
|
if (currentUser) {
|
||||||
|
console.log('ID:', currentUser.id);
|
||||||
|
console.log('ФИО:', currentUser.name);
|
||||||
|
console.log('Логин:', currentUser.login);
|
||||||
|
console.log('Роль:', currentUser.role);
|
||||||
|
} else {
|
||||||
|
console.log('currentUser отсутствует (не авторизован)');
|
||||||
|
}
|
||||||
// Базовые кнопки для всех авторизованных пользователей
|
// Базовые кнопки для всех авторизованных пользователей
|
||||||
const navButtons = [
|
const navButtons = [
|
||||||
{
|
{
|
||||||
@@ -42,8 +50,8 @@ function createNavigation() {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Кнопка админ-панели только для admin
|
// 👇 Кнопка админ-панели ТОЛЬКО для admin 👇
|
||||||
//if (currentUser.role === 'admin') {
|
if (currentUser && currentUser.role === 'admin') {
|
||||||
navButtons.push({
|
navButtons.push({
|
||||||
onclick: "window.location.href = '/admin'",
|
onclick: "window.location.href = '/admin'",
|
||||||
className: "nav-btn admin",
|
className: "nav-btn admin",
|
||||||
@@ -51,7 +59,7 @@ function createNavigation() {
|
|||||||
text: "Админ-панель",
|
text: "Админ-панель",
|
||||||
id: "admin-btn"
|
id: "admin-btn"
|
||||||
});
|
});
|
||||||
//}
|
}
|
||||||
|
|
||||||
// Кнопка выхода
|
// Кнопка выхода
|
||||||
navButtons.push({
|
navButtons.push({
|
||||||
@@ -76,7 +84,7 @@ function createNavigation() {
|
|||||||
|
|
||||||
// Инициализация при загрузке страницы
|
// Инициализация при загрузке страницы
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
createNavigation();
|
// createNavigation();
|
||||||
|
|
||||||
// Если нужно обновлять навигацию при изменениях
|
// Если нужно обновлять навигацию при изменениях
|
||||||
// window.addEventListener('userRoleChanged', createNavigation);
|
// window.addEventListener('userRoleChanged', createNavigation);
|
||||||
|
|||||||
@@ -15,56 +15,77 @@ async function loadUserProfile() {
|
|||||||
if (data.user) {
|
if (data.user) {
|
||||||
const userInfo = document.getElementById('user-profile-info');
|
const userInfo = document.getElementById('user-profile-info');
|
||||||
userInfo.innerHTML = `
|
userInfo.innerHTML = `
|
||||||
<div class="profile-card">
|
<div class="profile-modern">
|
||||||
<div class="profile-field">
|
<!-- Шапка профиля с аватаром -->
|
||||||
<i class="fas fa-user"></i>
|
<div class="profile-header">
|
||||||
<div>
|
<div class="profile-avatar">
|
||||||
<strong>Имя:</strong>
|
<i class="fas fa-user-circle"></i>
|
||||||
<p>${data.user.name}</p>
|
</div>
|
||||||
|
<div class="profile-title">
|
||||||
|
<h3>${data.user.name}</h3>
|
||||||
|
<span class="profile-badge ${data.user.role}">
|
||||||
|
${data.user.role === 'admin' ? 'Администратор' : 'Сотрудник'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-field">
|
<!-- Сетка с информацией -->
|
||||||
|
<div class="profile-grid">
|
||||||
|
<div class="profile-info-card">
|
||||||
|
<div class="info-icon">
|
||||||
<i class="fas fa-at"></i>
|
<i class="fas fa-at"></i>
|
||||||
<div>
|
</div>
|
||||||
<strong>Логин:</strong>
|
<div class="info-content">
|
||||||
<p>${data.user.login}</p>
|
<span class="info-label">Логин</span>
|
||||||
|
<span class="info-value">${data.user.login}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-field">
|
<div class="profile-info-card">
|
||||||
<i class="fas fa-envelope"></i>
|
<div class="info-icon">
|
||||||
<div>
|
<i class="fas fa-shield-alt"></i>
|
||||||
<strong>Email:</strong>
|
</div>
|
||||||
<p>${data.user.email || 'Не указан'}</p>
|
<div class="info-content">
|
||||||
</div>
|
<span class="info-label">Тип авторизации</span>
|
||||||
</div>
|
<span class="info-value">
|
||||||
<div class="profile-field">
|
${data.user.auth_type === 'ldap' ?
|
||||||
<i class="fas fa-user-tag"></i>
|
'<span class="badge-ldap"><i class="fas fa-building"></i> LDAP</span>' :
|
||||||
<div>
|
'<span class="badge-local"><i class="fas fa-database"></i> Локальная</span>'}
|
||||||
<strong>Роль:</strong>
|
</span>
|
||||||
<p>${data.user.role === 'admin' ? 'Администратор' : 'Учитель'}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="profile-field">
|
|
||||||
<i class="fas fa-key"></i>
|
|
||||||
<div>
|
|
||||||
<strong>Тип авторизации:</strong>
|
|
||||||
<p>${data.user.auth_type === 'ldap' ? 'LDAP' : 'Локальная'}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${data.user.groups && data.user.groups.length > 0 ? `
|
${data.user.groups && data.user.groups.length > 0 ? `
|
||||||
<div class="profile-field">
|
<div class="profile-info-card groups-card">
|
||||||
|
<div class="info-icon">
|
||||||
<i class="fas fa-users"></i>
|
<i class="fas fa-users"></i>
|
||||||
<div>
|
</div>
|
||||||
<strong>Группы:</strong>
|
<div class="info-content">
|
||||||
<p>${Array.isArray(data.user.groups) ? data.user.groups.join(', ') : data.user.groups}</p>
|
<span class="info-label">Группы доступа</span>
|
||||||
|
<div class="groups-list">
|
||||||
|
${Array.isArray(data.user.groups) ?
|
||||||
|
data.user.groups.map(group =>
|
||||||
|
`<span class="group-tag">${group}</span>`
|
||||||
|
).join('') :
|
||||||
|
`<span class="group-tag">${data.user.groups}</span>`
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка загрузки профиля:', error);
|
console.error('Ошибка загрузки профиля:', error);
|
||||||
|
const userInfo = document.getElementById('user-profile-info');
|
||||||
|
if (userInfo) {
|
||||||
|
userInfo.innerHTML = `
|
||||||
|
<div class="profile-error">
|
||||||
|
<i class="fas fa-exclamation-circle"></i>
|
||||||
|
<p>Ошибка загрузки профиля</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
203
public/style.css
203
public/style.css
@@ -4176,3 +4176,206 @@ button.btn-primary {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
/* Современный профиль */
|
||||||
|
.profile-modern {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 3px solid rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar i {
|
||||||
|
font-size: 50px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-title {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-title h3 {
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 15px;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-badge.admin {
|
||||||
|
background: linear-gradient(45deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-info-card {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
transition: transform 0.3s, background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-info-card:hover {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon i {
|
||||||
|
font-size: 20px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groups-card {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groups-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-tag {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-ldap, .badge-local {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 13px;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-error {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 30px;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-error i {
|
||||||
|
font-size: 40px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-error p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Адаптивность */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.profile-modern {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-header {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-stats {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -467,6 +467,8 @@ function canUserEditTask(task) {
|
|||||||
|
|
||||||
// Администратор может всё
|
// Администратор может всё
|
||||||
if (currentUser.role === 'admin') return true;
|
if (currentUser.role === 'admin') return true;
|
||||||
|
// Пользователи с ролью 'tasks' могут редактировать любые задачи
|
||||||
|
if (currentUser.role === 'tasks') return true;
|
||||||
|
|
||||||
// Создатель может редактировать свою задачу
|
// Создатель может редактировать свою задачу
|
||||||
if (parseInt(task.created_by) === currentUser.id) {
|
if (parseInt(task.created_by) === currentUser.id) {
|
||||||
|
|||||||
13
public/ui.js
13
public/ui.js
@@ -87,8 +87,8 @@ function renderTasks() {
|
|||||||
<button class="add-file-btn" onclick="openAddFileModal(${task.id})" title="Добавить файл">📎</button>
|
<button class="add-file-btn" onclick="openAddFileModal(${task.id})" title="Добавить файл">📎</button>
|
||||||
${currentUser && currentUser.login === 'minicrm' ? `<button class="edit-btn" onclick="openEditModal(${task.id})" title="Редактировать">✏️</button>` : ''}
|
${currentUser && currentUser.login === 'minicrm' ? `<button class="edit-btn" onclick="openEditModal(${task.id})" title="Редактировать">✏️</button>` : ''}
|
||||||
${currentUser && currentUser.login === 'kalugin.o' ? `<button class="manage-assignees-btn" onclick="openManageAssigneesModal(${task.id})" title="Управление исполнителями">👥</button>` : ''}
|
${currentUser && currentUser.login === 'kalugin.o' ? `<button class="manage-assignees-btn" onclick="openManageAssigneesModal(${task.id})" title="Управление исполнителями">👥</button>` : ''}
|
||||||
${currentUser && currentUser.login === 'kalugin.o' ? `<button class="manage-assignees-btn" onclick="assignAdd_openModal(${task.id})" title="Управление исполнителями">🧑💼➕Добавить</button>` : ''}
|
${currentUser && currentUser.role === 'tasks' && canEdit || currentUser.role === 'admin' ? `<button class="manage-assignees-btn" onclick="assignAdd_openModal(${task.id})" title="Управление исполнителями">🧑💼➕Добавить</button>` : ''}
|
||||||
${currentUser && currentUser.login === 'kalugin.o' ? `<button class="manage-assignees-btn" onclick="assignRemove_openModal(${task.id})" title="Управление исполнителями">🧑💼❌Удалить</button>` : ''}
|
${currentUser && currentUser.role === 'tasks' && canEdit || currentUser.role === 'admin' ? `<button class="manage-assignees-btn" onclick="assignRemove_openModal(${task.id})" title="Управление исполнителями">🧑💼❌Удалить</button>` : ''}
|
||||||
<button class="copy-btn" onclick="openCopyModal(${task.id})" title="Создать копию">📋</button>
|
<button class="copy-btn" onclick="openCopyModal(${task.id})" title="Создать копию">📋</button>
|
||||||
${currentUser && currentUser.login === 'minicrm' ? `<button class="rework-btn" onclick="openReworkModal(${task.id})" title="Вернуть на доработку">🔄</button>` : ''}
|
${currentUser && currentUser.login === 'minicrm' ? `<button class="rework-btn" onclick="openReworkModal(${task.id})" title="Вернуть на доработку">🔄</button>` : ''}
|
||||||
${currentUser && currentUser.login === 'minicrm' ? `<button class="close-btn" onclick="closeTask(${task.id})" title="Закрыть задачу">🔒</button>` : ''}
|
${currentUser && currentUser.login === 'minicrm' ? `<button class="close-btn" onclick="closeTask(${task.id})" title="Закрыть задачу">🔒</button>` : ''}
|
||||||
@@ -1320,7 +1320,14 @@ async function assignAdd_openModal(taskId) {
|
|||||||
// Получаем доступных для добавления исполнителей
|
// Получаем доступных для добавления исполнителей
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/tasks/${taskId}/available-assignees`);
|
const response = await fetch(`/api/tasks/${taskId}/available-assignees`);
|
||||||
const availableUsers = await response.json();
|
//const availableUsers = await response.json();
|
||||||
|
let availableUsers = await response.json();
|
||||||
|
|
||||||
|
// 👇 ИСКЛЮЧАЕМ АВТОРА ЗАДАЧИ ИЗ СПИСКА 👇
|
||||||
|
const taskCreatorId = task.created_by;
|
||||||
|
availableUsers = availableUsers.filter(user =>
|
||||||
|
parseInt(user.id) !== parseInt(taskCreatorId)
|
||||||
|
);
|
||||||
|
|
||||||
// Создаем модальное окно
|
// Создаем модальное окно
|
||||||
const modalHtml = `
|
const modalHtml = `
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ function setupTaskEndpoints(app, db, upload) {
|
|||||||
|
|
||||||
// Администратор может всё
|
// Администратор может всё
|
||||||
if (user.role === 'admin') return true;
|
if (user.role === 'admin') return true;
|
||||||
|
// Пользователи с ролью 'tasks' могут редактировать любые задачи
|
||||||
|
if (user.role === 'tasks') return true;
|
||||||
|
|
||||||
// Создатель может редактировать свою задачу только если она не назначена другим
|
// Создатель может редактировать свою задачу только если она не назначена другим
|
||||||
if (parseInt(task.created_by) === user.id) {
|
if (parseInt(task.created_by) === user.id) {
|
||||||
|
|||||||
Reference in New Issue
Block a user