task-file
This commit is contained in:
@@ -258,8 +258,10 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="copy-due-date">Дата выполнения:</label>
|
||||
<input type="date" id="copy-due-date" name="dueDate" required>
|
||||
|
||||
<input type="date" class="date-btn" id="copy-due-date" name="dueDate" required>
|
||||
<input type="hidden" id="copy-due-time" name="dueTime" value="19:00">
|
||||
<input type="text" id="copy-user-search" placeholder="Поиск исполнителей..." oninput="filterCopyUsers()">
|
||||
<!--
|
||||
<div class="time-buttons">
|
||||
<button type="button" class="copy-time-btn active" onclick="setCopyTaskTime('12:00')">
|
||||
<i class="fas fa-sun"></i> До обеда (12:00)
|
||||
@@ -268,14 +270,14 @@
|
||||
<i class="fas fa-moon"></i> После обеда (19:00)
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" id="copy-due-time" name="dueTime" value="12:00">
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Назначить исполнителей для копии:</label>
|
||||
<!-- <label>Назначить исполнителей для копии:</label>
|
||||
<div class="user-search">
|
||||
<input type="text" id="copy-user-search" placeholder="Поиск исполнителей..." oninput="filterCopyUsers()">
|
||||
</div>
|
||||
</div> -->
|
||||
<div id="copy-users-checklist" class="checkbox-group"></div>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">
|
||||
|
||||
@@ -2397,7 +2397,6 @@ small {
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.time-btn:hover {
|
||||
background: #e9ecef;
|
||||
border-color: #dee2e6;
|
||||
|
||||
@@ -537,7 +537,7 @@ function canUserEditTask(task) {
|
||||
|
||||
if (assignedToOthers) {
|
||||
// Создатель может только просматривать и закрывать задачу
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -554,5 +554,25 @@ function canUserEditTask(task) {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
return isExecutor;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
190
public/ui.js
190
public/ui.js
@@ -79,6 +79,7 @@ function renderTasks() {
|
||||
<div class="task-content ${isExpanded ? 'expanded' : ''}">
|
||||
<div class="task-actions">
|
||||
${!isDeleted && !isClosed ? `
|
||||
${canEdit ? `<button class="add-file-btn" onclick="openAddFileModal(${task.id})" title="Добавить файл">📎</button>` : ''}
|
||||
${canEdit ? `<button class="edit-btn" onclick="openEditModal(${task.id})" title="Редактировать">✏️</button>` : ''}
|
||||
<button class="copy-btn" onclick="openCopyModal(${task.id})" title="Создать копию">📋</button>
|
||||
${canEdit ? `<button class="rework-btn" onclick="openReworkModal(${task.id})" title="Вернуть на доработку">🔄</button>` : ''}
|
||||
@@ -246,7 +247,7 @@ function renderAssignment(assignment, taskId, canEdit) {
|
||||
${assignment.start_date || assignment.due_date ? `
|
||||
<div class="assignment-dates">
|
||||
${assignment.start_date ? `<small>Начало: ${formatDateTime(assignment.start_date)}</small>` : ''}
|
||||
${assignment.due_date ? `<small>2Выполнить до: ${formatDateTime(assignment.due_date)}</small>` : ''}
|
||||
${assignment.due_date ? `<small>Выполнить до: ${formatDateTime(assignment.due_date)}</small>` : ''}
|
||||
</div>
|
||||
` : ''}
|
||||
${assignment.rework_comment ? `
|
||||
@@ -363,15 +364,8 @@ function getUserRoleInTask(task) {
|
||||
|
||||
if (currentUser.role === 'admin') return 'Администратор';
|
||||
|
||||
// Создатель задачи всегда должен иметь право редактировать свою задачу
|
||||
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 'Создатель (только просмотр)';
|
||||
}
|
||||
}
|
||||
return 'Создатель';
|
||||
}
|
||||
|
||||
@@ -468,4 +462,182 @@ async function viewTaskDetails(taskId) {
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки деталей задачи:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для открытия модального окна добавления файла
|
||||
function openAddFileModal(taskId) {
|
||||
// Находим задачу
|
||||
const task = tasks.find(t => t.id === taskId);
|
||||
if (!task) {
|
||||
alert('Задача не найдена');
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем права
|
||||
if (!canUserAddFilesToTask(task)) {
|
||||
alert('У вас нет прав для добавления файлов к этой задаче');
|
||||
return;
|
||||
}
|
||||
|
||||
// Создаем модальное окно
|
||||
const modalHtml = `
|
||||
<div class="modal" id="add-file-modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>Добавить файл к задаче: "${task.title}"</h3>
|
||||
<span class="close" onclick="closeAddFileModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="add-file-form">
|
||||
<input type="hidden" name="task_id" value="${taskId}">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="file-input">Выберите файл:</label>
|
||||
<input type="file" id="file-input" name="files" multiple>
|
||||
<small>Максимальный размер: 10MB</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="file-description">Описание файла (необязательно):</label>
|
||||
<textarea id="file-description" name="description" rows="3"
|
||||
placeholder="Описание файла..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn-cancel" onclick="closeAddFileModal()">Отмена</button>
|
||||
<button type="submit" class="btn-primary">Добавить файл</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Добавляем модальное окно в DOM
|
||||
const modalContainer = document.createElement('div');
|
||||
modalContainer.innerHTML = modalHtml;
|
||||
document.body.appendChild(modalContainer);
|
||||
|
||||
// Обработчик отправки формы
|
||||
document.getElementById('add-file-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const fileInput = document.getElementById('file-input');
|
||||
const description = document.getElementById('file-description').value;
|
||||
|
||||
if (fileInput.files.length === 0) {
|
||||
alert('Выберите файл для загрузки');
|
||||
return;
|
||||
}
|
||||
|
||||
const file = fileInput.files[0];
|
||||
const formData = new FormData();
|
||||
|
||||
// Пробуем сначала 'files', затем 'file'
|
||||
formData.append('files', file); // Или 'file' в зависимости от сервера
|
||||
formData.append('task_id', taskId);
|
||||
if (description) {
|
||||
formData.append('description', description);
|
||||
}
|
||||
|
||||
try {
|
||||
// Попробуем с полем 'files' (скорее всего это правильное имя)
|
||||
let response = await fetch(`/api/tasks/${taskId}/files`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// Попробуем с полем 'file'
|
||||
console.log('Попытка с именем поля "file"...');
|
||||
formData.delete('files');
|
||||
formData.append('file', file);
|
||||
|
||||
response = await fetch(`/api/tasks/${taskId}/files`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
alert('Файл успешно добавлен');
|
||||
await loadTaskFiles(taskId);
|
||||
closeAddFileModal();
|
||||
|
||||
// Обновляем задачу, если она развернута
|
||||
if (expandedTasks.has(taskId)) {
|
||||
renderTasks();
|
||||
}
|
||||
} else {
|
||||
const errorText = await response.text();
|
||||
console.error('Ошибка сервера:', errorText);
|
||||
alert(`Ошибка при добавлении файла: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка:', error);
|
||||
alert('Сетевая ошибка при добавлении файла: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// Показываем модальное окно
|
||||
setTimeout(() => {
|
||||
document.getElementById('add-file-modal').style.display = 'block';
|
||||
}, 10);
|
||||
}
|
||||
|
||||
// Функция для закрытия модального окна добавления файла
|
||||
function closeAddFileModal() {
|
||||
const modal = document.getElementById('add-file-modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
// Удаляем модальное окно из DOM через некоторое время
|
||||
setTimeout(() => {
|
||||
modal.parentElement.remove();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для закрытия модального окна добавления файла
|
||||
function closeAddFileModal() {
|
||||
const modal = document.getElementById('add-file-modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
// Удаляем модальное окно из DOM через некоторое время
|
||||
setTimeout(() => {
|
||||
modal.parentElement.remove();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для загрузки файлов задачи (если её еще нет)
|
||||
async function loadTaskFiles(taskId) {
|
||||
try {
|
||||
const response = await fetch(`/api/tasks/${taskId}/files`);
|
||||
const files = await response.json();
|
||||
|
||||
// Обновляем файлы в задаче
|
||||
const taskIndex = tasks.findIndex(t => t.id === taskId);
|
||||
if (taskIndex !== -1) {
|
||||
tasks[taskIndex].files = files;
|
||||
}
|
||||
|
||||
// Обновляем отображение файлов
|
||||
const fileContainer = document.getElementById(`files-${taskId}`);
|
||||
if (fileContainer) {
|
||||
const fileList = fileContainer.querySelector('.file-icons-container');
|
||||
if (fileList) {
|
||||
fileList.innerHTML = files.map(file => renderFileIcon(file)).join('');
|
||||
} else {
|
||||
fileContainer.innerHTML = `
|
||||
<strong>Файлы:</strong>
|
||||
${files.length > 0 ?
|
||||
`<div class="file-icons-container">${files.map(file => renderFileIcon(file)).join('')}</div>` :
|
||||
'<span class="no-files">нет файлов</span>'
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки файлов:', error);
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,16 @@ function setupTaskEndpoints(app, db, upload) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Исполнитель может загружать файлы в свою задачу
|
||||
// Проверяем, назначен ли пользователь на эту задачу
|
||||
if (task.assignments && task.assignments.length > 0) {
|
||||
const isAssigned = task.assignments.some(assignment =>
|
||||
parseInt(assignment.user_id) === user.id
|
||||
);
|
||||
if (isAssigned) {
|
||||
return true; // Исполнитель может загружать файлы
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user