реквизиты
This commit is contained in:
605
public/document-fields.js
Normal file
605
public/document-fields.js
Normal file
@@ -0,0 +1,605 @@
|
|||||||
|
// document-fields.js - Скрипт для управления полями документа в задачах
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Конфигурация
|
||||||
|
const CONFIG = {
|
||||||
|
modalId: 'documentFieldsModal',
|
||||||
|
modalStyles: `
|
||||||
|
<style>
|
||||||
|
.document-fields-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
z-index: 10000;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-modal.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-content {
|
||||||
|
background: white;
|
||||||
|
padding: 25px;
|
||||||
|
border-radius: 8px;
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
|
||||||
|
animation: slideIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateY(-20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-close:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-form {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-field-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-field-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #555;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-field-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-field-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4CAF50;
|
||||||
|
box-shadow: 0 0 0 2px rgba(76,175,80,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-field-group input[readonly] {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.95em;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-btn.save {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-btn.save:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-btn.cancel {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-btn.cancel:hover {
|
||||||
|
background-color: #da190b;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 30px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-error {
|
||||||
|
background-color: #ffebee;
|
||||||
|
color: #c62828;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border: 1px solid #ef9a9a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-success {
|
||||||
|
background-color: #e8f5e9;
|
||||||
|
color: #2e7d32;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border: 1px solid #a5d6a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-info {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-info strong {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`,
|
||||||
|
buttonStyles: `
|
||||||
|
<style>
|
||||||
|
.document-fields-btn-icon {
|
||||||
|
background-color: #2196F3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin: 0 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9em;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-btn-icon:hover {
|
||||||
|
background-color: #1976D2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-btn-icon.document {
|
||||||
|
background-color: #9C27B0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-btn-icon.document:hover {
|
||||||
|
background-color: #7B1FA2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-fields-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
color: #1976d2;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
// Текущий пользователь
|
||||||
|
let currentUser = null;
|
||||||
|
|
||||||
|
// Получение текущего пользователя
|
||||||
|
async function getCurrentUser() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/user');
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.user) {
|
||||||
|
currentUser = data.user;
|
||||||
|
console.log('✅ DocumentFields: текущий пользователь', currentUser);
|
||||||
|
}
|
||||||
|
return currentUser;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ DocumentFields: ошибка получения пользователя', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создание модального окна
|
||||||
|
function createModal() {
|
||||||
|
// Добавляем стили
|
||||||
|
if (!document.getElementById('document-fields-styles')) {
|
||||||
|
const styleElement = document.createElement('div');
|
||||||
|
styleElement.id = 'document-fields-styles';
|
||||||
|
styleElement.innerHTML = CONFIG.modalStyles + CONFIG.buttonStyles;
|
||||||
|
document.head.appendChild(styleElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, существует ли уже модальное окно
|
||||||
|
if (document.getElementById(CONFIG.modalId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalHTML = `
|
||||||
|
<div id="${CONFIG.modalId}" class="document-fields-modal">
|
||||||
|
<div class="document-fields-content">
|
||||||
|
<div class="document-fields-header">
|
||||||
|
<h3>📄 Реквизиты документа</h3>
|
||||||
|
<button class="document-fields-close" onclick="documentFields.closeModal()">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="documentFieldsMessage" class="document-fields-success" style="display: none;"></div>
|
||||||
|
<div id="documentFieldsError" class="document-fields-error" style="display: none;"></div>
|
||||||
|
|
||||||
|
<div id="documentFieldsLoading" class="document-fields-loading" style="display: none;">
|
||||||
|
Загрузка...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="documentFieldsForm" class="document-fields-form" style="display: none;">
|
||||||
|
<div class="document-fields-info">
|
||||||
|
<strong>ID задачи:</strong> <span id="documentTaskId"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="document-field-group">
|
||||||
|
<label for="documentNumber">Номер документа:</label>
|
||||||
|
<input type="text" id="documentNumber" placeholder="Введите номер документа" maxlength="100">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="document-field-group">
|
||||||
|
<label for="documentDate">Дата документа:</label>
|
||||||
|
<input type="text" id="documentDate" placeholder="ДД.ММ.ГГГГ" maxlength="10">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="document-field-group">
|
||||||
|
<label for="documentAuthor">Автор (логин):</label>
|
||||||
|
<input type="text" id="documentAuthor" placeholder="Логин автора" readonly>
|
||||||
|
<small style="color: #666;">Автоматически устанавливается ваш логин</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="document-fields-buttons">
|
||||||
|
<button class="document-fields-btn cancel" onclick="documentFields.closeModal()">Отмена</button>
|
||||||
|
<button class="document-fields-btn save" onclick="documentFields.saveFields()">Сохранить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Открытие модального окна
|
||||||
|
async function openModal(taskId) {
|
||||||
|
const modal = document.getElementById(CONFIG.modalId);
|
||||||
|
if (!modal) {
|
||||||
|
createModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalElement = document.getElementById(CONFIG.modalId);
|
||||||
|
const form = document.getElementById('documentFieldsForm');
|
||||||
|
const loading = document.getElementById('documentFieldsLoading');
|
||||||
|
const errorDiv = document.getElementById('documentFieldsError');
|
||||||
|
const messageDiv = document.getElementById('documentFieldsMessage');
|
||||||
|
|
||||||
|
// Скрываем предыдущие сообщения
|
||||||
|
errorDiv.style.display = 'none';
|
||||||
|
messageDiv.style.display = 'none';
|
||||||
|
|
||||||
|
// Показываем загрузку
|
||||||
|
form.style.display = 'none';
|
||||||
|
loading.style.display = 'block';
|
||||||
|
|
||||||
|
// Активируем модальное окно
|
||||||
|
modalElement.classList.add('active');
|
||||||
|
|
||||||
|
// Устанавливаем ID задачи
|
||||||
|
document.getElementById('documentTaskId').textContent = taskId;
|
||||||
|
|
||||||
|
// Устанавливаем автора (логин текущего пользователя)
|
||||||
|
const authorInput = document.getElementById('documentAuthor');
|
||||||
|
if (currentUser) {
|
||||||
|
authorInput.value = currentUser.login || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Загружаем текущие значения
|
||||||
|
const response = await fetch(`/api/tasks/${taskId}/document-fields`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка загрузки данных');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
document.getElementById('documentNumber').value = result.data.document_n || '';
|
||||||
|
document.getElementById('documentDate').value = result.data.document_d || '';
|
||||||
|
// Автор из БД может отличаться от текущего пользователя
|
||||||
|
if (result.data.document_a && !authorInput.value) {
|
||||||
|
authorInput.value = result.data.document_a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка загрузки полей документа:', error);
|
||||||
|
showError('Ошибка загрузки данных: ' + error.message);
|
||||||
|
} finally {
|
||||||
|
// Скрываем загрузку, показываем форму
|
||||||
|
loading.style.display = 'none';
|
||||||
|
form.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Закрытие модального окна
|
||||||
|
function closeModal() {
|
||||||
|
const modal = document.getElementById(CONFIG.modalId);
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показать ошибку
|
||||||
|
function showError(message) {
|
||||||
|
const errorDiv = document.getElementById('documentFieldsError');
|
||||||
|
errorDiv.textContent = message;
|
||||||
|
errorDiv.style.display = 'block';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
errorDiv.style.display = 'none';
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показать успех
|
||||||
|
function showSuccess(message) {
|
||||||
|
const messageDiv = document.getElementById('documentFieldsMessage');
|
||||||
|
messageDiv.textContent = message;
|
||||||
|
messageDiv.style.display = 'block';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
messageDiv.style.display = 'none';
|
||||||
|
closeModal();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Валидация даты
|
||||||
|
function validateDate(dateStr) {
|
||||||
|
if (!dateStr) return true; // Пустая дата допустима
|
||||||
|
|
||||||
|
// Проверка формата ДД.ММ.ГГГГ
|
||||||
|
const datePattern = /^(\d{2})\.(\d{2})\.(\d{4})$/;
|
||||||
|
if (!datePattern.test(dateStr)) {
|
||||||
|
return 'Неверный формат даты. Используйте ДД.ММ.ГГГГ';
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, day, month, year] = dateStr.match(datePattern);
|
||||||
|
const date = new Date(year, month - 1, day);
|
||||||
|
|
||||||
|
if (date.getDate() != day || date.getMonth() + 1 != month || date.getFullYear() != year) {
|
||||||
|
return 'Некорректная дата';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохранение полей
|
||||||
|
async function saveFields() {
|
||||||
|
const taskId = document.getElementById('documentTaskId').textContent;
|
||||||
|
const document_n = document.getElementById('documentNumber').value.trim();
|
||||||
|
let document_d = document.getElementById('documentDate').value.trim();
|
||||||
|
const document_a = document.getElementById('documentAuthor').value.trim() || currentUser?.login;
|
||||||
|
|
||||||
|
// Валидация даты
|
||||||
|
if (document_d) {
|
||||||
|
const dateValidation = validateDate(document_d);
|
||||||
|
if (dateValidation !== true) {
|
||||||
|
showError(dateValidation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показываем загрузку
|
||||||
|
const saveBtn = document.querySelector('.document-fields-btn.save');
|
||||||
|
const originalText = saveBtn.textContent;
|
||||||
|
saveBtn.textContent = 'Сохранение...';
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/tasks/${taskId}/document-fields`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
document_n: document_n || null,
|
||||||
|
document_d: document_d || null,
|
||||||
|
document_a: document_a || null
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result.success) {
|
||||||
|
showSuccess('✅ Поля документа сохранены');
|
||||||
|
|
||||||
|
// Обновляем отображение в карточке задачи, если есть
|
||||||
|
updateTaskCardDisplay(taskId, result.data);
|
||||||
|
} else {
|
||||||
|
showError(result.error || 'Ошибка сохранения');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка сохранения:', error);
|
||||||
|
showError('Ошибка сети: ' + error.message);
|
||||||
|
} finally {
|
||||||
|
saveBtn.textContent = originalText;
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновление отображения в карточке задачи
|
||||||
|
function updateTaskCardDisplay(taskId, data) {
|
||||||
|
const taskCards = document.querySelectorAll(`[data-task-id="${taskId}"]`);
|
||||||
|
|
||||||
|
taskCards.forEach(card => {
|
||||||
|
// Ищем или создаем контейнер для полей документа
|
||||||
|
let docContainer = card.querySelector('.document-fields-display');
|
||||||
|
|
||||||
|
if (!docContainer) {
|
||||||
|
docContainer = document.createElement('div');
|
||||||
|
docContainer.className = 'document-fields-display';
|
||||||
|
|
||||||
|
// Вставляем после заголовка или в подходящее место
|
||||||
|
const header = card.querySelector('.task-header');
|
||||||
|
if (header) {
|
||||||
|
header.after(docContainer);
|
||||||
|
} else {
|
||||||
|
card.prepend(docContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Формируем HTML для отображения
|
||||||
|
let displayHTML = '<div style="margin: 8px 0; padding: 5px; background-color: #f5f5f5; border-radius: 4px; font-size: 0.9em;">';
|
||||||
|
|
||||||
|
if (data.document_n) {
|
||||||
|
displayHTML += `<span style="margin-right: 15px;"><strong>№:</strong> ${data.document_n}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.document_d) {
|
||||||
|
displayHTML += `<span style="margin-right: 15px;"><strong>Дата:</strong> ${data.document_d}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.document_a) {
|
||||||
|
displayHTML += `<span><strong>Автор:</strong> ${data.document_a}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.document_n && !data.document_d) {
|
||||||
|
displayHTML += '<span style="color: #999;">Нет данных документа</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
displayHTML += '</div>';
|
||||||
|
|
||||||
|
docContainer.innerHTML = displayHTML;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавление кнопки в карточки задач
|
||||||
|
function addDocumentFieldsButtons() {
|
||||||
|
if (!currentUser) return;
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-task-id]').forEach(card => {
|
||||||
|
// Проверяем, есть ли уже кнопка
|
||||||
|
if (card.querySelector('.document-fields-btn-icon')) return;
|
||||||
|
|
||||||
|
const taskId = card.dataset.taskId;
|
||||||
|
|
||||||
|
// Ищем контейнер action-buttons
|
||||||
|
const actionsContainer = card.querySelector('.action-buttons, .task-actions');
|
||||||
|
|
||||||
|
if (actionsContainer) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'document-fields-btn-icon document';
|
||||||
|
btn.innerHTML = '📄 Реквизиты';
|
||||||
|
btn.onclick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.documentFields.openModal(taskId);
|
||||||
|
};
|
||||||
|
btn.title = 'Редактировать реквизиты документа';
|
||||||
|
actionsContainer.appendChild(btn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Наблюдатель за изменениями DOM
|
||||||
|
function observeDOM() {
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.addedNodes.length) {
|
||||||
|
window.documentFields.addButtons();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация
|
||||||
|
async function init() {
|
||||||
|
console.log('🔄 DocumentFields module initializing...');
|
||||||
|
|
||||||
|
// Получаем текущего пользователя
|
||||||
|
await getCurrentUser();
|
||||||
|
|
||||||
|
// Создаем модальное окно
|
||||||
|
createModal();
|
||||||
|
|
||||||
|
// Добавляем кнопки на существующие карточки
|
||||||
|
setTimeout(() => {
|
||||||
|
window.documentFields.addButtons();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Запускаем наблюдение за DOM
|
||||||
|
observeDOM();
|
||||||
|
|
||||||
|
console.log('✅ DocumentFields module loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Экспортируем функции в глобальную область
|
||||||
|
window.documentFields = {
|
||||||
|
openModal,
|
||||||
|
closeModal,
|
||||||
|
saveFields,
|
||||||
|
addButtons: addDocumentFieldsButtons,
|
||||||
|
init
|
||||||
|
};
|
||||||
|
|
||||||
|
// Запускаем инициализацию после загрузки DOM
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
@@ -450,6 +450,7 @@
|
|||||||
<script src="tasks_files.js"></script>
|
<script src="tasks_files.js"></script>
|
||||||
<script src="navbar.js"></script>
|
<script src="navbar.js"></script>
|
||||||
<script src="chat-ui.js"></script>
|
<script src="chat-ui.js"></script>
|
||||||
|
<script src="document-fields.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1815,6 +1815,125 @@ app.post('/api/tasks/:taskId/files', requireAuth, upload.array('files', 15), (re
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// API для обновления полей документа в задаче
|
||||||
|
app.put('/api/tasks/:taskId/document-fields', requireAuth, (req, res) => {
|
||||||
|
const { taskId } = req.params;
|
||||||
|
const { document_n, document_d, document_a } = req.body;
|
||||||
|
const userId = req.session.user.id;
|
||||||
|
const userLogin = req.session.user.login;
|
||||||
|
|
||||||
|
console.log('📄 Обновление полей документа:');
|
||||||
|
console.log('taskId:', taskId);
|
||||||
|
console.log('document_n:', document_n);
|
||||||
|
console.log('document_d:', document_d);
|
||||||
|
console.log('document_a:', document_a || userLogin);
|
||||||
|
console.log('userId:', userId);
|
||||||
|
|
||||||
|
// Проверяем доступ к задаче
|
||||||
|
checkTaskAccess(userId, taskId, (err, hasAccess) => {
|
||||||
|
if (err || !hasAccess) {
|
||||||
|
return res.status(404).json({ error: 'Задача не найдена или у вас нет прав доступа' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем существование задачи
|
||||||
|
db.get("SELECT id FROM tasks WHERE id = ?", [taskId], (err, task) => {
|
||||||
|
if (err || !task) {
|
||||||
|
return res.status(404).json({ error: 'Задача не найдена' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Формируем запрос на обновление
|
||||||
|
let query = "UPDATE tasks SET ";
|
||||||
|
const updates = [];
|
||||||
|
const params = [];
|
||||||
|
|
||||||
|
if (document_n !== undefined) {
|
||||||
|
updates.push("document_n = ?");
|
||||||
|
params.push(document_n || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document_d !== undefined) {
|
||||||
|
updates.push("document_d = ?");
|
||||||
|
params.push(document_d || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// document_a по умолчанию берем из сессии, если не передан
|
||||||
|
const authorValue = document_a !== undefined ? document_a : userLogin;
|
||||||
|
updates.push("document_a = ?");
|
||||||
|
params.push(authorValue || null);
|
||||||
|
|
||||||
|
if (updates.length === 0) {
|
||||||
|
return res.status(400).json({ error: 'Нет данных для обновления' });
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.push("updated_at = CURRENT_TIMESTAMP");
|
||||||
|
query += updates.join(", ") + " WHERE id = ?";
|
||||||
|
params.push(taskId);
|
||||||
|
|
||||||
|
db.run(query, params, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error('❌ Ошибка обновления полей документа:', err);
|
||||||
|
return res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.changes === 0) {
|
||||||
|
return res.status(404).json({ error: 'Задача не обновлена' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Логируем действие
|
||||||
|
logActivity(taskId, userId, 'DOCUMENT_FIELDS_UPDATED',
|
||||||
|
`Обновлены поля документа: №${document_n || ''}, дата: ${document_d || ''}`);
|
||||||
|
|
||||||
|
console.log('✅ Поля документа успешно обновлены');
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Поля документа обновлены',
|
||||||
|
data: {
|
||||||
|
document_n: document_n || null,
|
||||||
|
document_d: document_d || null,
|
||||||
|
document_a: authorValue
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// API для получения полей документа задачи
|
||||||
|
app.get('/api/tasks/:taskId/document-fields', requireAuth, (req, res) => {
|
||||||
|
const { taskId } = req.params;
|
||||||
|
const userId = req.session.user.id;
|
||||||
|
|
||||||
|
checkTaskAccess(userId, taskId, (err, hasAccess) => {
|
||||||
|
if (err || !hasAccess) {
|
||||||
|
return res.status(404).json({ error: 'Задача не найдена или у вас нет прав доступа' });
|
||||||
|
}
|
||||||
|
|
||||||
|
db.get(
|
||||||
|
"SELECT document_n, document_d, document_a FROM tasks WHERE id = ?",
|
||||||
|
[taskId],
|
||||||
|
(err, row) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('❌ Ошибка получения полей документа:', err);
|
||||||
|
return res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
return res.status(404).json({ error: 'Задача не найдена' });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
document_n: row.document_n || '',
|
||||||
|
document_d: row.document_d || '',
|
||||||
|
document_a: row.document_a || ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { setupTaskEndpoints,getApproverUsers };
|
module.exports = { setupTaskEndpoints,getApproverUsers };
|
||||||
Reference in New Issue
Block a user