доработка

This commit is contained in:
2026-02-12 19:14:23 +05:00
parent 30dc1a7053
commit 139b53ffbd
4 changed files with 284 additions and 9 deletions

View File

@@ -28,7 +28,7 @@
</button>
</form>
<div class="test-users">
<h3><i class="fas fa-users"></i> Управление задачами 0.8</h3>
<h3><i class="fas fa-users"></i> Управление задачами 0.9</h3>
<ul>
<li><strong><i class="fas fa-school"></i> @2025</strong> МАОУ - СОШ № 25</li>
</ul>

View File

@@ -4135,3 +4135,44 @@ button.btn-primary {
pointer-events: none;
user-select: none;
}
/* Стили для модального окна доработки */
#rework-assignment-modal .modal-content {
max-width: 550px;
border-radius: 8px;
}
#rework-assignment-modal textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
resize: vertical;
font-family: inherit;
font-size: 14px;
}
#rework-assignment-modal textarea:focus {
border-color: #f39c12;
outline: none;
box-shadow: 0 0 5px rgba(243, 156, 18, 0.3);
}
#rework-assignment-modal .btn-warning {
background-color: #f39c12;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
#rework-assignment-modal .btn-warning:hover {
background-color: #e67e22;
}
#rework-assignment-modal .btn-warning:disabled {
background-color: #f1c40f;
cursor: not-allowed;
opacity: 0.7;
}

View File

