документы
This commit is contained in:
@@ -92,8 +92,10 @@ function reloadAllScripts() {
|
||||
//console.log('🔄 Перезагрузка всех скриптов после авторизации...');
|
||||
|
||||
// Список скриптов для перезагрузки (в правильном порядке)
|
||||
// 'loading-start.js',
|
||||
// 'document-fields.js',
|
||||
const scriptsToReload = [
|
||||
'loading-start.js',
|
||||
|
||||
'users.js',
|
||||
'ui.js',
|
||||
'signature.js',
|
||||
@@ -106,7 +108,6 @@ function reloadAllScripts() {
|
||||
'tasks_files.js',
|
||||
'navbar.js',
|
||||
'chat-ui.js',
|
||||
'document-fields.js',
|
||||
'main.js'
|
||||
];
|
||||
|
||||
|
||||
234
public/style.css
234
public/style.css
@@ -4421,3 +4421,237 @@ button.btn-primary {
|
||||
50% { opacity: 1; }
|
||||
100% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
.document-fields-container {
|
||||
margin: 12px 0;
|
||||
padding: 12px 15px;
|
||||
background: linear-gradient(135deg, #f8f9ff 0%, #f0f2fa 100%);
|
||||
border-left: 4px solid #4a90e2;
|
||||
border-radius: 0 8px 8px 0;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||||
font-size: 0.95em;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.document-fields-container.empty {
|
||||
background: linear-gradient(135deg, #f9f9f9 0%, #f0f0f0 100%);
|
||||
border-left-color: #9e9e9e;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.document-fields-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px dashed rgba(74, 144, 226, 0.3);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.document-fields-container.empty .document-fields-header {
|
||||
border-bottom-color: rgba(158, 158, 158, 0.3);
|
||||
}
|
||||
|
||||
.document-icon {
|
||||
font-size: 1.2em;
|
||||
margin-right: 8px;
|
||||
filter: drop-shadow(0 2px 2px rgba(0,0,0,0.1));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.document-title {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.85em;
|
||||
letter-spacing: 0.5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.document-fields-container.empty .document-title {
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* Горизонтальное расположение полей */
|
||||
.document-fields-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 20px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Стили для каждого поля - текст по центру вертикально */
|
||||
.document-field {
|
||||
display: inline-flex;
|
||||
align-items: center; /* Выравнивание по центру вертикально */
|
||||
background: white;
|
||||
padding: 4px 14px; /* Уменьшил верхний/нижний padding для лучшего выравнивания */
|
||||
border-radius: 30px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
border: 1px solid rgba(0,0,0,0.03);
|
||||
transition: all 0.2s ease;
|
||||
max-width: 100%;
|
||||
flex: 0 1 auto;
|
||||
line-height: 1.4; /* Добавил для единообразия высоты строки */
|
||||
}
|
||||
|
||||
.document-field:hover {
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.document-field.long-value {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.document-fields-container.empty .document-field {
|
||||
background: rgba(255,255,255,0.7);
|
||||
}
|
||||
|
||||
.field-label {
|
||||
font-weight: 500;
|
||||
color: #7f8c8d;
|
||||
margin-right: 6px;
|
||||
font-size: 0.9em;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
line-height: 1.4; /* Единообразие высоты строки */
|
||||
}
|
||||
|
||||
.field-value {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200px;
|
||||
line-height: 1.4; /* Единообразие высоты строки */
|
||||
padding: 2px 0; /* Небольшой отступ для лучшего выравнивания */
|
||||
}
|
||||
|
||||
.document-field.long-value .field-value {
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
/* Специфичные стили для разных типов полей */
|
||||
.document-number {
|
||||
color: #e67e22;
|
||||
background: rgba(230, 126, 34, 0.1);
|
||||
padding: 2px 10px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.document-date {
|
||||
color: #27ae60;
|
||||
background: rgba(39, 174, 96, 0.1);
|
||||
padding: 2px 10px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.document-author {
|
||||
color: #3498db;
|
||||
background: rgba(52, 152, 219, 0.1);
|
||||
padding: 2px 10px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Стили для пустого состояния */
|
||||
.document-fields-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
color: #95a5a6;
|
||||
font-style: italic;
|
||||
font-size: 0.9em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.empty-message::before {
|
||||
content: "⏳";
|
||||
font-size: 1.1em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Анимация появления */
|
||||
.document-fields-container {
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Адаптивность для мобильных и узких экранов */
|
||||
@media (max-width: 768px) {
|
||||
.document-fields-content {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.document-field {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
justify-content: flex-start; /* Выравнивание по левому краю на мобильных */
|
||||
}
|
||||
|
||||
.document-field.long-value {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
max-width: calc(100% - 60px);
|
||||
}
|
||||
|
||||
.document-field.long-value .field-value {
|
||||
max-width: calc(100% - 60px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Для очень узких экранов */
|
||||
@media (max-width: 480px) {
|
||||
.document-fields-header {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.document-title {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.document-field {
|
||||
flex-wrap: wrap;
|
||||
padding: 8px 12px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
106
public/tasks.js
106
public/tasks.js
@@ -22,11 +22,11 @@ async function loadTasks() {
|
||||
}
|
||||
|
||||
if (creatorFilter) {
|
||||
params.push(`creator=${encodeURIComponent(creatorFilter)}`); // было creator_id, теперь creator
|
||||
params.push(`creator=${encodeURIComponent(creatorFilter)}`);
|
||||
}
|
||||
|
||||
if (assigneeFilter) {
|
||||
params.push(`assignee=${encodeURIComponent(assigneeFilter)}`); // было assignee_id, теперь assignee
|
||||
params.push(`assignee=${encodeURIComponent(assigneeFilter)}`);
|
||||
}
|
||||
|
||||
if (deadlineFilter) {
|
||||
@@ -38,7 +38,7 @@ async function loadTasks() {
|
||||
}
|
||||
|
||||
if (typeFilter) {
|
||||
params.push(`task_type=${encodeURIComponent(typeFilter)}`); // было type, теперь task_type
|
||||
params.push(`task_type=${encodeURIComponent(typeFilter)}`);
|
||||
}
|
||||
|
||||
url += params.join('&');
|
||||
@@ -53,6 +53,12 @@ async function loadTasks() {
|
||||
tasks = await response.json();
|
||||
console.log(`Загружено ${tasks.length} задач`);
|
||||
|
||||
// Загружаем поля документа для задач типа "document"
|
||||
await loadDocumentFieldsForTasks();
|
||||
|
||||
// Загружаем файлы для развернутых задач
|
||||
await loadFilesForExpandedTasks();
|
||||
|
||||
// Обновляем отображение
|
||||
const activeSection = document.querySelector('.section.active');
|
||||
if (activeSection) {
|
||||
@@ -77,12 +83,64 @@ async function loadTasks() {
|
||||
}
|
||||
}
|
||||
|
||||
// Новая функция для загрузки полей документов
|
||||
async function loadDocumentFieldsForTasks() {
|
||||
const documentTasks = tasks.filter(task => task.task_type === 'document');
|
||||
|
||||
if (documentTasks.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Загрузка полей документов для ${documentTasks.length} задач`);
|
||||
|
||||
// Загружаем поля для каждой задачи
|
||||
for (const task of documentTasks) {
|
||||
try {
|
||||
const docResponse = await fetch(`/api/tasks/${task.id}/document-fields`);
|
||||
if (docResponse.ok) {
|
||||
const docData = await docResponse.json();
|
||||
task.document_fields = docData.data || {};
|
||||
console.log(`✅ Загружены поля для задачи ${task.id}:`, task.document_fields);
|
||||
} else {
|
||||
task.document_fields = {};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Ошибка загрузки полей документа для задачи ${task.id}:`, error);
|
||||
task.document_fields = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Новая функция для загрузки файлов развернутых задач
|
||||
async function loadFilesForExpandedTasks() {
|
||||
const expandedTasksArray = tasks.filter(task => expandedTasks.has(task.id));
|
||||
|
||||
if (expandedTasksArray.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(expandedTasksArray.map(async (task) => {
|
||||
try {
|
||||
const filesResponse = await fetch(`/api/tasks/${task.id}/files`);
|
||||
if (filesResponse.ok) {
|
||||
task.files = await filesResponse.json();
|
||||
} else {
|
||||
task.files = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Ошибка загрузки файлов для задачи ${task.id}:`, error);
|
||||
task.files = [];
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function showTasksWithoutDate() {
|
||||
showingTasksWithoutDate = true;
|
||||
const btn = document.getElementById('tasks-no-date-btn');
|
||||
if (btn) btn.classList.add('active');
|
||||
loadTasksWithoutDate();
|
||||
}
|
||||
|
||||
function resetAllFilters() {
|
||||
document.getElementById('status-filter').value = 'active,in_progress,assigned,overdue,rework';
|
||||
document.getElementById('creator-filter').value = '';
|
||||
@@ -92,6 +150,7 @@ function resetAllFilters() {
|
||||
document.getElementById('search-tasks').value = '';
|
||||
loadTasks();
|
||||
}
|
||||
|
||||
async function loadTasksWithoutDate() {
|
||||
try {
|
||||
const response = await fetch('/api/tasks');
|
||||
@@ -120,14 +179,15 @@ async function loadTasksWithoutDate() {
|
||||
}
|
||||
}));
|
||||
|
||||
// Загружаем поля документов
|
||||
await loadDocumentFieldsForTasks();
|
||||
|
||||
renderTasks();
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки задач без срока:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// удален async function createTask(event)
|
||||
|
||||
async function openEditModal(taskId) {
|
||||
try {
|
||||
const response = await fetch(`/api/tasks/${taskId}`);
|
||||
@@ -188,7 +248,6 @@ async function updateTask(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Используем editSelectedUsers
|
||||
const assignedUserIds = editSelectedUsers;
|
||||
|
||||
const formData = new FormData();
|
||||
@@ -226,12 +285,10 @@ async function updateTask(event) {
|
||||
function openCopyModal(taskId) {
|
||||
document.getElementById('copy-task-id').value = taskId;
|
||||
|
||||
// Устанавливаем дату по умолчанию (через 7 дней)
|
||||
const defaultDate = new Date();
|
||||
defaultDate.setDate(defaultDate.getDate() + 7);
|
||||
document.getElementById('copy-due-date').value = defaultDate.toISOString().substring(0, 16);
|
||||
|
||||
// Сбрасываем выбранных пользователей
|
||||
copySelectedUsers = [];
|
||||
renderCopyUsersChecklist(users);
|
||||
|
||||
@@ -256,7 +313,6 @@ async function copyTask(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Используем copySelectedUsers
|
||||
const assignedUserIds = copySelectedUsers;
|
||||
|
||||
if (assignedUserIds.length === 0) {
|
||||
@@ -501,53 +557,41 @@ async function updateStatus(taskId, userId, status) {
|
||||
function canUserEditTask(task) {
|
||||
if (!currentUser) return false;
|
||||
|
||||
// Администратор может всё
|
||||
if (currentUser.role === 'admin') return true;
|
||||
// Пользователи с ролью 'tasks' могут редактировать любые задачи
|
||||
if (currentUser.role === 'tasks') return true;
|
||||
|
||||
// Создатель может редактировать свою задачу
|
||||
if (parseInt(task.created_by) === currentUser.id) {
|
||||
// Но если задача уже назначена другим пользователям,
|
||||
// создатель может только просматривать
|
||||
if (task.assignments && task.assignments.length > 0) {
|
||||
// Проверяем, назначена ли задача другим пользователям (не только себе)
|
||||
const assignedToOthers = task.assignments.some(assignment =>
|
||||
parseInt(assignment.user_id) !== currentUser.id
|
||||
);
|
||||
|
||||
if (assignedToOthers) {
|
||||
// Создатель может только просматривать и закрывать задачу
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Исполнитель может менять только свой статус
|
||||
if (task.assignments) {
|
||||
const isExecutor = task.assignments.some(assignment =>
|
||||
parseInt(assignment.user_id) === currentUser.id
|
||||
);
|
||||
if (isExecutor) {
|
||||
// Исполнитель может менять только статус
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// функция для проверки прав на добавление файлов
|
||||
|
||||
function canUserAddFilesToTask(task) {
|
||||
if (!currentUser) return false;
|
||||
|
||||
// Администратор может всё
|
||||
if (currentUser.role === 'admin') return true;
|
||||
|
||||
// Создатель задачи может добавлять файлы
|
||||
if (parseInt(task.created_by) === currentUser.id) return true;
|
||||
|
||||
// Исполнитель задачи может добавлять файлы
|
||||
if (task.assignments) {
|
||||
const isExecutor = task.assignments.some(assignment =>
|
||||
parseInt(assignment.user_id) === currentUser.id
|
||||
@@ -557,3 +601,21 @@ function canUserAddFilesToTask(task) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Добавляем отладочную функцию
|
||||
function debugDocumentFields() {
|
||||
console.log('=== ОТЛАДКА ПОЛЕЙ ДОКУМЕНТОВ ===');
|
||||
const documentTasks = tasks.filter(task => task.task_type === 'document');
|
||||
console.log(`Найдено ${documentTasks.length} задач типа "document"`);
|
||||
|
||||
documentTasks.forEach(task => {
|
||||
console.log(`Задача ${task.id}:`, {
|
||||
title: task.title,
|
||||
document_fields: task.document_fields || 'НЕТ ПОЛЕЙ'
|
||||
});
|
||||
});
|
||||
console.log('================================');
|
||||
}
|
||||
|
||||
// Вызываем отладку после загрузки (можно вызвать вручную из консоли)
|
||||
window.debugDocumentFields = debugDocumentFields;
|
||||
579
public/ui.js
579
public/ui.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user