доработка
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -4134,4 +4134,45 @@ button.btn-primary {
|
||||
opacity: 0.3;
|
||||
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;
|
||||
}
|
||||
173
public/ui.js
173
public/ui.js
@@ -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()">×</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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
// Функция для отправки на доработку конкретного исполнителя
|
||||
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>
|
||||
@@ -266,12 +421,14 @@ function renderAssignment(assignment, taskId, canEdit) {
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
${isCurrentUser && assignment.status === 'assigned' ?
|
||||
`<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>` : ''}
|
||||
${canEdit ?
|
||||
`<button class="edit-date-btn" onclick="openEditAssignmentModal(${taskId}, ${assignment.user_id})" title="Редактировать сроки">📅</button>` : ''}
|
||||
${isCurrentUser && assignment.status === 'assigned' ?
|
||||
`<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>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user