@@ -145,8 +145,7 @@ ${task.assignments && task.assignments.length > 0 ?
`;
}).join('');
}
// Улучшенная функция рендеринга списка исполнителей с фильтрацией
// Функция для рендеринга списка исполнителей с фильтрацией
function renderAssignmentList(assignments, taskId, canEdit) {
if (!assignments || assignments.length === 0) {
return '<div>Не назначены</div>';
@@ -170,6 +169,157 @@ function renderAssignmentList(assignments, taskId, canEdit) {
`;
}
// Функция для открытия модального окна доработки для конкретного исполнителя
function openReworkAssignmentModal(taskId, userId, userName) {
const task = tasks.find(t => t.id === taskId);
if (!task) {
alert('Задача не найдена');
return;
}
// Проверяем права (только автор задачи или администратор)
if (parseInt(task.created_by) !== currentUser.id && currentUser.role !== 'admin') {
alert('Только автор задачи может отправлять на доработку');
return;
}
// Удаляем предыдущее модальное окно, если оно есть
const existingModal = document.getElementById('rework-assignment-modal');
if (existingModal) {
existingModal.remove();
}
// Создаем модальное окно
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'rework-assignment-modal';
modal.style.display = 'block';
modal.innerHTML = `
<div class="modal-content">
<div class="modal-header">
<h3>Отправить на доработку</h3>
<span class="close" onclick="closeReworkAssignmentModal()">&times;</span>
</div>
<div class="modal-body">
<p><strong>Задача:</strong> ${escapeHtml(task.title)}</p>
<p><strong>Исполнитель:</strong> ${escapeHtml(userName)}</p>
<div class="form-group">
<label for="rework-comment-${taskId}-${userId}"><strong>Комментарий к доработке:</strong></label>
<textarea id="rework-comment-${taskId}-${userId}"
class="form-control"
rows="5"
placeholder="Опишите, что нужно доработать..."></textarea>
</div>
<div class="modal-footer" style="margin-top: 20px; text-align: right;">
<button type="button" class="btn-rework-cancel" onclick="closeReworkAssignmentModal()">Отмена</button>
<button type="button" class="btn-rework-primary" onclick="submitReworkAssignment('${taskId}', '${userId}')">🔄 Отправить на доработку</button>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
// Фокусируемся на textarea
const textarea = document.getElementById(`rework-comment-${taskId}-${userId}`);
if (textarea) {
setTimeout(() => textarea.focus(), 100);
}
}
// Вспомогательная функция для экранирования HTML
function escapeHtml(text) {
if (!text) return '';
return String(text)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
// Функция для отправки на доработку конкретного исполнителя
async function submitReworkAssignment(taskId, userId) {
// Получаем textarea по уникальному ID
const textarea = document.getElementById(`rework-comment-${taskId}-${userId}`);
if (!textarea) {
alert('Ошибка: поле комментария не найдено');
return;
}
const comment = textarea.value.trim();
// Детальная отладка
console.log('=== ОТПРАВКА НА ДОРАБОТКУ ===');
console.log('Task ID:', taskId);
console.log('User ID:', userId);
console.log('Comment length:', comment.length);
console.log('Comment text:', comment);
console.log('============================');
if (!comment) {
alert('Пожалуйста, укажите комментарий к доработке');
textarea.style.border = '2px solid red';
textarea.focus();
return;
}
// Блокируем кнопку отправки
const submitBtn = document.querySelector(`#rework-assignment-modal .btn-warning`);
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = '⏳ Отправка...';
}
try {
const response = await fetch(`/api/tasks/${taskId}/rework-assignment/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
comment: comment // Отправляем явно
})
});
const data = await response.json();
if (response.ok) {
alert('✅ Исполнитель отправлен на доработку');
closeReworkAssignmentModal();
// Перезагружаем задачи
if (typeof loadTasks === 'function') {
loadTasks();
} else {
location.reload();
}
} else {
alert(`❌ Ошибка: ${data.error || 'Неизвестная ошибка'}`);
}
} catch (error) {
console.error('❌ Ошибка:', error);
alert('Сетевая ошибка при отправке на доработку: ' + error.message);
} finally {
// Разблокируем кнопку
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.innerHTML = '🔄 Отправить на доработку';
}
}
}
// Функция для закрытия модального окна доработки
function closeReworkAssignmentModal() {
const modal = document.getElementById('rework-assignment-modal');
if (modal) {
modal.style.display = 'none';
setTimeout(() => {
modal.remove();
}, 300);
}
}
// Функция для фильтрации исполнителей в конкретной задаче
function filterAssignments(taskId) {
const filterInput = document.querySelector(`.assignment-filter-input[data-task-id="${taskId}"]`);
@@ -238,6 +388,7 @@ function getTimeLeftInfo(task) {
return null;
}
// Функция для рендеринга одного исполнителя
function renderAssignment(assignment, taskId, canEdit) {
const statusClass = getStatusClass(assignment.status);
const isCurrentUser = assignment.user_id === currentUser.id;
@@ -246,6 +397,10 @@ function renderAssignment(assignment, taskId, canEdit) {
const timeLeftInfo = getAssignmentTimeLeftInfo(assignment);
// Проверяем, является ли текущий пользователь автором задачи
const task = tasks.find(t => t.id === taskId);
const isTaskCreator = task && parseInt(task.created_by) === currentUser.id;
return `
<div class="assignment ${isOverdue ? 'overdue' : ''} ${isRework ? 'rework' : ''}">
<span class="assignment-status ${statusClass}"></span>
@@ -270,6 +425,8 @@ function renderAssignment(assignment, taskId, canEdit) {
`<button onclick="updateStatus(${taskId}, ${assignment.user_id}, 'in_progress')">Приступить</button>` : ''}
${isCurrentUser && (assignment.status === 'in_progress' || assignment.status === 'overdue' || assignment.status === 'rework') ?
`<button onclick="updateStatus(${taskId}, ${assignment.user_id}, 'completed')">Выполнено</button>` : ''}
${isTaskCreator && assignment.status !== 'assigned' ?
`<button class="rework-btn" onclick="openReworkAssignmentModal(${taskId}, ${assignment.user_id}, '${assignment.user_name}')" title="Отправить на доработку">🔄</button>` : ''}
${canEdit ?
`<button class="edit-date-btn" onclick="openEditAssignmentModal(${taskId}, ${assignment.user_id})" title="Редактировать сроки">📅</button>` : ''}
</div>

View File

@@ -1269,6 +1269,83 @@ app.get('/api/document-approval-tasks', requireAuth, (req, res) => {
});
});
});
// API для отправки конкретного исполнителя на доработку
app.put('/api/tasks/:taskId/rework-assignment/:userId', requireAuth, (req, res) => {
const { taskId, userId } = req.params;
const { comment } = req.body;
const currentUserId = req.session.user.id;
if (!comment) {
return res.status(400).json({ error: 'Комментарий к доработке обязателен' });
}
db.get("SELECT created_by, status FROM tasks WHERE id = ?", [taskId], (err, task) => {
if (err || !task) {
return res.status(404).json({ error: 'Задача не найдена' });
}
// Проверяем права: автор задачи или администратор
if (req.session.user.role !== 'admin' && parseInt(task.created_by) !== currentUserId) {
return res.status(403).json({ error: 'Только автор задачи может отправлять на доработку' });
}
db.serialize(() => {
// Обновляем статус конкретного исполнителя
db.run(
`UPDATE task_assignments
SET status = 'rework',
rework_comment = ?,
updated_at = CURRENT_TIMESTAMP
WHERE task_id = ? AND user_id = ?`,
[comment, taskId, userId],
function(err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
if (this.changes === 0) {
return res.status(404).json({ error: 'Исполнитель не найден в задаче' });
}
// Логируем действие
const { logActivity } = require('./database');
if (logActivity) {
logActivity(taskId, currentUserId, 'ASSIGNMENT_REWORK',
`Исполнитель ${userId} отправлен на доработку: ${comment}`);
}
// Получаем данные для уведомления
db.get(`SELECT t.title, t.description, u.name as executor_name
FROM tasks t
LEFT JOIN users u ON u.id = ?
WHERE t.id = ?`,
[userId, taskId], (err, taskData) => {
if (!err && taskData) {
const { sendTaskNotifications } = require('./notifications');
sendTaskNotifications(
'rework_assignment',
taskId,
taskData.title,
taskData.description,
currentUserId,
comment,
'rework',
req.session.user.name,
userId // ID исполнителя для персонального уведомления
);
}
});
res.json({
success: true,
message: 'Исполнитель отправлен на доработку'
});
}
);
});
});
});
app.post('/api/tasks/:taskId/files', requireAuth, upload.array('files', 15), (req, res) => {
const { taskId } = req.params;