1478 lines
56 KiB
JavaScript
1478 lines
56 KiB
JavaScript
// document-fields.js - Скрипт для управления полями документа в задачах
|
||
// Показывает кнопку "Реквизиты" только для задач с типом "document",
|
||
// только когда задача раскрыта и только для пользователей из групп "Подписант" или "Секретарь"
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
// Конфигурация
|
||
const CONFIG = {
|
||
modalId: 'documentFieldsModal',
|
||
signerGroup: 'Подписант',
|
||
secretaryGroup: 'Секретарь',
|
||
apiEndpoint: '/api2/idusers',
|
||
usersEndpoint: '/api/users',
|
||
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;
|
||
position: relative;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* Стили для поля с календарем */
|
||
.date-input-wrapper {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.date-input-wrapper input {
|
||
padding-right: 40px;
|
||
}
|
||
|
||
.calendar-icon {
|
||
position: absolute;
|
||
right: 10px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 24px;
|
||
height: 24px;
|
||
background-color: #4CAF50;
|
||
color: white;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
transition: background-color 0.2s;
|
||
user-select: none;
|
||
z-index: 2;
|
||
}
|
||
|
||
.calendar-icon:hover {
|
||
background-color: #45a049;
|
||
}
|
||
|
||
/* Календарь */
|
||
.inline-calendar {
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 0;
|
||
right: 0;
|
||
background: white;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||
z-index: 1000;
|
||
margin-top: 5px;
|
||
padding: 10px;
|
||
width: 280px;
|
||
}
|
||
|
||
.calendar-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
padding-bottom: 5px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.calendar-month-year {
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.calendar-nav {
|
||
display: flex;
|
||
gap: 5px;
|
||
}
|
||
|
||
.calendar-nav button {
|
||
background: none;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
width: 30px;
|
||
height: 30px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
color: #555;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.calendar-nav button:hover {
|
||
background-color: #f0f0f0;
|
||
border-color: #999;
|
||
}
|
||
|
||
.calendar-weekdays {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
text-align: center;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.calendar-weekday {
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
color: #666;
|
||
padding: 5px;
|
||
}
|
||
|
||
.calendar-days {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
gap: 2px;
|
||
}
|
||
|
||
.calendar-day {
|
||
aspect-ratio: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 13px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
border: none;
|
||
background: none;
|
||
padding: 5px;
|
||
}
|
||
|
||
.calendar-day:hover {
|
||
background-color: #e8f5e9;
|
||
}
|
||
|
||
.calendar-day.selected {
|
||
background-color: #4CAF50;
|
||
color: white;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.calendar-day.today {
|
||
border: 1px solid #4CAF50;
|
||
}
|
||
|
||
.calendar-day.empty {
|
||
cursor: default;
|
||
color: #ccc;
|
||
}
|
||
|
||
.calendar-day.empty:hover {
|
||
background-color: transparent;
|
||
}
|
||
|
||
.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;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.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-btn-icon.document.loading {
|
||
opacity: 0.7;
|
||
cursor: wait;
|
||
}
|
||
|
||
.document-fields-badge {
|
||
display: inline-block;
|
||
padding: 2px 8px;
|
||
border-radius: 12px;
|
||
font-size: 0.8em;
|
||
background-color: #e3f2fd;
|
||
color: #1976d2;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.document-fields-placeholder {
|
||
display: inline-block;
|
||
width: 16px;
|
||
height: 16px;
|
||
border: 2px solid #f3f3f3;
|
||
border-top: 2px solid #9C27B0;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
</style>
|
||
`
|
||
};
|
||
|
||
// Текущий пользователь
|
||
let currentUser = null;
|
||
|
||
// Группы пользователя (все группы из разных источников)
|
||
let userGroups = [];
|
||
|
||
// Кэш для типов задач
|
||
const taskTypeCache = new Map();
|
||
|
||
// Множество задач, для которых уже выполняется проверка
|
||
const pendingChecks = new Set();
|
||
|
||
// Селекторы для поиска раскрытых задач
|
||
const EXPANDED_TASK_SELECTORS = [
|
||
'.task-card.expanded',
|
||
'.task-item.expanded',
|
||
'[data-expanded="true"]',
|
||
'.task-details',
|
||
'.task-content.expanded'
|
||
];
|
||
|
||
// Состояние календаря
|
||
let calendarState = {
|
||
currentDate: new Date(),
|
||
selectedDate: null,
|
||
isOpen: false,
|
||
inputElement: null
|
||
};
|
||
|
||
// Получение текущего пользователя
|
||
async function getCurrentUser() {
|
||
try {
|
||
const response = await fetch('/api/user');
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
const data = await response.json();
|
||
if (data.user) {
|
||
currentUser = data.user;
|
||
console.log('✅ DocumentFields: текущий пользователь', currentUser.login);
|
||
|
||
// Получаем все группы пользователя
|
||
await getAllUserGroups(currentUser.login || currentUser.id);
|
||
}
|
||
return currentUser;
|
||
} catch (error) {
|
||
console.error('❌ DocumentFields: ошибка получения пользователя', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// Получение ВСЕХ групп пользователя из разных источников
|
||
async function getAllUserGroups(userLogin) {
|
||
const allGroups = new Set();
|
||
|
||
try {
|
||
// 1. Пробуем получить через API /api2/idusers
|
||
await fetchGroupsFromApi2(userLogin, allGroups);
|
||
|
||
// 2. Пробуем получить через API /api/users
|
||
await fetchGroupsFromUsersApi(userLogin, allGroups);
|
||
|
||
// 3. Проверяем, есть ли группы в currentUser
|
||
if (currentUser) {
|
||
addGroupsFromCurrentUser(allGroups);
|
||
}
|
||
|
||
// Преобразуем Set в массив и сохраняем
|
||
userGroups = Array.from(allGroups);
|
||
|
||
console.log('✅ DocumentFields: ВСЕ группы пользователя:', userGroups);
|
||
|
||
// Детальный вывод для отладки
|
||
console.log('🔍 Проверка доступа:');
|
||
console.log(' - Группы:', userGroups);
|
||
console.log(' - Ищем "Подписант":', userGroups.some(g =>
|
||
g.toLowerCase().includes(CONFIG.signerGroup.toLowerCase())));
|
||
console.log(' - Ищем "Секретарь":', userGroups.some(g =>
|
||
g.toLowerCase().includes(CONFIG.secretaryGroup.toLowerCase())));
|
||
|
||
return userGroups;
|
||
|
||
} catch (error) {
|
||
console.error('❌ DocumentFields: ошибка получения групп', error);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
// Получение групп из /api2/idusers
|
||
async function fetchGroupsFromApi2(userLogin, groupsSet) {
|
||
try {
|
||
const response = await fetch(`${CONFIG.apiEndpoint}?login=${userLogin}`);
|
||
if (!response.ok) {
|
||
console.warn('⚠️ DocumentFields: API /api2/idusers вернул ошибку', response.status);
|
||
return;
|
||
}
|
||
|
||
const data = await response.json();
|
||
console.log('📦 Данные из /api2/idusers:', data);
|
||
|
||
// Обрабатываем массив пользователей
|
||
if (Array.isArray(data)) {
|
||
const userData = data.find(u =>
|
||
u.login === userLogin ||
|
||
u.user_login === userLogin ||
|
||
u.name === userLogin ||
|
||
u.user_name === userLogin
|
||
);
|
||
|
||
if (userData) {
|
||
extractGroupsFromUserData(userData, groupsSet);
|
||
}
|
||
}
|
||
// Обрабатываем объект пользователя
|
||
else if (data && typeof data === 'object') {
|
||
extractGroupsFromUserData(data, groupsSet);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ DocumentFields: ошибка fetchGroupsFromApi2', error);
|
||
}
|
||
}
|
||
|
||
// Получение групп из /api/users
|
||
async function fetchGroupsFromUsersApi(userLogin, groupsSet) {
|
||
try {
|
||
const response = await fetch(`${CONFIG.usersEndpoint}?login=${userLogin}`);
|
||
if (!response.ok) {
|
||
console.warn('⚠️ DocumentFields: API /api/users вернул ошибку', response.status);
|
||
return;
|
||
}
|
||
|
||
const data = await response.json();
|
||
console.log('📦 Данные из /api/users:', data);
|
||
|
||
// Обрабатываем данные в зависимости от формата
|
||
if (Array.isArray(data)) {
|
||
const userData = data.find(u =>
|
||
u.login === userLogin ||
|
||
u.username === userLogin ||
|
||
u.email === userLogin
|
||
);
|
||
|
||
if (userData) {
|
||
extractGroupsFromUserData(userData, groupsSet);
|
||
}
|
||
} else if (data && typeof data === 'object') {
|
||
extractGroupsFromUserData(data, groupsSet);
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('❌ DocumentFields: ошибка fetchGroupsFromUsersApi', error);
|
||
}
|
||
}
|
||
|
||
// Извлечение групп из данных пользователя
|
||
function extractGroupsFromUserData(userData, groupsSet) {
|
||
if (!userData) return;
|
||
|
||
console.log('🔍 Извлекаем группы из:', userData);
|
||
|
||
// 1. Проверяем group_name (может быть строкой или массивом)
|
||
if (userData.group_name) {
|
||
if (Array.isArray(userData.group_name)) {
|
||
userData.group_name.forEach(g => {
|
||
if (g) groupsSet.add(String(g).trim());
|
||
});
|
||
} else if (typeof userData.group_name === 'string') {
|
||
// Может быть строкой с разделителями
|
||
const groups = userData.group_name.split(/[,;|]/).map(g => g.trim());
|
||
groups.forEach(g => {
|
||
if (g) groupsSet.add(g);
|
||
});
|
||
}
|
||
}
|
||
|
||
// 2. Проверяем ldap_group
|
||
if (userData.ldap_group) {
|
||
if (Array.isArray(userData.ldap_group)) {
|
||
userData.ldap_group.forEach(g => {
|
||
if (g) groupsSet.add(String(g).trim());
|
||
});
|
||
} else if (typeof userData.ldap_group === 'string') {
|
||
groupsSet.add(userData.ldap_group.trim());
|
||
}
|
||
}
|
||
|
||
// 3. Проверяем metadata.groups
|
||
if (userData.metadata?.groups) {
|
||
if (Array.isArray(userData.metadata.groups)) {
|
||
userData.metadata.groups.forEach(g => {
|
||
if (g) groupsSet.add(String(g).trim());
|
||
});
|
||
}
|
||
}
|
||
|
||
// 4. Проверяем просто groups (если есть)
|
||
if (userData.groups) {
|
||
if (Array.isArray(userData.groups)) {
|
||
userData.groups.forEach(g => {
|
||
if (g) groupsSet.add(String(g).trim());
|
||
});
|
||
} else if (typeof userData.groups === 'string') {
|
||
const groups = userData.groups.split(/[,;|]/).map(g => g.trim());
|
||
groups.forEach(g => {
|
||
if (g) groupsSet.add(g);
|
||
});
|
||
}
|
||
}
|
||
|
||
// 5. Проверяем roles (если есть)
|
||
if (userData.roles) {
|
||
if (Array.isArray(userData.roles)) {
|
||
userData.roles.forEach(r => {
|
||
if (r) groupsSet.add(String(r).trim());
|
||
});
|
||
}
|
||
}
|
||
|
||
// 6. Проверяем department (может содержать группу)
|
||
if (userData.department) {
|
||
groupsSet.add(String(userData.department).trim());
|
||
}
|
||
|
||
// 7. Проверяем position (может содержать группу)
|
||
if (userData.position) {
|
||
groupsSet.add(String(userData.position).trim());
|
||
}
|
||
}
|
||
|
||
// Добавление групп из currentUser
|
||
function addGroupsFromCurrentUser(groupsSet) {
|
||
if (!currentUser) return;
|
||
|
||
// Проверяем различные поля в currentUser
|
||
const fieldsToCheck = [
|
||
'group',
|
||
'groups',
|
||
'role',
|
||
'roles',
|
||
'department',
|
||
'position',
|
||
'user_group',
|
||
'user_groups'
|
||
];
|
||
|
||
fieldsToCheck.forEach(field => {
|
||
if (currentUser[field]) {
|
||
if (Array.isArray(currentUser[field])) {
|
||
currentUser[field].forEach(g => {
|
||
if (g) groupsSet.add(String(g).trim());
|
||
});
|
||
} else if (typeof currentUser[field] === 'string') {
|
||
groupsSet.add(currentUser[field].trim());
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Проверка, имеет ли пользователь доступ (Подписант или Секретарь)
|
||
function hasUserAccess() {
|
||
if (!userGroups || userGroups.length === 0) {
|
||
console.log('❌ DocumentFields: у пользователя нет групп');
|
||
return false;
|
||
}
|
||
|
||
// Приводим искомые группы к нижнему регистру
|
||
const signerGroupLower = CONFIG.signerGroup.toLowerCase();
|
||
const secretaryGroupLower = CONFIG.secretaryGroup.toLowerCase();
|
||
|
||
// Проверяем КАЖДУЮ группу пользователя
|
||
for (const group of userGroups) {
|
||
if (!group) continue;
|
||
|
||
const groupLower = String(group).toLowerCase().trim();
|
||
|
||
// Точное совпадение
|
||
if (groupLower === signerGroupLower || groupLower === secretaryGroupLower) {
|
||
console.log(`✅ Найдено точное совпадение: "${group}"`);
|
||
return true;
|
||
}
|
||
|
||
// Частичное совпадение (содержит подстроку)
|
||
if (groupLower.includes(signerGroupLower) || groupLower.includes(secretaryGroupLower)) {
|
||
console.log(`✅ Найдено частичное совпадение: "${group}" содержит искомую группу`);
|
||
return true;
|
||
}
|
||
|
||
// Совпадение по словам (если группа состоит из нескольких слов)
|
||
const groupWords = groupLower.split(/[\s\-_]/);
|
||
const signerWords = signerGroupLower.split(/[\s\-_]/);
|
||
const secretaryWords = secretaryGroupLower.split(/[\s\-_]/);
|
||
|
||
// Проверяем, содержит ли группа все слова из искомой группы
|
||
const matchesSigner = signerWords.every(word =>
|
||
groupLower.includes(word) || groupWords.some(gw => gw.includes(word))
|
||
);
|
||
|
||
const matchesSecretary = secretaryWords.every(word =>
|
||
groupLower.includes(word) || groupWords.some(gw => gw.includes(word))
|
||
);
|
||
|
||
if (matchesSigner || matchesSecretary) {
|
||
console.log(`✅ Найдено совпадение по словам: "${group}"`);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
console.log('❌ DocumentFields: пользователь НЕ имеет доступа');
|
||
console.log(' Группы пользователя:', userGroups);
|
||
console.log(' Нужные группы:', [CONFIG.signerGroup, CONFIG.secretaryGroup]);
|
||
|
||
return false;
|
||
}
|
||
|
||
// Функция для отладки - показывает все группы пользователя
|
||
function debugUserGroups() {
|
||
console.log('=== ОТЛАДКА ГРУПП ПОЛЬЗОВАТЕЛЯ ===');
|
||
console.log('Текущий пользователь:', currentUser);
|
||
console.log('Все группы:', userGroups);
|
||
console.log('Ищем "Подписант":', userGroups.filter(g =>
|
||
g.toLowerCase().includes(CONFIG.signerGroup.toLowerCase())
|
||
));
|
||
console.log('Ищем "Секретарь":', userGroups.filter(g =>
|
||
g.toLowerCase().includes(CONFIG.secretaryGroup.toLowerCase())
|
||
));
|
||
console.log('Доступ:', hasUserAccess());
|
||
console.log('================================');
|
||
|
||
return {
|
||
user: currentUser,
|
||
groups: userGroups,
|
||
hasAccess: hasUserAccess()
|
||
};
|
||
}
|
||
|
||
// Получение типа задачи по ID
|
||
async function getTaskType(taskId) {
|
||
// Проверяем кэш
|
||
if (taskTypeCache.has(taskId)) {
|
||
return taskTypeCache.get(taskId);
|
||
}
|
||
|
||
// Проверяем, не выполняется ли уже проверка для этой задачи
|
||
if (pendingChecks.has(taskId)) {
|
||
// Ждем завершения проверки
|
||
return new Promise((resolve) => {
|
||
const checkInterval = setInterval(() => {
|
||
if (taskTypeCache.has(taskId)) {
|
||
clearInterval(checkInterval);
|
||
resolve(taskTypeCache.get(taskId));
|
||
}
|
||
}, 100);
|
||
|
||
// Таймаут на случай ошибки
|
||
setTimeout(() => {
|
||
clearInterval(checkInterval);
|
||
resolve('regular');
|
||
}, 5000);
|
||
});
|
||
}
|
||
|
||
pendingChecks.add(taskId);
|
||
|
||
try {
|
||
const response = await fetch(`/api/tasks/${taskId}/type`);
|
||
|
||
if (!response.ok) {
|
||
if (response.status === 404) {
|
||
taskTypeCache.set(taskId, 'regular');
|
||
return 'regular';
|
||
}
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
const taskType = data.task_type || 'regular';
|
||
|
||
// Сохраняем в кэш
|
||
taskTypeCache.set(taskId, taskType);
|
||
|
||
return taskType;
|
||
} catch (error) {
|
||
console.error(`❌ Ошибка получения типа задачи ${taskId}:`, error);
|
||
taskTypeCache.set(taskId, 'regular');
|
||
return 'regular';
|
||
} finally {
|
||
pendingChecks.delete(taskId);
|
||
}
|
||
}
|
||
|
||
// Проверка, раскрыта ли задача
|
||
function isTaskExpanded(taskCard) {
|
||
// Проверяем по различным селекторам
|
||
for (const selector of EXPANDED_TASK_SELECTORS) {
|
||
if (taskCard.matches(selector) || taskCard.querySelector(selector)) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// Проверяем наличие классов раскрытия
|
||
if (taskCard.classList.contains('expanded') ||
|
||
taskCard.classList.contains('open') ||
|
||
taskCard.classList.contains('active')) {
|
||
return true;
|
||
}
|
||
|
||
// Проверяем атрибуты
|
||
if (taskCard.getAttribute('data-expanded') === 'true' ||
|
||
taskCard.getAttribute('aria-expanded') === 'true') {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// Функции для работы с календарем
|
||
function formatDate(date) {
|
||
if (!date) return '';
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
const year = date.getFullYear();
|
||
return `${day}.${month}.${year}`;
|
||
}
|
||
|
||
function parseDate(dateStr) {
|
||
if (!dateStr) return null;
|
||
const parts = dateStr.split('.');
|
||
if (parts.length === 3) {
|
||
const day = parseInt(parts[0], 10);
|
||
const month = parseInt(parts[1], 10) - 1;
|
||
const year = parseInt(parts[2], 10);
|
||
return new Date(year, month, day);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function getMonthName(month, year) {
|
||
const date = new Date(year, month, 1);
|
||
return date.toLocaleString('ru-RU', { month: 'long' });
|
||
}
|
||
|
||
function generateCalendar(year, month, selectedDate) {
|
||
const firstDay = new Date(year, month, 1).getDay();
|
||
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||
|
||
// Корректировка первого дня (0 - воскресенье, делаем понедельник первым)
|
||
let startOffset = firstDay === 0 ? 6 : firstDay - 1;
|
||
|
||
const today = new Date();
|
||
const todayStr = formatDate(today);
|
||
|
||
let html = '';
|
||
let dayCount = 1;
|
||
|
||
for (let i = 0; i < 6; i++) {
|
||
for (let j = 0; j < 7; j++) {
|
||
if (i === 0 && j < startOffset) {
|
||
html += '<div class="calendar-day empty"></div>';
|
||
} else if (dayCount <= daysInMonth) {
|
||
const currentDate = new Date(year, month, dayCount);
|
||
const dateStr = formatDate(currentDate);
|
||
const isSelected = selectedDate && dateStr === formatDate(selectedDate);
|
||
const isToday = dateStr === todayStr;
|
||
|
||
html += `<div class="calendar-day ${isSelected ? 'selected' : ''} ${isToday ? 'today' : ''}" onclick="documentFields.selectDate('${dateStr}')">${dayCount}</div>`;
|
||
dayCount++;
|
||
} else {
|
||
html += '<div class="calendar-day empty"></div>';
|
||
}
|
||
}
|
||
}
|
||
|
||
return html;
|
||
}
|
||
|
||
function renderCalendar() {
|
||
const calendar = document.getElementById('inlineCalendar');
|
||
if (!calendar) return;
|
||
|
||
const year = calendarState.currentDate.getFullYear();
|
||
const month = calendarState.currentDate.getMonth();
|
||
|
||
const monthName = getMonthName(month, year);
|
||
|
||
let html = `
|
||
<div class="calendar-header">
|
||
<div class="calendar-month-year">${monthName} ${year}</div>
|
||
<div class="calendar-nav">
|
||
<button onclick="documentFields.prevMonth()">←</button>
|
||
<button onclick="documentFields.nextMonth()">→</button>
|
||
</div>
|
||
</div>
|
||
<div class="calendar-weekdays">
|
||
<div class="calendar-weekday">Пн</div>
|
||
<div class="calendar-weekday">Вт</div>
|
||
<div class="calendar-weekday">Ср</div>
|
||
<div class="calendar-weekday">Чт</div>
|
||
<div class="calendar-weekday">Пт</div>
|
||
<div class="calendar-weekday">Сб</div>
|
||
<div class="calendar-weekday">Вс</div>
|
||
</div>
|
||
<div class="calendar-days">
|
||
${generateCalendar(year, month, calendarState.selectedDate)}
|
||
</div>
|
||
`;
|
||
|
||
calendar.innerHTML = html;
|
||
}
|
||
|
||
function toggleCalendar() {
|
||
if (calendarState.isOpen) {
|
||
closeCalendar();
|
||
} else {
|
||
openCalendar();
|
||
}
|
||
}
|
||
|
||
function openCalendar() {
|
||
const calendar = document.getElementById('inlineCalendar');
|
||
if (!calendar) return;
|
||
|
||
const dateInput = document.getElementById('documentDate');
|
||
if (dateInput && dateInput.value) {
|
||
const parsedDate = parseDate(dateInput.value);
|
||
if (parsedDate && !isNaN(parsedDate.getTime())) {
|
||
calendarState.selectedDate = parsedDate;
|
||
calendarState.currentDate = new Date(parsedDate);
|
||
}
|
||
}
|
||
|
||
calendarState.isOpen = true;
|
||
calendar.style.display = 'block';
|
||
renderCalendar();
|
||
}
|
||
|
||
function closeCalendar() {
|
||
const calendar = document.getElementById('inlineCalendar');
|
||
if (calendar) {
|
||
calendar.style.display = 'none';
|
||
}
|
||
calendarState.isOpen = false;
|
||
}
|
||
|
||
function selectDate(dateStr) {
|
||
const dateInput = document.getElementById('documentDate');
|
||
if (dateInput) {
|
||
dateInput.value = dateStr;
|
||
calendarState.selectedDate = parseDate(dateStr);
|
||
}
|
||
closeCalendar();
|
||
}
|
||
|
||
function prevMonth() {
|
||
calendarState.currentDate.setMonth(calendarState.currentDate.getMonth() - 1);
|
||
renderCalendar();
|
||
}
|
||
|
||
function nextMonth() {
|
||
calendarState.currentDate.setMonth(calendarState.currentDate.getMonth() + 1);
|
||
renderCalendar();
|
||
}
|
||
|
||
// Создание модального окна
|
||
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>
|
||
<div class="date-input-wrapper">
|
||
<input type="text" id="documentDate" placeholder="ДД.ММ.ГГГГ" maxlength="10" readonly>
|
||
<div class="calendar-icon" onclick="documentFields.toggleCalendar()">📅</div>
|
||
<div id="inlineCalendar" class="inline-calendar" style="display: none;"></div>
|
||
</div>
|
||
</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);
|
||
|
||
// Добавляем обработчик клика вне календаря для его закрытия
|
||
document.addEventListener('click', function(event) {
|
||
const calendar = document.getElementById('inlineCalendar');
|
||
const calendarIcon = document.querySelector('.calendar-icon');
|
||
|
||
if (calendar && calendarState.isOpen) {
|
||
if (!calendar.contains(event.target) && !calendarIcon.contains(event.target)) {
|
||
closeCalendar();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// Открытие модального окна
|
||
async function openModal(taskId) {
|
||
// Проверяем тип задачи
|
||
const taskType = await getTaskType(taskId);
|
||
|
||
if (taskType !== 'document') {
|
||
alert('Эта функция доступна только для задач типа "Документ"');
|
||
return;
|
||
}
|
||
|
||
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');
|
||
|
||
if (errorDiv) errorDiv.style.display = 'none';
|
||
if (messageDiv) messageDiv.style.display = 'none';
|
||
|
||
if (form) form.style.display = 'none';
|
||
if (loading) loading.style.display = 'block';
|
||
|
||
modalElement.classList.add('active');
|
||
|
||
const taskIdSpan = document.getElementById('documentTaskId');
|
||
if (taskIdSpan) taskIdSpan.textContent = taskId;
|
||
|
||
const authorInput = document.getElementById('documentAuthor');
|
||
if (authorInput && 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) {
|
||
const numberInput = document.getElementById('documentNumber');
|
||
const dateInput = document.getElementById('documentDate');
|
||
|
||
if (numberInput) numberInput.value = result.data.document_n || '';
|
||
if (dateInput) {
|
||
dateInput.value = result.data.document_d || '';
|
||
|
||
// Устанавливаем выбранную дату в календарь
|
||
if (result.data.document_d) {
|
||
const parsedDate = parseDate(result.data.document_d);
|
||
if (parsedDate && !isNaN(parsedDate.getTime())) {
|
||
calendarState.selectedDate = parsedDate;
|
||
calendarState.currentDate = new Date(parsedDate);
|
||
}
|
||
} else {
|
||
calendarState.selectedDate = null;
|
||
calendarState.currentDate = new Date();
|
||
}
|
||
}
|
||
|
||
if (result.data.document_a && authorInput && !authorInput.value) {
|
||
authorInput.value = result.data.document_a;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Ошибка загрузки полей документа:', error);
|
||
showError('Ошибка загрузки данных: ' + error.message);
|
||
} finally {
|
||
if (loading) loading.style.display = 'none';
|
||
if (form) form.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
// Закрытие модального окна
|
||
function closeModal() {
|
||
const modal = document.getElementById(CONFIG.modalId);
|
||
if (modal) {
|
||
modal.classList.remove('active');
|
||
|
||
const numberInput = document.getElementById('documentNumber');
|
||
const dateInput = document.getElementById('documentDate');
|
||
const authorInput = document.getElementById('documentAuthor');
|
||
|
||
if (numberInput) numberInput.value = '';
|
||
if (dateInput) dateInput.value = '';
|
||
if (authorInput) authorInput.value = '';
|
||
|
||
// Закрываем календарь
|
||
closeCalendar();
|
||
|
||
// Сбрасываем состояние календаря
|
||
calendarState = {
|
||
currentDate: new Date(),
|
||
selectedDate: null,
|
||
isOpen: false,
|
||
inputElement: null
|
||
};
|
||
}
|
||
}
|
||
|
||
// Показать ошибку
|
||
function showError(message) {
|
||
const errorDiv = document.getElementById('documentFieldsError');
|
||
if (!errorDiv) return;
|
||
|
||
errorDiv.textContent = message;
|
||
errorDiv.style.display = 'block';
|
||
|
||
setTimeout(() => {
|
||
errorDiv.style.display = 'none';
|
||
}, 5000);
|
||
}
|
||
|
||
// Показать успех
|
||
function showSuccess(message) {
|
||
const messageDiv = document.getElementById('documentFieldsMessage');
|
||
if (!messageDiv) return;
|
||
|
||
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;
|
||
if (!taskId) {
|
||
showError('ID задачи не найден');
|
||
return;
|
||
}
|
||
|
||
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');
|
||
if (!saveBtn) return;
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
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;
|
||
});
|
||
}
|
||
|
||
// Добавление кнопки в раскрытую задачу
|
||
async function addButtonToExpandedTask(taskCard) {
|
||
if (!currentUser) return;
|
||
|
||
// Проверяем, имеет ли пользователь доступ (Подписант или Секретарь)
|
||
if (!hasUserAccess()) {
|
||
return;
|
||
}
|
||
|
||
const taskId = taskCard.dataset.taskId;
|
||
if (!taskId) return;
|
||
|
||
// Проверяем, есть ли уже кнопка
|
||
if (taskCard.querySelector('.document-fields-btn-icon')) return;
|
||
|
||
// Проверяем, раскрыта ли задача
|
||
if (!isTaskExpanded(taskCard)) return;
|
||
|
||
// Показываем индикатор загрузки
|
||
const actionsContainer = taskCard.querySelector('.action-buttons, .task-actions');
|
||
if (!actionsContainer) return;
|
||
|
||
const loadingIndicator = document.createElement('span');
|
||
loadingIndicator.className = 'document-fields-placeholder';
|
||
loadingIndicator.title = 'Проверка типа задачи...';
|
||
actionsContainer.appendChild(loadingIndicator);
|
||
|
||
try {
|
||
// Получаем тип задачи
|
||
const taskType = await getTaskType(taskId);
|
||
|
||
// Удаляем индикатор загрузки
|
||
loadingIndicator.remove();
|
||
|
||
// Добавляем кнопку только для задач с типом "document"
|
||
if (taskType === 'document') {
|
||
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);
|
||
|
||
console.log(`✅ Добавлена кнопка для раскрытой задачи ${taskId} (тип: document)`);
|
||
}
|
||
} catch (error) {
|
||
console.error(`❌ Ошибка проверки задачи ${taskId}:`, error);
|
||
loadingIndicator.remove();
|
||
}
|
||
}
|
||
|
||
// Обработчик раскрытия задачи
|
||
function handleTaskExpand(mutations) {
|
||
for (const mutation of mutations) {
|
||
if (mutation.type === 'attributes' &&
|
||
(mutation.attributeName === 'class' || mutation.attributeName === 'data-expanded')) {
|
||
|
||
const target = mutation.target;
|
||
if (target.dataset?.taskId && isTaskExpanded(target)) {
|
||
addButtonToExpandedTask(target);
|
||
}
|
||
}
|
||
|
||
if (mutation.type === 'childList' && mutation.addedNodes.length) {
|
||
for (const node of mutation.addedNodes) {
|
||
if (node.nodeType === 1) { // Element node
|
||
// Проверяем сам элемент
|
||
if (node.dataset?.taskId && isTaskExpanded(node)) {
|
||
addButtonToExpandedTask(node);
|
||
}
|
||
|
||
// Проверяем дочерние элементы
|
||
const expandedTasks = node.querySelectorAll('[data-task-id]');
|
||
expandedTasks.forEach(task => {
|
||
if (isTaskExpanded(task)) {
|
||
addButtonToExpandedTask(task);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Наблюдатель за изменениями DOM
|
||
function observeDOM() {
|
||
// Наблюдатель за атрибутами и появлением новых элементов
|
||
const observer = new MutationObserver(handleTaskExpand);
|
||
|
||
observer.observe(document.body, {
|
||
attributes: true,
|
||
attributeFilter: ['class', 'data-expanded', 'aria-expanded'],
|
||
childList: true,
|
||
subtree: true
|
||
});
|
||
|
||
console.log('👀 Наблюдатель за раскрытием задач запущен');
|
||
|
||
// Также проверяем уже раскрытые задачи при загрузке
|
||
setTimeout(() => {
|
||
document.querySelectorAll('[data-task-id]').forEach(task => {
|
||
if (isTaskExpanded(task)) {
|
||
addButtonToExpandedTask(task);
|
||
}
|
||
});
|
||
}, 2000);
|
||
}
|
||
|
||
// Очистка кэша
|
||
function clearCache() {
|
||
taskTypeCache.clear();
|
||
pendingChecks.clear();
|
||
console.log('🗑️ Кэш типов задач очищен');
|
||
}
|
||
|
||
// Функция для ручного обновления групп пользователя
|
||
async function refreshUserGroups() {
|
||
if (currentUser) {
|
||
await getAllUserGroups(currentUser.login || currentUser.id);
|
||
return hasUserAccess();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// Инициализация
|
||
async function init() {
|
||
console.log('🔄 DocumentFields module initializing...');
|
||
|
||
try {
|
||
await getCurrentUser();
|
||
|
||
// Проверяем доступ пользователя
|
||
if (hasUserAccess()) {
|
||
createModal();
|
||
observeDOM();
|
||
console.log('✅ DocumentFields module loaded (with access)');
|
||
|
||
// Для отладки показываем группы
|
||
setTimeout(debugUserGroups, 1000);
|
||
} else {
|
||
console.log('ℹ️ DocumentFields module loaded (no access - user is not Signer or Secretary)');
|
||
debugUserGroups(); // Показываем отладку даже при отсутствии доступа
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Ошибка инициализации:', error);
|
||
}
|
||
}
|
||
|
||
// Экспортируем функции
|
||
window.documentFields = {
|
||
openModal,
|
||
closeModal,
|
||
saveFields,
|
||
getTaskType,
|
||
clearCache,
|
||
init,
|
||
addButtonToExpandedTask,
|
||
isTaskExpanded,
|
||
hasUserAccess,
|
||
getAllUserGroups,
|
||
refreshUserGroups,
|
||
debugUserGroups,
|
||
// Функции календаря
|
||
toggleCalendar,
|
||
selectDate,
|
||
prevMonth,
|
||
nextMonth
|
||
};
|
||
|
||
// Запускаем инициализацию
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
|
||
})(); |