Files
medods_n3health_extension/documents.js
T
2026-03-17 20:41:44 +03:00

1136 lines
59 KiB
JavaScript

// documents.js
(function () {
// Глобальная переменная для хранения текущего уведомления
let currentMessageEl = null;
let messageTimeout = null;
let deliveryTypeSettings = {};
// Функция для отображения сообщений
function showMessage(message, type = 'success', duration = 3000) {
// Удаляем предыдущее уведомление, если оно есть
if (currentMessageEl && currentMessageEl.parentNode) {
clearTimeout(messageTimeout);
document.body.removeChild(currentMessageEl);
currentMessageEl = null;
}
const messageEl = document.createElement('div');
messageEl.className = `el-message el-message--${type}`;
messageEl.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
min-width: 380px;
padding: 15px 15px 15px 20px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
background-color: ${type === 'success' ? '#f0f9eb' :
type === 'info' ? '#f4f4f5' : '#fef0f0'};
border: 1px solid ${type === 'success' ? '#e1f3d8' :
type === 'info' ? '#e9e9eb' : '#fde2e2'};
color: ${type === 'success' ? '#67c23a' :
type === 'info' ? '#909399' : '#f56c6c'};
display: flex;
align-items: center;
`;
const iconClass = type === 'success' ? 'el-icon-success' :
type === 'info' ? 'el-icon-info' : 'el-icon-error';
const iconColor = type === 'success' ? '#67c23a' :
type === 'info' ? '#909399' : '#f56c6c';
messageEl.innerHTML = `
<i class="el-icon ${iconClass}" style="font-size: 16px; color: ${iconColor}; margin-right: 10px;"></i>
<span style="font-size: 14px;">${message}</span>
`;
document.body.appendChild(messageEl);
currentMessageEl = messageEl;
// Скрываем через указанное время
messageTimeout = setTimeout(() => {
if (currentMessageEl === messageEl && messageEl.parentNode) {
document.body.removeChild(messageEl);
currentMessageEl = null;
}
}, duration);
}
// Функция отправки сообщения
function sendMessageToContent(type, payload) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error('Таймаут ожидания ответа'));
}, 60000);
const messageHandler = (event) => {
if (
event.source !== window ||
!event.data ||
event.data.source !== 'medods-extension' ||
event.data.type !== `${type}Response`
) {
return;
}
window.removeEventListener('message', messageHandler);
clearTimeout(timeoutId);
resolve(event.data.payload);
};
window.addEventListener('message', messageHandler);
window.postMessage({
source: 'medods-extension',
type: type,
payload: payload,
}, '*');
});
}
// Функция форматирования даты
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
// Функция получения класса для статуса
function getStatusClass(category) {
switch (category) {
case 'completed':
return 'status-completed';
case 'processing':
return 'status-processing';
case 'error':
return 'status-error';
default:
return '';
}
}
// Функция получения иконки для статуса
function getStatusIcon(category) {
switch (category) {
case 'completed':
return 'el-icon-success';
case 'processing':
return 'el-icon-loading';
case 'error':
return 'el-icon-error';
default:
return 'el-icon-question';
}
}
// Функция создания модального окна со статусами
function createStatusesModal(singing) {
const modalWrapper = document.createElement('div');
modalWrapper.className = 'el-dialog__wrapper';
modalWrapper.style.cssText = 'z-index: 2001; position: fixed; top: 0; right: 0; bottom: 0; left: 0; overflow: auto;';
// Разделяем статусы на общие и по получателям
const commonStatuses = singing.statuses.filter(s => s.idPatientMis === null);
const recipientStatuses = singing.statuses.filter(s => s.idPatientMis !== null);
// Группируем статусы по получателям
const statusesByRecipient = {};
recipientStatuses.forEach(status => {
if (status.patient) {
const patientName = status.patient.name || 'Неизвестный получатель';
if (!statusesByRecipient[patientName]) {
statusesByRecipient[patientName] = [];
}
statusesByRecipient[patientName].push(status);
}
});
// Сортируем получателей по имени
const sortedRecipients = Object.keys(statusesByRecipient).sort();
// Функция для генерации HTML статуса
const getStatusHtml = (status) => `
<div class="status-history-item" style="padding: 12px; border-bottom: 1px solid #ebeef5; display: flex; align-items: center; gap: 12px;">
<i class="el-icon ${getStatusIcon(status.category)}" style="font-size: 16px; color: ${status.category === 'completed' ? '#67C23A' :
status.category === 'processing' ? '#409EFF' :
status.category === 'error' ? '#F56C6C' : '#909399'
}; width: 20px;"></i>
<div style="flex: 1;">
<div style="font-weight: 500; color: #303133;">${status.description}</div>
<div style="font-size: 12px; color: #909399; margin-top: 4px;">${formatDate(status.created_at)}</div>
</div>
</div>
`;
// Генерируем HTML для общих статусов
const commonStatusesHtml = commonStatuses
.sort((a, b) => a.id - b.id)
.map(getStatusHtml)
.join('');
// Генерируем HTML для статусов получателей
const recipientStatusesHtml = sortedRecipients.map(recipientName => {
const recipientStatuses = statusesByRecipient[recipientName]
.sort((a, b) => a.id - b.id)
.map(getStatusHtml)
.join('');
return `
<div style="background: #F5F7FA; padding: 8px 12px; font-weight: 500; color: #303133; border-bottom: 1px solid #DCDFE6;">
<i class="el-icon el-icon-user" style="margin-right: 6px; color: #409EFF;"></i>
${recipientName}
</div>
${recipientStatuses}
`;
}).join('');
modalWrapper.innerHTML = `
<div class="el-dialog" style="width: 500px; margin-top: 15vh;">
<div class="el-dialog__header">
<span class="el-dialog__title">История статусов</span>
<button type="button" class="el-dialog__headerbtn" aria-label="Close">
<i class="el-dialog__close el-icon el-icon-close"></i>
</button>
</div>
<div class="el-dialog__body" style="max-height: 400px; overflow-y: auto; padding: 0;">
${commonStatusesHtml}
${recipientStatusesHtml}
</div>
<div class="el-dialog__footer" style="margin-top: 12px; width: 100%; text-align: right;">
<button type="button" class="el-button m-button el-button--small is-plain button-with-icon close-btn">
<i class="m-icon fa-times button-icon fad" style="font-size: 14px; margin-right: 6px;"></i>
<span>Закрыть</span>
</button>
</div>
</div>
`;
document.body.appendChild(modalWrapper);
// Обработчики закрытия
const closeModal = () => modalWrapper.remove();
modalWrapper.querySelector('.el-dialog__headerbtn').addEventListener('click', closeModal);
modalWrapper.querySelector('.close-btn').addEventListener('click', closeModal);
modalWrapper.addEventListener('click', (e) => {
if (e.target === modalWrapper) closeModal();
});
// Закрытие по ESC
const escHandler = (e) => {
if (e.key === 'Escape') {
closeModal();
document.removeEventListener('keydown', escHandler);
}
};
document.addEventListener('keydown', escHandler);
}
// Функция для получения имени получателя по ID
function getRecipientNameById(singing, idPatientMis) {
const status = singing.statuses.find(s => s.idPatientMis === idPatientMis && s.patient);
return status?.patient?.name || 'Неизвестный получатель';
}
// Функция отображения PDF
async function viewDocument(documentPath) {
try {
showMessage('Загрузка документа...', 'info', 1000);
const response = await sendMessageToContent('getDocument', { documentPath });
if (response.success && response.data) {
// Декодируем base64
const byteCharacters = atob(response.data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
// Создаем blob
const blob = new Blob([byteArray], { type: 'application/pdf' });
// Создаем URL для blob
const url = window.URL.createObjectURL(blob);
// Пытаемся открыть в новой вкладке
const newWindow = window.open(url, '_blank');
// Если браузер заблокировал всплывающее окно
if (!newWindow) {
// Создаем ссылку и имитируем клик для скачивания
const link = document.createElement('a');
link.href = url;
link.download = 'document.pdf'; // Простое имя по умолчанию
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showMessage('Разрешите всплывающие окна для просмотра PDF', 'warning');
}
// Очищаем URL через некоторое время
setTimeout(() => {
window.URL.revokeObjectURL(url);
}, 1000);
} else {
showMessage('Ошибка загрузки документа: ' + (response.message || 'Неизвестная ошибка'), 'error');
}
} catch (error) {
console.info('Ошибка просмотра документа:', error);
showMessage('Ошибка просмотра документа', 'error');
}
}
// Функция отзыва документов
async function revokeDocuments(singing, modalToClose) {
try {
// Сразу закрываем окно со списком документов
if (modalToClose && modalToClose.parentNode) {
modalToClose.remove();
}
// Проверяем права на отзыв
const checkResult = await sendMessageToContent('checkRevokePermission', {
userIdLpu: singing.userIdLpu
});
if (!checkResult.hasPermission) {
showMessage('Отозвать документы может только автор запроса', 'error');
return;
}
// Показываем подтверждение
const confirmed = await createRevokeConfirmModal(singing);
if (!confirmed) return;
showMessage('Отзыв документов...', 'info');
// Отправляем запрос на отзыв
const response = await sendMessageToContent('revokeDocuments', {
trackingId: singing.trackingId
});
if (response.success) {
showMessage('Документы успешно отозваны', 'success');
console.log('Документы успешно отозваны');
// Обновляем список документов
await prepareDocuments();
} else {
showMessage('Ошибка отзыва документов: ' + (response.message || 'Неизвестная ошибка'), 'error');
}
} catch (error) {
console.info('Ошибка отзыва документов:', error);
showMessage('Ошибка отзыва документов', 'error');
}
}
// Функция повторной отправки документов
async function resendDocuments(singing, modalToClose) {
try {
// Сразу закрываем окно со списком документов
if (modalToClose && modalToClose.parentNode) {
modalToClose.remove();
}
// Показываем подтверждение с выбором получателей и способа отправки
const result = await createResendConfirmModal(singing);
if (!result.confirmed) return;
// Если получателей нет или не выбраны, показываем ошибку
if (result.recipients.length === 0) {
showMessage('Не выбраны получатели для повторной отправки', 'error');
return;
}
// Если не выбран способ отправки, показываем ошибку
if (!result.deliveryType) {
showMessage('Не выбран способ отправки', 'error');
return;
}
showMessage('Повторная отправка документов...', 'info');
// Отправляем запрос на повторную отправку с указанием получателей и способа отправки
const response = await sendMessageToContent('resendDocuments', {
trackingId: singing.trackingId,
idPatientMis: result.recipients,
deliveryType: result.deliveryType
});
if (response.success) {
showMessage('Документы успешно отправлены повторно', 'success');
console.log('Документы успешно отправлены повторно');
// Обновляем список документов
await prepareDocuments();
} else {
showMessage('Ошибка повторной отправки документов: ' + (response.message || 'Неизвестная ошибка'), 'error');
}
} catch (error) {
console.info('Ошибка повторной отправки документов:', error);
showMessage('Ошибка повторной отправки документов', 'error');
}
}
// Функция создания модального окна подтверждения повторной отправки
function createResendConfirmModal(singing) {
return new Promise((resolve) => {
const modalWrapper = document.createElement('div');
modalWrapper.className = 'el-dialog__wrapper';
modalWrapper.style.cssText = 'z-index: 2001; position: fixed; top: 0; right: 0; bottom: 0; left: 0; overflow: auto;';
// Собираем всех получателей (recipients + idPatientMis)
const allRecipients = [];
// Добавляем idPatientMis если есть
if (singing.idPatientMis) {
allRecipients.push({
id: singing.idPatientMis,
name: getRecipientNameById(singing, singing.idPatientMis)
});
}
// Добавляем recipients
if (singing.recipients && singing.recipients.length > 0) {
singing.recipients.forEach(id => {
// Проверяем, не добавили ли уже этого получателя
if (!allRecipients.some(r => r.id === id)) {
allRecipients.push({
id: id,
name: getRecipientNameById(singing, id)
});
}
});
}
const hasRecipients = allRecipients.length > 0;
// Маппинг типов доставки на отображаемые названия
const deliveryTypeLabels = {
'sms': 'СМС-сообщение',
'max': 'МАХ-сообщение',
'mila': 'Mila-сообщение',
'goskey': 'Goskey-сообщение'
};
// Доступные типы доставки
const deliveryTypes = Object.keys(deliveryTypeSettings).filter(key => deliveryTypeSettings[key] === true);
// Создаем HTML для списка получателей с чекбоксами
const recipientsHtml = hasRecipients ? `
<div style="margin-top: 16px;">
<div style="font-weight: 500; color: #303133; margin-bottom: 8px;">Получатели:</div>
<div style="max-height: 200px; overflow-y: auto; border: 1px solid #DCDFE6; border-radius: 4px; padding: 8px;">
${allRecipients.map(recipient => `
<div style="display: flex; align-items: center; padding: 6px 8px; border-bottom: 1px solid #EBEEF5;">
<label style="display: flex; align-items: center; width: 100%; cursor: pointer;">
<span class="el-checkbox" style="margin-right: 10px;">
<input type="checkbox" class="recipient-checkbox" value="${recipient.id}" style="width: 16px; height: 16px; cursor: pointer;">
</span>
<span style="color: #606266;">${recipient.name}</span>
</label>
</div>
`).join('')}
</div>
</div>
` : '';
modalWrapper.innerHTML = `
<div class="el-dialog" style="width: 500px; margin-top: 15vh;">
<div class="el-dialog__header">
<span class="el-dialog__title">Подтверждение повторной отправки</span>
<button type="button" class="el-dialog__headerbtn" aria-label="Close">
<i class="el-dialog__close el-icon el-icon-close"></i>
</button>
</div>
<div class="el-dialog__body" style="padding: 20px; max-height: 60vh; overflow-y: auto;">
<div style="display: flex; align-items: flex-start; gap: 12px;">
<i class="el-icon el-icon-warning" style="font-size: 24px; color: #E6A23C;"></i>
<div style="flex: 1;">
<p style="margin: 0 0 10px 0; color: #606266;">Вы действительно хотите повторно отправить документы на подписание?</p>
<div style="background: #f8f8f8; padding: 10px; border-radius: 4px; font-size: 13px; color: #E6A23C; border-left: 3px solid #E6A23C; margin-bottom: 10px;">
<i class="el-icon el-icon-document" style="margin-right: 6px;"></i>
<strong>Документы:</strong> ${singing.documents.map(d => `${d.number} ${d.title}`).join(', ')}
</div>
${recipientsHtml}
<!-- Выпадающий список способа отправки -->
<div style="margin-top: 16px;">
<div style="font-weight: 500; color: #303133; margin-bottom: 8px;">Способ отправки:</div>
<div class="delivery-select-wrapper" style="position: relative; width: 100%;">
<select class="delivery-select" style="
width: 100%;
height: 32px;
padding: 0 24px 0 10px;
border: 1px solid #DCDFE6;
border-radius: 4px;
background: white;
font-size: 13px;
color: #606266;
outline: none;
cursor: pointer;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
font-family: inherit;
">
<option value="" selected disabled>Выберите способ отправки</option>
${deliveryTypes.map(type => `
<option value="${type}">
${deliveryTypeLabels[type]}
</option>
`).join('')}
</select>
<span style="
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: #C0C4CC;
font-size: 12px;
">▼</span>
</div>
</div>
${!hasRecipients ? '<p style="color: #909399; margin: 10px 0 0;">Нет получателей для повторной отправки</p>' : ''}
</div>
</div>
</div>
<div class="el-dialog__footer" style="display: flex; justify-content: space-between; align-items: center;">
<button type="button" class="el-button m-button el-button--small is-plain button-with-icon cancel-btn"
style="display: flex; align-items: center;">
<i class="m-icon fa-times button-icon fad" style="font-size: 14px; margin-right: 6px;"></i>
<span>Отмена</span>
</button>
<button type="button" class="el-button m-button el-button--small is-plain button-with-icon confirm-btn"
style="color: #67C23A; border-color: #67C23A; display: flex; align-items: center; ${!hasRecipients ? 'opacity: 0.5; pointer-events: none;' : ''}">
<i class="m-icon fa-redo-alt button-icon fad" style="font-size: 14px; margin-right: 6px;"></i>
<span>Отправить повторно</span>
</button>
</div>
</div>
`;
document.body.appendChild(modalWrapper);
// Получаем элементы
const checkboxes = modalWrapper.querySelectorAll('.recipient-checkbox');
const confirmBtn = modalWrapper.querySelector('.confirm-btn');
const deliverySelect = modalWrapper.querySelector('.delivery-select');
let selectedDeliveryType = deliverySelect.value || null;
// Функция обновления состояния кнопки
function updateConfirmButton() {
const anyChecked = Array.from(checkboxes).some(cb => cb.checked);
const hasDeliveryType = selectedDeliveryType && selectedDeliveryType !== '';
if (anyChecked && hasDeliveryType) {
confirmBtn.style.opacity = '1';
confirmBtn.style.pointerEvents = 'auto';
} else {
confirmBtn.style.opacity = '0.5';
confirmBtn.style.pointerEvents = 'none';
}
}
// Обработчик изменения выбора в select
deliverySelect.addEventListener('change', (e) => {
selectedDeliveryType = e.target.value;
updateConfirmButton();
});
// Обработчики для чекбоксов
if (checkboxes.length > 0) {
checkboxes.forEach(cb => {
cb.addEventListener('change', updateConfirmButton);
});
}
// Функции закрытия
const closeModal = () => {
modalWrapper.remove();
resolve({ confirmed: false, recipients: [], deliveryType: null });
};
// Обработчик кнопки подтверждения
confirmBtn.addEventListener('click', () => {
const selectedRecipients = Array.from(checkboxes)
.filter(cb => cb.checked)
.map(cb => cb.value);
if (selectedRecipients.length === 0 || !selectedDeliveryType) {
return;
}
modalWrapper.remove();
resolve({
confirmed: true,
recipients: selectedRecipients,
deliveryType: selectedDeliveryType
});
});
// Обработчики закрытия
modalWrapper.querySelector('.el-dialog__headerbtn').addEventListener('click', closeModal);
modalWrapper.querySelector('.cancel-btn').addEventListener('click', closeModal);
modalWrapper.addEventListener('click', (e) => {
if (e.target === modalWrapper) closeModal();
});
// Закрытие по ESC
const escHandler = (e) => {
if (e.key === 'Escape') {
closeModal();
document.removeEventListener('keydown', escHandler);
}
};
document.addEventListener('keydown', escHandler);
// Инициализация состояния кнопки
updateConfirmButton();
});
}
// Функция создания модального окна подтверждения отзыва
function createRevokeConfirmModal(singing) {
return new Promise((resolve) => {
const modalWrapper = document.createElement('div');
modalWrapper.className = 'el-dialog__wrapper';
modalWrapper.style.cssText = 'z-index: 2001; position: fixed; top: 0; right: 0; bottom: 0; left: 0; overflow: auto;';
modalWrapper.innerHTML = `
<div class="el-dialog" style="width: 450px; margin-top: 25vh;">
<div class="el-dialog__header">
<span class="el-dialog__title">Подтверждение отзыва</span>
<button type="button" class="el-dialog__headerbtn" aria-label="Close">
<i class="el-dialog__close el-icon el-icon-close"></i>
</button>
</div>
<div class="el-dialog__body" style="padding: 20px;">
<div style="display: flex; align-items: flex-start; gap: 12px;">
<i class="el-icon el-icon-warning" style="font-size: 24px; color: #E6A23C;"></i>
<div>
<p style="margin: 0 0 10px 0; color: #606266;">Вы действительно хотите отозвать документы из подписания?</p>
<div style="background: #f8f8f8; padding: 10px; border-radius: 4px; font-size: 13px; color: #E6A23C; border-left: 3px solid #E6A23C;">
<i class="el-icon el-icon-document" style="margin-right: 6px;"></i>
<strong>Документы:</strong> ${singing.documents.map(d => `${d.number} ${d.title}`).join(', ')}
</div>
</div>
</div>
</div>
<div class="el-dialog__footer" style="display: flex; justify-content: space-between; align-items: center;">
<button type="button" class="el-button m-button el-button--small is-plain button-with-icon cancel-btn"
style="display: flex; align-items: center;">
<i class="m-icon fa-times button-icon fad" style="font-size: 14px; margin-right: 6px;"></i>
<span>Отмена</span>
</button>
<button type="button" class="el-button m-button el-button--small is-plain button-with-icon confirm-btn" style="color: #F56C6C; border-color: #F56C6C; display: flex; align-items: center;">
<i class="m-icon fa-ban button-icon fad" style="font-size: 14px; margin-right: 6px;"></i>
<span>Отозвать</span>
</button>
</div>
</div>
`;
document.body.appendChild(modalWrapper);
const closeModal = () => {
modalWrapper.remove();
resolve(false);
};
modalWrapper.querySelector('.el-dialog__headerbtn').addEventListener('click', closeModal);
modalWrapper.querySelector('.cancel-btn').addEventListener('click', closeModal);
modalWrapper.querySelector('.confirm-btn').addEventListener('click', () => {
modalWrapper.remove();
resolve(true);
});
modalWrapper.addEventListener('click', (e) => {
if (e.target === modalWrapper) closeModal();
});
// Закрытие по ESC
const escHandler = (e) => {
if (e.key === 'Escape') {
closeModal();
document.removeEventListener('keydown', escHandler);
}
};
document.addEventListener('keydown', escHandler);
});
}
// Функция создания таблицы документов
function createDocumentsTable(data) {
// Создаем модальное окно
const modalWrapper = document.createElement('div');
modalWrapper.className = 'el-dialog__wrapper';
modalWrapper.style.cssText = 'z-index: 2001; position: fixed; top: 0; right: 0; bottom: 0; left: 0; overflow: auto;';
const backdrop = document.createElement('div');
backdrop.className = 'v-modal';
backdrop.style.cssText = 'z-index: 2001;';
backdrop.tabIndex = 0;
if (!data || data.length === 0) {
modalWrapper.innerHTML = `
<div class="el-dialog" style="width: 500px; margin-top: 25vh;">
<div class="el-dialog__header">
<span class="el-dialog__title">Электронные документы</span>
<button type="button" class="el-dialog__headerbtn" aria-label="Close">
<i class="el-dialog__close el-icon el-icon-close"></i>
</button>
</div>
<div class="el-dialog__body" style="text-align: center; padding: 40px;">
<i class="el-icon el-icon-document" style="font-size: 48px; color: #DCDFE6; margin-bottom: 16px;"></i>
<p style="color: #909399; font-size: 16px; margin: 0;">Нет документов, подписанных электронным способом</p>
</div>
<div class="el-dialog__footer" style="text-align: right;">
<button type="button" class="el-button m-button el-button--small is-plain button-with-icon close-btn">
<i class="m-icon fa-times button-icon fad" style="font-size: 14px; margin-right: 6px;"></i>
<span>Закрыть</span>
</button>
</div>
</div>
`;
} else {
const tableRows = data.map((singing, index) => {
// Определяем последний статус
const commonStatuses = singing.statuses.filter(s => s.idPatientMis === null);
let lastStatus = commonStatuses.reduce((max, status) =>
status.id > max.id ? status : max
, commonStatuses[0] || singing.statuses[0]);
if (!lastStatus) {
lastStatus = {
category: 'processing',
description: 'Неизвестный статус',
created_at: singing.created_at
}
}
const statusClass = getStatusClass(lastStatus.category);
const statusIcon = getStatusIcon(lastStatus.category);
const statusColor = lastStatus.category === 'completed' ? '#67C23A' :
lastStatus.category === 'processing' ? '#409EFF' :
lastStatus.category === 'error' ? '#F56C6C' : '#909399';
// Маппинг типов доставки на отображаемые названия
const deliveryTypeLabels = {
'sms': 'СМС-сообщение',
'max': 'МАХ-сообщение',
'mila': 'Mila-сообщение',
'goskey': 'Goskey-сообщение'
};
// Формируем ячейку с документами
const documentsHtml = singing.documents.map(doc => `
<div style="display: flex; align-items: flex-start; gap: 8px; margin-bottom: ${singing.documents.length > 1 ? '8px' : '0'}; padding: 4px 0;">
<div style="flex: 1; word-break: break-word; color: #606266;">
<span style="font-weight: 500; color: #303133;">№${doc.number}</span>
<span style="margin-left: 4px;">${doc.title}</span>
</div>
${doc.storagePath ? `
<button class="el-button el-button--text view-doc-btn"
data-title="${doc.title}"
data-path="${doc.storagePath}"
style="padding: 0 4px; min-height: auto; border: none;">
<i class="el-icon el-icon-view" style="color: #409EFF; font-size: 16px;"></i>
</button>
` : ''}
</div>
`).join('');
return `
<tr style="border-bottom: 1px solid #EBEEF5;">
<td style="padding: 12px; vertical-align: top;">
<div style="font-weight: 500; color: #303133;">${formatDate(singing.created_at)}</div>
<div style="font-size: 12px; color: #909399; margin-top: 4px;">
<i class="el-icon el-icon-user" style="margin-right: 4px;"></i>
${singing.practitioner}
</div>
</td>
<td style="padding: 12px; vertical-align: top; max-width: 550px;">
${documentsHtml}
</td>
<td style="padding: 12px; vertical-align: top;">
<div style="display: flex; align-items: center; gap: 4px;">
<div style="display: flex; align-items: center; gap: 4px; flex: 1;">
<i class="el-icon ${statusIcon}" style="color: ${statusColor}; font-size: 16px;"></i>
<div>
<div style="color: ${statusColor};">${lastStatus.description}</div>
<div style="font-size: 12px; color: #909399; margin-top: 2px;">${formatDate(lastStatus.created_at)} ✉️ ${deliveryTypeLabels[singing.deliveryType]}</div>
</div>
</div>
<div style="display: flex; gap: 2px; align-items: center;">
${singing.storagePath && singing.storagePath.length > 0
? singing.storagePath.map((path, index) => `
<button class="el-button el-button--text view-main-doc-btn"
data-title="${singing.title || `Итоговый документ ${index + 1}`}"
data-path="${path}"
title="Просмотреть основной документ ${singing.storagePath.length > 1 ? index + 1 : ''}"
style="margin: 0; padding: 0; border: none; min-width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;">
<i class="el-icon el-icon-document" style="color: #409EFF; font-size: 16px;"></i>
</button>
`).join('')
: ''}
${singing.statuses.length > 1 ? `
<button class="el-button el-button--text view-statuses-btn"
data-index="${index}"
title="История статусов"
style="margin: 0; padding: 0; border: none; min-width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;">
<i class="el-icon el-icon-time" style="color: #909399; font-size: 16px;"></i>
</button>
` : ''}
${lastStatus.category === 'processing' ? `
<button class="el-button el-button--text resend-btn"
data-index="${index}"
title="Повторно отправить документы"
style="margin: 0; padding: 0; border: none; min-width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;">
<i class="el-icon el-icon-refresh" style="color: #67C23A; font-size: 16px;"></i>
</button>
<button class="el-button el-button--text revoke-btn"
data-index="${index}"
title="Отозвать документы"
style="margin: 0; padding: 0; border: none; min-width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;">
<i class="el-icon el-icon-circle-close" style="color: #F56C6C; font-size: 16px;"></i>
</button>
` : ''}
</div>
</div>
</td>
</tr>
`;
}).join('');
modalWrapper.innerHTML = `
<div class="el-dialog" style="width: 90%; max-width: 1200px; margin-top: 5vh;">
<div class="el-dialog__header">
<span class="el-dialog__title">Электронные документы</span>
<button type="button" class="el-dialog__headerbtn" aria-label="Close">
<i class="el-dialog__close el-icon el-icon-close"></i>
</button>
</div>
<div class="el-dialog__body" style="padding: 20px;">
<div style="border: 1px solid #EBEEF5; border-radius: 4px; overflow: hidden;">
<table style="width: 100%; border-collapse: collapse; font-size: 14px;">
<thead>
<tr style="background: #F5F7FA;">
<th style="padding: 12px; text-align: left; font-weight: 500; color: #909399;">Оформлено</th>
<th style="padding: 12px; text-align: left; font-weight: 500; color: #909399;">Документы</th>
<th style="padding: 12px; text-align: left; font-weight: 500; color: #909399;">Статус</th>
</tr>
</thead>
<tbody>
${tableRows}
</tbody>
</table>
</div>
</div>
<div class="el-dialog__footer" style="text-align: right;">
<button type="button" class="el-button m-button el-button--small is-plain button-with-icon close-btn">
<i class="m-icon fa-times button-icon fad" style="font-size: 14px; margin-right: 6px;"></i>
<span>Закрыть</span>
</button>
</div>
</div>
`;
}
document.body.appendChild(modalWrapper);
// Обработчики закрытия
const closeModal = () => modalWrapper.remove();
modalWrapper.querySelector('.el-dialog__headerbtn').addEventListener('click', closeModal);
modalWrapper.querySelector('.close-btn').addEventListener('click', closeModal);
modalWrapper.addEventListener('click', (e) => {
if (e.target === modalWrapper) closeModal();
});
// Закрытие по ESC
const escHandler = (e) => {
if (e.key === 'Escape') {
closeModal();
document.removeEventListener('keydown', escHandler);
}
};
document.addEventListener('keydown', escHandler);
// Обработчики для кнопок просмотра документов
if (data && data.length > 0) {
modalWrapper.querySelectorAll('.view-doc-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const path = btn.dataset.path;
viewDocument(path);
});
});
modalWrapper.querySelectorAll('.view-main-doc-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const path = btn.dataset.path;
viewDocument(path);
});
});
modalWrapper.querySelectorAll('.view-statuses-btn').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const index = btn.dataset.index;
createStatusesModal(data[index]);
});
});
modalWrapper.querySelectorAll('.revoke-btn').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const index = btn.dataset.index;
revokeDocuments(data[index], modalWrapper);
});
});
// Добавляем обработчики для кнопок повторной отправки
modalWrapper.querySelectorAll('.resend-btn').forEach((btn) => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const index = btn.dataset.index;
resendDocuments(data[index], modalWrapper);
});
});
}
}
async function getDocuments() {
try {
return await sendMessageToContent('prepareDocuments', {});
} catch (error) {
console.info('Ошибка получения документов:', error);
showMessage('Ошибка загрузки документов', 'error');
return null;
}
}
// Основная функция подготовки документов
async function prepareDocuments() {
try {
showMessage('Загрузка документов...', 'info');
const docsData = await getDocuments();
if (!docsData) {
showMessage('Ошибка загрузки документов', 'error');
console.info('Нет данных документов');
return
};
if (docsData.success) {
deliveryTypeSettings = docsData.data.deliveryType;
createDocumentsTable(docsData.data.singings);
} else {
showMessage('Ошибка загрузки документов: ' + (docsData.message || 'Неизвестная ошибка'), 'error');
}
} catch (error) {
console.info('Ошибка подготовки документов:', error);
showMessage('Ошибка загрузки документов', 'error');
}
}
// Функция добавления кнопки
function addDocsBtn() {
const docsBtn = document.createElement('button');
docsBtn.className = 'el-button m-button el-button--small is-plain button-with-icon';
docsBtn.innerHTML = `
<span>
<i class="m-icon fa-copy button-icon fad" style="font-size: 16px;"></i>
<span class="m-button__text use-indent">ЭДО</span>
</span>
`;
const m_panel_footer = document.querySelector('.m-panel__footer');
if (m_panel_footer) {
m_panel_footer.insertAdjacentElement('afterbegin', docsBtn);
docsBtn.addEventListener('click', prepareDocuments);
} else {
// Если панель не найдена, пробуем еще раз через небольшую задержку
setTimeout(addDocsBtn, 1000);
}
}
async function handleDocsStatuses() {
try {
const statusesData = await getDocuments();
if (statusesData && statusesData.success) {
const singings = statusesData.data.singings || [];
// Получаем таблицу и все строки
const table = document.querySelector('.m-table.m-table-generator.m-si-generator__table');
if (!table) {
console.info('Таблица не найдена');
return;
}
const tableRows = table.querySelectorAll('.m-table-row:not(.m-table__header)'); // Исключаем заголовок
singings.forEach(singing => {
if (singing.statuses && singing.statuses.length > 0) {
const commonStatuses = singing.statuses.filter(s => s.idPatientMis === null);
const lastStatus = commonStatuses.reduce((max, status) =>
status.id > max.id ? status : max
, commonStatuses[0] || singing.statuses[0]);
singing.documents.forEach(doc => {
const isCompleted = lastStatus.category === 'completed';
// Ищем строку таблицы с соответствующим номером документа
const targetRow = Array.from(tableRows).find(row => {
const numberCell = row.querySelector('.col__number');
if (numberCell) {
const numberText = numberCell.textContent.trim();
return numberText === doc.number.toString();
}
return false;
});
if (targetRow) {
// Находим ячейку с заголовком
const titleCell = targetRow.querySelector('.col__title');
if (titleCell) {
// Находим span с текстом заголовка
const titleSpan = titleCell.querySelector('.m-table-row-cell__struct');
if (titleSpan) {
// Создаем контейнер для кнопок и текста
const container = document.createElement('div');
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.gap = '8px';
// Сохраняем текст заголовка
const titleText = titleSpan.cloneNode(true);
// Очищаем ячейку
titleCell.innerHTML = '';
if (isCompleted) {
// Для завершенных документов - создаем кнопки по количеству файлов в storagePath
if (singing.storagePath && singing.storagePath.length > 0) {
singing.storagePath.forEach((path, index) => {
const statusButton = document.createElement('span');
statusButton.className = 'status-button';
statusButton.style.display = 'inline-flex';
statusButton.style.alignItems = 'center';
statusButton.style.justifyContent = 'center';
statusButton.style.cursor = 'pointer';
statusButton.style.marginRight = index < singing.storagePath.length - 1 ? '4px' : '0';
// Добавляем номер документа, если их больше одного
const showNumber = singing.storagePath.length > 1;
statusButton.innerHTML = `<i class="m-icon fa-copy fad" style="font-size: 16px; color: #4CAF50;"></i>${showNumber ? `<span style="margin-left: 2px; font-size: 10px; color: #4CAF50;">${index + 1}</span>` : ''}`;
// Добавляем обработчик клика для просмотра конкретного документа
statusButton.addEventListener('click', async (e) => {
e.stopPropagation();
e.preventDefault();
await viewDocument(path);
});
container.appendChild(statusButton);
});
} else {
// Если storagePath пустой, создаем одну кнопку как запасной вариант
const statusButton = document.createElement('span');
statusButton.className = 'status-button';
statusButton.style.display = 'inline-flex';
statusButton.style.alignItems = 'center';
statusButton.style.justifyContent = 'center';
statusButton.style.cursor = 'pointer';
statusButton.style.marginRight = '4px';
statusButton.innerHTML = `<i class="m-icon fa-circle-check fad" style="font-size: 16px; color: #4CAF50;"></i>`;
container.appendChild(statusButton);
}
} else {
// Для незавершенных документов - одна кнопка с часами
const statusButton = document.createElement('span');
statusButton.className = 'status-button';
statusButton.style.display = 'inline-flex';
statusButton.style.alignItems = 'center';
statusButton.style.justifyContent = 'center';
statusButton.style.cursor = 'pointer';
statusButton.style.marginRight = '4px';
statusButton.innerHTML = `<i class="m-icon fa-clock fad" style="font-size: 16px; color: #FFC107;"></i>`;
// Добавляем обработчик клика для открытия модального окна со статусами
statusButton.addEventListener('click', function (e) {
e.stopPropagation();
e.preventDefault();
createStatusesModal(singing);
});
container.appendChild(statusButton);
}
// Добавляем текст заголовка
container.appendChild(titleText);
// Добавляем контейнер в ячейку
titleCell.appendChild(container);
}
} else {
console.log(`Не найдена ячейка заголовка для документа №${doc.number}`);
}
} else {
console.log(`Строка для документа №${doc.number} не найдена в таблице`);
}
});
}
});
} else {
showMessage('Ошибка загрузки статусов документов: ' + (statusesData.message || 'Неизвестная ошибка'), 'error');
}
} catch (error) {
console.info('Ошибка получения статусов документов:', error);
showMessage('Ошибка загрузки статусов документов', 'error');
}
}
// Добавляем стили для статусов
const style = document.createElement('style');
style.textContent = `
.status-history-item:hover {
background-color: #F5F7FA;
}
.el-button--text {
padding: 0;
border: none;
background: none;
}
.el-button--text:hover {
background: none;
opacity: 0.8;
}
.el-button--text:focus {
outline: none;
}
`;
document.head.appendChild(style);
// Запуск
setTimeout(async () => {
addDocsBtn();
await handleDocsStatuses();
}, 500);
})();