доработка
This commit is contained in:
@@ -28,7 +28,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="test-users">
|
<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>
|
<ul>
|
||||||
<li><strong><i class="fas fa-school"></i> @2025</strong> МАОУ - СОШ № 25</li>
|
<li><strong><i class="fas fa-school"></i> @2025</strong> МАОУ - СОШ № 25</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -4134,4 +4134,45 @@ button.btn-primary {
|
|||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: 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('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
// Функция для рендеринга списка исполнителей с фильтрацией
|
||||||
// Улучшенная функция рендеринга списка исполнителей с фильтрацией
|
|
||||||
function renderAssignmentList(assignments, taskId, canEdit) {
|
function renderAssignmentList(assignments, taskId, canEdit) {
|
||||||
if (!assignments || assignments.length === 0) {
|
if (!assignments || assignments.length === 0) {
|
||||||
return '<div>Не назначены</div>';
|
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) {
|
function filterAssignments(taskId) {
|
||||||
const filterInput = document.querySelector(`.assignment-filter-input[data-task-id="${taskId}"]`);
|
const filterInput = document.querySelector(`.assignment-filter-input[data-task-id="${taskId}"]`);
|
||||||
@@ -238,6 +388,7 @@ function getTimeLeftInfo(task) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция для рендеринга одного исполнителя
|
||||||
function renderAssignment(assignment, taskId, canEdit) {
|
function renderAssignment(assignment, taskId, canEdit) {
|
||||||
const statusClass = getStatusClass(assignment.status);
|
const statusClass = getStatusClass(assignment.status);
|
||||||
const isCurrentUser = assignment.user_id === currentUser.id;
|
const isCurrentUser = assignment.user_id === currentUser.id;
|
||||||
@@ -246,6 +397,10 @@ function renderAssignment(assignment, taskId, canEdit) {
|
|||||||
|
|
||||||
const timeLeftInfo = getAssignmentTimeLeftInfo(assignment);
|
const timeLeftInfo = getAssignmentTimeLeftInfo(assignment);
|
||||||
|
|
||||||
|
// Проверяем, является ли текущий пользователь автором задачи
|
||||||
|
const task = tasks.find(t => t.id === taskId);
|
||||||
|
const isTaskCreator = task && parseInt(task.created_by) === currentUser.id;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="assignment ${isOverdue ? 'overdue' : ''} ${isRework ? 'rework' : ''}">
|
<div class="assignment ${isOverdue ? 'overdue' : ''} ${isRework ? 'rework' : ''}">
|
||||||
<span class="assignment-status ${statusClass}"></span>
|
<span class="assignment-status ${statusClass}"></span>
|
||||||
@@ -266,12 +421,14 @@ function renderAssignment(assignment, taskId, canEdit) {
|
|||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
${isCurrentUser && assignment.status === 'assigned' ?
|
${isCurrentUser && assignment.status === 'assigned' ?
|
||||||
`<button onclick="updateStatus(${taskId}, ${assignment.user_id}, 'in_progress')">Приступить</button>` : ''}
|
`<button onclick="updateStatus(${taskId}, ${assignment.user_id}, 'in_progress')">Приступить</button>` : ''}
|
||||||
${isCurrentUser && (assignment.status === 'in_progress' || assignment.status === 'overdue' || assignment.status === 'rework') ?
|
${isCurrentUser && (assignment.status === 'in_progress' || assignment.status === 'overdue' || assignment.status === 'rework') ?
|
||||||
`<button onclick="updateStatus(${taskId}, ${assignment.user_id}, 'completed')">Выполнено</button>` : ''}
|
`<button onclick="updateStatus(${taskId}, ${assignment.user_id}, 'completed')">Выполнено</button>` : ''}
|
||||||
${canEdit ?
|
${isTaskCreator && assignment.status !== 'assigned' ?
|
||||||
`<button class="edit-date-btn" onclick="openEditAssignmentModal(${taskId}, ${assignment.user_id})" title="Редактировать сроки">📅</button>` : ''}
|
`<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>
|
||||||
</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) => {
|
app.post('/api/tasks/:taskId/files', requireAuth, upload.array('files', 15), (req, res) => {
|
||||||
const { taskId } = req.params;
|
const { taskId } = req.params;
|
||||||
|
|||||||
Reference in New Issue
Block a user