release 1.1
This commit is contained in:
+77
-3
@@ -36,7 +36,7 @@ const statuses = {
|
||||
category: "error"
|
||||
},
|
||||
207: {
|
||||
name: "Ожидание действий всех получателей",
|
||||
name: "Ожидание действий",
|
||||
description: "Ожидание действий от всех получателей",
|
||||
category: "processing"
|
||||
},
|
||||
@@ -70,6 +70,11 @@ const statuses = {
|
||||
description: "Подписание отменено для всех получателей",
|
||||
category: "completed"
|
||||
},
|
||||
214: {
|
||||
name: "Успешно. Требуется подписание УКЭП",
|
||||
description: "Требуется подписание УКЭП медицинского работника",
|
||||
category: "processing"
|
||||
},
|
||||
498: {
|
||||
name: "Завершено. Отклонено всеми",
|
||||
description: "Все получатели отказались от подписания",
|
||||
@@ -315,6 +320,23 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (message.action === 'searchRecipients') {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const result = await searchRecipients(message.data);
|
||||
sendResponse(result);
|
||||
} catch (error) {
|
||||
sendResponse({
|
||||
success: false,
|
||||
message: error.message || 'Ошибка поиска получателей',
|
||||
error: error.toString(),
|
||||
data: []
|
||||
});
|
||||
}
|
||||
}, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Функция расширенного поиска
|
||||
@@ -442,8 +464,8 @@ async function getDocumentsFromServer(data) {
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'SUCCESS' && Array.isArray(result.data)) {
|
||||
result.data = enrichStatuses(result.data);
|
||||
if (result.status === 'SUCCESS' && Array.isArray(result.data.singings)) {
|
||||
result.data.singings = enrichStatuses(result.data.singings);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -616,4 +638,56 @@ async function resendDocumentsOnServer(data) {
|
||||
}
|
||||
}
|
||||
|
||||
async function searchRecipients(data) {
|
||||
try {
|
||||
const settings = await chrome.storage.local.get(['serverIp', 'serverPort']);
|
||||
|
||||
if (!settings.serverIp || !settings.serverPort) {
|
||||
throw new Error('Server IP or port not configured');
|
||||
}
|
||||
|
||||
const url = `http://${settings.serverIp}:${settings.serverPort}/api/search-recipients`;
|
||||
|
||||
console.log('[background] Поиск получателей:', url, data);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
signal: AbortSignal.timeout(15000) // 15 секунд таймаут
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`HTTP error: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
let recipients = result;
|
||||
if (result.data && Array.isArray(result.data)) {
|
||||
recipients = result.data;
|
||||
} else if (!Array.isArray(result)) {
|
||||
recipients = [];
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: recipients
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('[background] Ошибка поиска получателей:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: error.message || 'Ошибка поиска получателей',
|
||||
error: error.toString(),
|
||||
data: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Background script loaded');
|
||||
+30
-15
@@ -40,6 +40,8 @@ window.addEventListener('message', (event) => {
|
||||
|
||||
async function loadPageData() {
|
||||
storageData = await chrome.storage.local.get(dataType);
|
||||
const userData = await chrome.storage.local.get(userDataType);
|
||||
storageData.userData = userData[userDataType];
|
||||
|
||||
if (!storageData || !storageData.metaData || Object.keys(storageData.metaData).length === 0) {
|
||||
console.log(`[EXT][content] no data for ${dataType}, injecting...`);
|
||||
@@ -101,8 +103,8 @@ window.addEventListener('message', async (event) => {
|
||||
}
|
||||
|
||||
// Подготавливаем данные для отправки
|
||||
const userData = storageData.metaData.userData;
|
||||
const patientId = storageData.metaData.patients?.[0]?.idPatientMis;
|
||||
const userData = storageData.userData;
|
||||
const patientId = storageData.metaData.patient.idPatientMis;
|
||||
|
||||
if (!userData || !patientId) {
|
||||
window.postMessage({
|
||||
@@ -156,10 +158,6 @@ window.addEventListener('message', async (event) => {
|
||||
|
||||
isProcessing = true;
|
||||
|
||||
console.log('[EXT][content] Forwarding to background:', {
|
||||
docNumbers: event.data.payload.docNumbers
|
||||
});
|
||||
|
||||
// Немедленный ответ о принятии задачи
|
||||
window.postMessage({
|
||||
source: 'medods-extension',
|
||||
@@ -178,8 +176,6 @@ window.addEventListener('message', async (event) => {
|
||||
event.data.payload.docNumbers.includes(parseInt(doc.number, 10))
|
||||
);
|
||||
|
||||
console.log(`📄 [Content] Найдено документов для отправки: ${filteredDocs.length}`);
|
||||
|
||||
if (filteredDocs.length === 0) {
|
||||
window.postMessage({
|
||||
source: 'medods-extension',
|
||||
@@ -198,8 +194,10 @@ window.addEventListener('message', async (event) => {
|
||||
// Собираем все данные для отправки
|
||||
const sendData = {
|
||||
practitioner: storageData.metaData.practitioner,
|
||||
patients: storageData.metaData.patients,
|
||||
patient: storageData.metaData.patient,
|
||||
recipients: event.data.payload.recipients,
|
||||
docs: filteredDocs,
|
||||
deliveryType: event.data.payload.deliveryType
|
||||
};
|
||||
|
||||
// Асинхронная обработка в background
|
||||
@@ -243,7 +241,7 @@ window.addEventListener('message', async (event) => {
|
||||
}
|
||||
|
||||
// Получаем idPatientMis из метаданных
|
||||
const idPatientMis = storageData.metaData.patients?.[0]?.idPatientMis;
|
||||
const idPatientMis = storageData.metaData.patient.idPatientMis;
|
||||
|
||||
if (!idPatientMis) {
|
||||
window.postMessage({
|
||||
@@ -289,7 +287,7 @@ window.addEventListener('message', async (event) => {
|
||||
|
||||
// ОБРАБОТЧИК ПРОВЕРКИ ПРАВ НА ОТЗЫВ
|
||||
if (event.data.type === 'checkRevokePermission') {
|
||||
const hasPermission = String(storageData.metaData?.userData?.id) === String(event.data.payload.userIdLpu);
|
||||
const hasPermission = String(storageData.userData?.id) === String(event.data.payload.userIdLpu);
|
||||
|
||||
window.postMessage({
|
||||
source: 'medods-extension',
|
||||
@@ -302,8 +300,6 @@ window.addEventListener('message', async (event) => {
|
||||
|
||||
// ОБРАБОТЧИК ОТЗЫВА ДОКУМЕНТОВ
|
||||
if (event.data.type === 'revokeDocuments') {
|
||||
console.log('[EXT][content] Запрос на отзыв документов:', event.data.payload);
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'revokeDocuments',
|
||||
data: event.data.payload
|
||||
@@ -318,8 +314,6 @@ window.addEventListener('message', async (event) => {
|
||||
|
||||
// ОБРАБОТЧИК ПОВТОРНОЙ ОТПРАВКИ ДОКУМЕНТОВ
|
||||
if (event.data.type === 'resendDocuments') {
|
||||
console.log('[EXT][content] Запрос на повторную отправку документов:', event.data.payload);
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'resendDocuments',
|
||||
data: event.data.payload
|
||||
@@ -332,6 +326,27 @@ window.addEventListener('message', async (event) => {
|
||||
});
|
||||
}
|
||||
|
||||
// ОБРАБОТЧИК ПОИСКА ПОЛУЧАТЕЛЕЙ ПО ТЕЛЕФОНУ
|
||||
if (event.data.type === 'searchRecipients') {
|
||||
const data = {
|
||||
...event.data.payload,
|
||||
idPatientMis: storageData.metaData.patient.idPatientMis
|
||||
};
|
||||
|
||||
// Отправляем запрос в background
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'searchRecipients',
|
||||
data: data
|
||||
}, (response) => {
|
||||
// Пересылаем результат обратно в singing.js
|
||||
window.postMessage({
|
||||
source: 'medods-extension',
|
||||
type: 'searchRecipientsResponse',
|
||||
payload: response
|
||||
}, '*');
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Основной цикл
|
||||
|
||||
+408
-200
@@ -4,6 +4,7 @@
|
||||
// Глобальная переменная для хранения текущего уведомления
|
||||
let currentMessageEl = null;
|
||||
let messageTimeout = null;
|
||||
let deliveryTypeSettings = {};
|
||||
|
||||
// Функция для отображения сообщений
|
||||
function showMessage(message, type = 'success', duration = 3000) {
|
||||
@@ -136,45 +137,81 @@
|
||||
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;
|
||||
// Разделяем статусы на общие и по получателям
|
||||
const commonStatuses = singing.statuses.filter(s => s.idPatientMis === null);
|
||||
const recipientStatuses = singing.statuses.filter(s => s.idPatientMis !== null);
|
||||
|
||||
const sortedStatuses = [...singing.statuses].sort((a, b) => a.id - b.id);
|
||||
// Группируем статусы по получателям
|
||||
const statusesByRecipient = {};
|
||||
recipientStatuses.forEach(status => {
|
||||
if (status.patient) {
|
||||
const patientName = status.patient.name || 'Неизвестный получатель';
|
||||
if (!statusesByRecipient[patientName]) {
|
||||
statusesByRecipient[patientName] = [];
|
||||
}
|
||||
statusesByRecipient[patientName].push(status);
|
||||
}
|
||||
});
|
||||
|
||||
const statusesHtml = sortedStatuses.map(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' :
|
||||
// Сортируем получателей по имени
|
||||
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 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>
|
||||
`).join('');
|
||||
</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;">
|
||||
${statusesHtml}
|
||||
</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 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);
|
||||
|
||||
@@ -196,80 +233,13 @@
|
||||
document.addEventListener('keydown', escHandler);
|
||||
}
|
||||
|
||||
// Функция создания модального окна подтверждения отзыва
|
||||
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;';
|
||||
|
||||
const backdrop = document.createElement('div');
|
||||
backdrop.className = 'v-modal';
|
||||
backdrop.style.cssText = 'z-index: 2001;';
|
||||
backdrop.tabIndex = 0;
|
||||
|
||||
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);
|
||||
});
|
||||
// Функция для получения имени получателя по 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, title) {
|
||||
try {
|
||||
@@ -364,6 +334,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Функция повторной отправки документов
|
||||
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.error('Ошибка повторной отправки документов:', error);
|
||||
showMessage('Ошибка повторной отправки документов', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Функция создания модального окна подтверждения повторной отправки
|
||||
function createResendConfirmModal(singing) {
|
||||
return new Promise((resolve) => {
|
||||
@@ -371,44 +388,259 @@
|
||||
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;
|
||||
// Собираем всех получателей (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: 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 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 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;">
|
||||
<i class="m-icon fa-redo-alt button-icon fad" style="font-size: 14px; margin-right: 6px;"></i>
|
||||
<span>Отправить повторно</span>
|
||||
</button>
|
||||
</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);
|
||||
|
||||
@@ -438,43 +670,9 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Функция повторной отправки документов
|
||||
async function resendDocuments(singing, modalToClose) {
|
||||
try {
|
||||
// Сразу закрываем окно со списком документов
|
||||
if (modalToClose && modalToClose.parentNode) {
|
||||
modalToClose.remove();
|
||||
}
|
||||
|
||||
// Показываем подтверждение
|
||||
const confirmed = await createResendConfirmModal(singing);
|
||||
if (!confirmed) return;
|
||||
|
||||
showMessage('Повторная отправка документов...', 'info');
|
||||
|
||||
// Отправляем запрос на повторную отправку
|
||||
const response = await sendMessageToContent('resendDocuments', {
|
||||
id: singing.id,
|
||||
trackingId: singing.trackingId,
|
||||
idPatientMis: singing.idPatientMis
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
showMessage('Документы успешно отправлены повторно', 'success');
|
||||
console.log('Документы успешно отправлены повторно');
|
||||
// Обновляем список документов
|
||||
await prepareDocuments();
|
||||
} else {
|
||||
showMessage('Ошибка повторной отправки документов: ' + (response.message || 'Неизвестная ошибка'), 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка повторной отправки документов:', error);
|
||||
showMessage('Ошибка повторной отправки документов', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Функция создания таблицы документов
|
||||
function createDocumentsTable(data) {
|
||||
|
||||
// Создаем модальное окно
|
||||
const modalWrapper = document.createElement('div');
|
||||
modalWrapper.className = 'el-dialog__wrapper';
|
||||
@@ -509,9 +707,10 @@
|
||||
} else {
|
||||
const tableRows = data.map((singing, index) => {
|
||||
// Определяем последний статус
|
||||
const lastStatus = singing.statuses.reduce((max, status) =>
|
||||
const commonStatuses = singing.statuses.filter(s => s.idPatientMis === null);
|
||||
const lastStatus = commonStatuses.reduce((max, status) =>
|
||||
status.id > max.id ? status : max
|
||||
, singing.statuses[0]);
|
||||
, commonStatuses[0] || singing.statuses[0]);
|
||||
|
||||
const statusClass = getStatusClass(lastStatus.category);
|
||||
const statusIcon = getStatusIcon(lastStatus.category);
|
||||
@@ -519,6 +718,14 @@
|
||||
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;">
|
||||
@@ -550,47 +757,47 @@
|
||||
${documentsHtml}
|
||||
</td>
|
||||
<td style="padding: 12px; vertical-align: top;">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; flex: 1;">
|
||||
<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)}</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: 8px;">
|
||||
${singing.storagePath ? `
|
||||
<button class="el-button el-button--text view-main-doc-btn"
|
||||
data-title="${singing.title}"
|
||||
data-path="${singing.storagePath}"
|
||||
title="Просмотреть основной документ"
|
||||
style="padding: 0; border: none;">
|
||||
<i class="el-icon el-icon-document" style="color: #409EFF; font-size: 18px;"></i>
|
||||
</button>
|
||||
` : ''}
|
||||
<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="padding: 0; border: none;">
|
||||
<i class="el-icon el-icon-time" style="color: #909399; font-size: 18px;"></i>
|
||||
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' ? `
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<button class="el-button el-button--text resend-btn"
|
||||
data-index="${index}"
|
||||
title="Повторно отправить документы"
|
||||
style="padding: 0; border: none;">
|
||||
<i class="el-icon el-icon-refresh" style="color: #67C23A; font-size: 18px;"></i>
|
||||
</button>
|
||||
<button class="el-button el-button--text revoke-btn"
|
||||
data-index="${index}"
|
||||
title="Отозвать документы"
|
||||
style="padding: 0; border: none;">
|
||||
<i class="el-icon el-icon-circle-close" style="color: #F56C6C; font-size: 18px;"></i>
|
||||
</button>
|
||||
</div>
|
||||
<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>
|
||||
@@ -707,7 +914,8 @@
|
||||
const response = await sendMessageToContent('prepareDocuments', {});
|
||||
|
||||
if (response.success) {
|
||||
createDocumentsTable(response.data);
|
||||
deliveryTypeSettings = response.data.deliveryType;
|
||||
createDocumentsTable(response.data.singings);
|
||||
} else {
|
||||
showMessage('Ошибка загрузки документов: ' + (response.message || 'Неизвестная ошибка'), 'error');
|
||||
}
|
||||
|
||||
+3
-8
@@ -37,7 +37,7 @@
|
||||
patientPhone = window.gon.specific.client.phone;
|
||||
const patientPhoneNormalized = `+7${patientPhone.replace(/[^\d]/g, '').slice(1, 12)}`;
|
||||
|
||||
metaData.patients = [{
|
||||
metaData.patient = {
|
||||
idPatientMis: window.gon.specific.client.id,
|
||||
familyName: window.gon.specific.client.surname,
|
||||
givenName: window.gon.specific.client.name,
|
||||
@@ -55,10 +55,10 @@
|
||||
documentName: 'СНИЛС',
|
||||
idDocumentType: 223
|
||||
}]
|
||||
}]
|
||||
}
|
||||
|
||||
if (window.gon.specific.client.email) {
|
||||
metaData.patients[0].telecom.push({
|
||||
metaData.patient.telecom.push({
|
||||
system: 'Email',
|
||||
value: window.gon.specific.client.email
|
||||
});
|
||||
@@ -73,11 +73,6 @@
|
||||
data: doc.data
|
||||
}));
|
||||
|
||||
metaData.userData = {
|
||||
id: window.gon.application.current_user.id,
|
||||
login: window.gon.application.current_user.username,
|
||||
};
|
||||
|
||||
// Отправляем только один раз
|
||||
window.postMessage(
|
||||
{
|
||||
|
||||
@@ -612,7 +612,6 @@
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
cursor: help;
|
||||
position: relative;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
@@ -68,9 +68,9 @@
|
||||
|
||||
function getStatusIcon(category) {
|
||||
switch (category) {
|
||||
case 'completed': return '✓';
|
||||
case 'processing': return '⋯';
|
||||
case 'error': return '✗';
|
||||
case 'completed': return '✅';
|
||||
case 'processing': return '⌛';
|
||||
case 'error': return '❌';
|
||||
default: return '?';
|
||||
}
|
||||
}
|
||||
@@ -312,6 +312,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Рендер таблицы
|
||||
// Рендер таблицы
|
||||
function renderTable(data) {
|
||||
if (!data || data.length === 0) {
|
||||
@@ -323,56 +324,78 @@
|
||||
emptyState.classList.add('hidden');
|
||||
|
||||
const rows = data.map(item => {
|
||||
const lastStatus = item.statuses?.reduce((max, s) => s.id > max.id ? s : max, item.statuses[0]);
|
||||
const statusClass = getStatusClass(lastStatus?.category);
|
||||
const statusIcon = getStatusIcon(lastStatus?.category);
|
||||
// Берем только общие статусы (где idPatientMis === null)
|
||||
const commonStatuses = item.statuses?.filter(s => s.idPatientMis === null) || [];
|
||||
|
||||
// Берем последний общий статус
|
||||
const lastStatus = commonStatuses.length > 0
|
||||
? commonStatuses.reduce((max, s) => s.id > max.id ? s : max, commonStatuses[0])
|
||||
: null;
|
||||
|
||||
// Если нет общих статусов, используем первый статус из списка как fallback
|
||||
const displayStatus = lastStatus || (item.statuses && item.statuses[0]) || null;
|
||||
|
||||
const statusClass = getStatusClass(displayStatus?.category);
|
||||
const statusIcon = getStatusIcon(displayStatus?.category);
|
||||
|
||||
// Маппинг типов доставки на отображаемые названия
|
||||
const deliveryTypeLabels = {
|
||||
'sms': 'СМС',
|
||||
'max': 'МАХ',
|
||||
'mila': 'Mila',
|
||||
'goskey': 'Goskey'
|
||||
};
|
||||
|
||||
const documentsHtml = item.documents?.map(doc => {
|
||||
const isClickable = lastStatus?.category !== 'error';
|
||||
// Документы можно просматривать только если статус не error
|
||||
const isClickable = displayStatus?.category !== 'error' && doc.storagePath;
|
||||
const baseClass = isClickable ? 'doc-item clickable-doc' : 'doc-item';
|
||||
|
||||
return `
|
||||
<div class="${baseClass}"
|
||||
${isClickable ? `data-path="${doc.storagePath}" data-title="${doc.title}"` : ''}
|
||||
style="${isClickable ? 'cursor: pointer;' : ''} display: flex; align-items: center; justify-content: space-between;">
|
||||
<span style="flex: 1; text-align: left; word-break: break-word;">№${doc.number} ${doc.title?.replace(/\s+/g, ' ')}</span>
|
||||
${isClickable ? '<span style="margin-left: 4px;">📄</span>' : ''}
|
||||
</div>
|
||||
`;
|
||||
<div class="${baseClass}"
|
||||
${isClickable ? `data-path="${doc.storagePath}" data-title="${doc.title}"` : ''}
|
||||
style="${isClickable ? 'cursor: pointer;' : ''} display: flex; align-items: center; justify-content: space-between;">
|
||||
<span style="flex: 1; text-align: left; word-break: break-word;" title="${doc.title}">№${doc.number} ${doc.title?.replace(/\s+/g, ' ')}</span>
|
||||
${isClickable ? '<span style="margin-left: 4px; color: #409eff;">📄</span>' : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
const hasMainDocument = item.storagePath && !item.storagePath.includes('null');
|
||||
const hasMainDocument = item.storagePath.length > 0;
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>
|
||||
<div class="document-name">${item.patientName || 'Неизвестно'}</div>
|
||||
<div class="document-date">${formatDate(item.created_at)}</div>
|
||||
<div class="doctor-name" title="${item.userName || ''}">
|
||||
<span>👤 ${item.userName || 'Неизвестно'}</span>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="document-name" title="${item.patientName || 'Неизвестно'}">${item.patientName || 'Неизвестно'}</div>
|
||||
<div class="document-date">${formatDate(item.created_at)}</div>
|
||||
<div class="document-date">✉️ ${deliveryTypeLabels[item.deliveryType] || item.deliveryType}</div>
|
||||
<div class="doctor-name" title="${item.userName || ''}">
|
||||
<span>👤 ${item.userName || 'Неизвестно'}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="document-docs">
|
||||
${documentsHtml}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="status-container">
|
||||
<div class="document-status ${statusClass}"
|
||||
title="${displayStatus?.description || ''}">
|
||||
<span style="margin-right: 4px;">${statusIcon}</span>
|
||||
${displayStatus?.name || 'Неизвестно'}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="document-docs">
|
||||
${documentsHtml}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="status-container">
|
||||
<div class="document-status ${statusClass}"
|
||||
title="${lastStatus?.description || ''}">
|
||||
<span style="margin-right: 4px;">${statusIcon}</span>
|
||||
${lastStatus?.name || 'Неизвестно'}
|
||||
</div>
|
||||
<div class="status-date">${formatDate(lastStatus?.created_at || item.created_at)}</div>
|
||||
${hasMainDocument ? `
|
||||
<button class="doc-view-btn" data-path="${item.storagePath}" data-title="Основной документ">
|
||||
📄 Просмотр
|
||||
<div class="status-date">${formatDate(displayStatus?.created_at || item.created_at)}</div>
|
||||
${hasMainDocument && displayStatus?.category !== 'error'
|
||||
? item.storagePath.map((path, index) => `
|
||||
<button class="doc-view-btn" data-path="${path}" title="Итоговый документ ${index + 1}">
|
||||
📄 Файл ${index + 1}
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')
|
||||
: ''}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
|
||||
+16
-4
@@ -94,7 +94,7 @@
|
||||
|
||||
<div class="row g-3">
|
||||
<!-- Период -->
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small text-uppercase text-muted fw-semibold">Период</label>
|
||||
<select class="form-select" id="periodSelect">
|
||||
<option value="today">Сегодня</option>
|
||||
@@ -106,8 +106,20 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Способ отправки -->
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small text-uppercase text-muted fw-semibold">Способ отправки</label>
|
||||
<select class="form-select" id="deliveryTypeSelect">
|
||||
<option value="all">Все способы</option>
|
||||
<option value="sms">СМС-сообщение</option>
|
||||
<option value="max">МАХ-сообщение</option>
|
||||
<option value="mila">Mila-сообщение</option>
|
||||
<option value="goskey">Goskey-сообщение</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Статус -->
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small text-uppercase text-muted fw-semibold">Статус подписания</label>
|
||||
<select class="form-select" id="statusSelect">
|
||||
<option value="all">Все статусы</option>
|
||||
@@ -118,7 +130,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Тип подписания -->
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label small text-uppercase text-muted fw-semibold">Тип подписания</label>
|
||||
<select class="form-select" id="signatureTypeSelect">
|
||||
<option value="all">Все типы</option>
|
||||
@@ -194,7 +206,7 @@
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="sticky-top bg-white">
|
||||
<tr>
|
||||
<th style="width: 15%">Дата</th>
|
||||
<th style="width: 15%">Отправлено</th>
|
||||
<th style="width: 15%">Пациент</th>
|
||||
<th style="width: 35%">Документы</th>
|
||||
<th style="width: 20%">Статус</th>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
dateFrom: null,
|
||||
dateTo: null,
|
||||
status: 'all',
|
||||
deliveryType: 'all',
|
||||
signatureType: 'all',
|
||||
senderName: '',
|
||||
patientName: '',
|
||||
@@ -30,27 +31,6 @@
|
||||
// Bootstrap модальное окно
|
||||
let statusModal = null;
|
||||
|
||||
// Статусы из background.js
|
||||
const statuses = {
|
||||
201: { name: "Создан", description: "Документ создан через API, готов к обработке", category: "processing" },
|
||||
202: { name: "Отправлен на подписание", description: "Документ отправлен получателю", category: "processing" },
|
||||
203: { name: "Просмотрел", description: "Документ просмотрен получателем", category: "processing" },
|
||||
204: { name: "Подписал", description: "Документ подписан получателем", category: "completed" },
|
||||
205: { name: "Отказался", description: "Получатель отказался от подписания", category: "error" },
|
||||
206: { name: "Срок для подписания истек", description: "Срок действия ссылки истек до момента проставления подписи или отказа", category: "error" },
|
||||
207: { name: "Ожидание действий всех получателей", description: "Ожидание действий от всех получателей", category: "processing" },
|
||||
208: { name: "Успешно. Подписан всеми — обработка", description: "Все получатели подписали, документ обрабатывается", category: "processing" },
|
||||
209: { name: "Успешно. Подписан не всеми — обработка", description: "Не все получатели подписали, документ обрабатывается", category: "processing" },
|
||||
210: { name: "Завершено. Подписано всеми", description: "Все получатели подписали, документ сохранен", category: "completed" },
|
||||
211: { name: "Завершено. Подписано не всеми", description: "Не все получатели подписали, документ сохранен", category: "completed" },
|
||||
212: { name: "Отменен для получателя", description: "Подписание отменено для конкретного получателя", category: "error" },
|
||||
213: { name: "Завершено. Отменено для всех", description: "Подписание отменено для всех получателей", category: "completed" },
|
||||
498: { name: "Завершено. Отклонено всеми", description: "Все получатели отказались от подписания", category: "error" },
|
||||
499: { name: "Завершено. Истекло для всех", description: "Срок ссылки истек для всех получателей", category: "error" },
|
||||
500: { name: "Ошибка обработки", description: "Ошибка во время обработки документа", category: "error" },
|
||||
501: { name: "Ошибка доставки получателю", description: "Произошла ошибка при доставке конкретному получателю", category: "error" }
|
||||
};
|
||||
|
||||
// DOM элементы
|
||||
const elements = {
|
||||
periodSelect: document.getElementById('periodSelect'),
|
||||
@@ -58,6 +38,7 @@
|
||||
dateFrom: document.getElementById('dateFrom'),
|
||||
dateTo: document.getElementById('dateTo'),
|
||||
statusSelect: document.getElementById('statusSelect'),
|
||||
deliveryTypeSelect: document.getElementById('deliveryTypeSelect'),
|
||||
signatureTypeSelect: document.getElementById('signatureTypeSelect'),
|
||||
senderInput: document.getElementById('senderInput'),
|
||||
patientInput: document.getElementById('patientInput'),
|
||||
@@ -118,6 +99,7 @@
|
||||
// Применяем фильтры к элементам управления
|
||||
elements.periodSelect.value = state.filters.period;
|
||||
elements.statusSelect.value = state.filters.status;
|
||||
elements.deliveryTypeSelect.value = state.filters.deliveryType;
|
||||
elements.signatureTypeSelect.value = state.filters.signatureType;
|
||||
elements.senderInput.value = state.filters.senderName || '';
|
||||
elements.patientInput.value = state.filters.patientName || '';
|
||||
@@ -148,6 +130,7 @@
|
||||
|
||||
// Фильтры
|
||||
elements.statusSelect.addEventListener('change', updateFilters);
|
||||
elements.deliveryTypeSelect.addEventListener('change', updateFilters);
|
||||
elements.signatureTypeSelect.addEventListener('change', updateFilters);
|
||||
elements.senderInput.addEventListener('input', debounce(updateFilters, 500));
|
||||
elements.patientInput.addEventListener('input', debounce(updateFilters, 500));
|
||||
@@ -216,18 +199,17 @@
|
||||
switch (period) {
|
||||
case 'today':
|
||||
break;
|
||||
case 'yesterday':
|
||||
from.setDate(from.getDate() - 1);
|
||||
today.setDate(today.getDate() - 1);
|
||||
case '3days':
|
||||
from.setDate(from.getDate() - 3);
|
||||
break;
|
||||
case 'week':
|
||||
case '7days':
|
||||
from.setDate(from.getDate() - 7);
|
||||
break;
|
||||
case 'month':
|
||||
from.setMonth(from.getMonth() - 1);
|
||||
case '30days':
|
||||
from.setDate(from.getDate() - 30);
|
||||
break;
|
||||
case 'quarter':
|
||||
from.setMonth(from.getMonth() - 3);
|
||||
case '90days':
|
||||
from.setDate(from.getDate() - 90);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -245,6 +227,7 @@
|
||||
dateFrom: elements.dateFrom.value,
|
||||
dateTo: elements.dateTo.value,
|
||||
status: elements.statusSelect.value,
|
||||
deliveryType: elements.deliveryTypeSelect.value,
|
||||
signatureType: elements.signatureTypeSelect.value,
|
||||
senderName: elements.senderInput.value.trim(),
|
||||
patientName: elements.patientInput.value.trim(),
|
||||
@@ -280,6 +263,7 @@
|
||||
elements.periodSelect.value = 'today';
|
||||
elements.customDateRange.style.display = 'none';
|
||||
elements.statusSelect.value = 'all';
|
||||
elements.deliveryTypeSelect.value = 'all';
|
||||
elements.signatureTypeSelect.value = 'all';
|
||||
elements.senderInput.value = '';
|
||||
elements.patientInput.value = '';
|
||||
@@ -311,8 +295,7 @@
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
// Обогащаем статусы
|
||||
state.results = enrichStatuses(response.data || []);
|
||||
state.results = response.data || [];
|
||||
state.pagination.totalItems = state.results.length;
|
||||
state.pagination.totalPages = Math.ceil(state.pagination.totalItems / state.pagination.itemsPerPage);
|
||||
state.pagination.currentPage = 1;
|
||||
@@ -329,31 +312,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Обогащение статусов
|
||||
function enrichStatuses(data) {
|
||||
if (!Array.isArray(data)) return data;
|
||||
|
||||
data.forEach(item => {
|
||||
if (Array.isArray(item.statuses)) {
|
||||
item.statuses.forEach(status => {
|
||||
const statusInfo = statuses[status.status] || {
|
||||
name: `Статус ${status.status}`,
|
||||
description: `Неизвестный статус ${status.status}`,
|
||||
category: "error"
|
||||
};
|
||||
status.name = statusInfo.name;
|
||||
status.description = statusInfo.description;
|
||||
status.category = statusInfo.category;
|
||||
});
|
||||
|
||||
// Сортируем статусы по id для хронологии
|
||||
item.statuses.sort((a, b) => a.id - b.id);
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Определение типа подписания
|
||||
function getSignatureType(item) {
|
||||
return item.esiaAuth === true ? 'esia' : 'mchd';
|
||||
@@ -440,65 +398,116 @@
|
||||
|
||||
// Создание строки таблицы
|
||||
function createTableRow(item) {
|
||||
const lastStatus = item.statuses?.length > 0 ? item.statuses[item.statuses.length - 1] : null;
|
||||
const statusClass = lastStatus?.category || 'warning';
|
||||
// Берем только общие статусы (где idPatientMis === null)
|
||||
const commonStatuses = item.statuses?.filter(s => s.idPatientMis === null) || [];
|
||||
|
||||
let lastCommonStatus = null;
|
||||
if (commonStatuses.length > 0) {
|
||||
lastCommonStatus = commonStatuses.reduce((last, current) => current.id > last.id ? current : last);
|
||||
}
|
||||
|
||||
// Если нет общих статусов, используем первый статус из списка как fallback
|
||||
const displayStatus = lastCommonStatus || (item.statuses && item.statuses[0]) || null;
|
||||
|
||||
const date = formatDateTime(item.created_at);
|
||||
|
||||
// Документы
|
||||
// Маппинг типов доставки на отображаемые названия
|
||||
const deliveryTypeLabels = {
|
||||
'sms': 'СМС',
|
||||
'max': 'МАХ',
|
||||
'mila': 'Mila',
|
||||
'goskey': 'Goskey'
|
||||
};
|
||||
const deliveryType = item.deliveryType && deliveryTypeLabels[item.deliveryType] || item.deliveryType || '—';
|
||||
|
||||
// Получаем уникальных получателей из статусов (исключая самого пациента)
|
||||
const recipients = new Set();
|
||||
if (item.statuses && Array.isArray(item.statuses)) {
|
||||
item.statuses.forEach(status => {
|
||||
// Проверяем, что это статус получателя (есть patient) и это не сам пациент
|
||||
if (status.patient && status.patient.name &&
|
||||
status.idPatientMis !== item.idPatientMis) {
|
||||
recipients.add(status.patient.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Преобразуем Set в массив и сортируем
|
||||
const recipientsList = Array.from(recipients).sort();
|
||||
|
||||
// Формируем HTML для получателей
|
||||
const recipientsHtml = recipientsList.length > 0 ? `
|
||||
<div class="recipients-list small text-muted mt-1" style="font-size: 10px; line-height: 1.3;">
|
||||
<i class="bi bi-people me-1"></i>
|
||||
${recipientsList.join(', ')}
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
// Документы - теперь название отображается полностью, без обрезания
|
||||
const documentsHtml = item.documents?.map(doc => {
|
||||
const canView = lastStatus?.category !== 'completed';
|
||||
// Документы можно просматривать только если статус не error
|
||||
const canView = displayStatus?.category !== 'error' && doc.storagePath && doc.storagePath.length > 0;
|
||||
return `
|
||||
<div class="document-item p-2 mb-1 bg-light rounded ${canView ? 'text-primary' : 'text-muted'}"
|
||||
data-doc-path="${doc.storagePath || ''}"
|
||||
data-doc-title="${doc.title}"
|
||||
data-can-view="${canView}"
|
||||
style="cursor: ${canView ? 'pointer' : 'default'};">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="fw-semibold">№${doc.number}</span>
|
||||
<span class="small text-truncate flex-grow-1">${doc.title}</span>
|
||||
${canView ? '<i class="bi bi-eye"></i>' : ''}
|
||||
</div>
|
||||
<div class="document-item p-2 mb-1 bg-light rounded ${canView ? 'text-primary' : 'text-muted'}"
|
||||
data-doc-path="${doc.storagePath || ''}"
|
||||
data-doc-title="${doc.title}"
|
||||
data-can-view="${canView}"
|
||||
style="cursor: ${canView ? 'pointer' : 'default'};">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="fw-semibold flex-shrink-0">№${doc.number}</span>
|
||||
<span class="small flex-grow-1" style="white-space: normal; word-break: break-word;" title="${doc.title}">${doc.title}</span>
|
||||
${canView ? '<i class="bi bi-eye flex-shrink-0"></i>' : ''}
|
||||
</div>
|
||||
`;
|
||||
</div>
|
||||
`;
|
||||
}).join('') || '<span class="text-muted small">—</span>';
|
||||
|
||||
// Статус
|
||||
const statusHtml = lastStatus ? `
|
||||
<div>
|
||||
<span class="badge ${getStatusBadgeClass(lastStatus.category)} status-badge ${item.statuses?.length > 1 ? '' : ''}"
|
||||
data-status-history='${JSON.stringify(item.statuses)}'>
|
||||
<i class="bi ${getStatusIcon(lastStatus.category)} me-1"></i>
|
||||
${lastStatus.name || 'Неизвестно'}
|
||||
</span>
|
||||
<div class="small text-muted mt-1">${formatDateTime(lastStatus.created_at)}</div>
|
||||
</div>
|
||||
` : '<span class="text-muted small">—</span>';
|
||||
const statusHtml = displayStatus ? `
|
||||
<div>
|
||||
<span class="badge ${getStatusBadgeClass(displayStatus.category)} status-badge ${item.statuses?.length > 1 ? '' : ''}"
|
||||
data-status-history='${JSON.stringify(item.statuses)}'>
|
||||
<i class="bi ${getStatusIcon(displayStatus.category)} me-1"></i>
|
||||
${displayStatus.name || displayStatus.description || 'Неизвестно'}
|
||||
</span>
|
||||
<div class="small text-muted mt-1">${formatDateTime(displayStatus.created_at)}</div>
|
||||
</div>
|
||||
` : '<span class="text-muted small">—</span>';
|
||||
|
||||
// Тип и кнопка
|
||||
// Тип подписания
|
||||
const signatureTypeClass = getSignatureTypeClass(item);
|
||||
const finalAction = lastStatus?.category === 'completed' && item.storagePath ? `
|
||||
<button class="btn btn-sm btn-outline-primary mt-1 view-final-btn" data-path="${item.storagePath}" data-title="Итоговый документ">
|
||||
|
||||
// Кнопка просмотра итогового документа
|
||||
const finalAction = displayStatus?.category === 'completed' && item.storagePath && item.storagePath.length > 0
|
||||
? item.storagePath.map((path, index) => `
|
||||
<button class="btn btn-sm btn-outline-primary mt-1 view-final-btn" data-path="${path}" title="Итоговый документ ${index + 1}">
|
||||
<i class="bi bi-file-pdf"></i>
|
||||
</button>
|
||||
` : '';
|
||||
`).join('')
|
||||
: '';
|
||||
|
||||
return `
|
||||
<tr data-id="${item.id || ''}">
|
||||
<td class="align-middle"><span class="small">${date}</span></td>
|
||||
<td class="align-middle"><span class="small">${item.patientName || 'Неизвестно'}</span></td>
|
||||
<td class="align-middle" style="max-width: 350px;">
|
||||
<div class="documents-list" style="max-height: 100px; overflow-y: auto;">
|
||||
${documentsHtml}
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">${statusHtml}</td>
|
||||
<td class="align-middle"><span class="small">${item.userName || '—'}</span></td>
|
||||
<td class="align-middle">
|
||||
<span class="badge ${signatureTypeClass}">${getSignatureTypeText(item)}</span>
|
||||
${finalAction}
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
<tr data-id="${item.id || ''}">
|
||||
<td class="align-middle"><span class="small">${date}<br>✉️ ${deliveryType}</span></td>
|
||||
<td class="align-middle">
|
||||
<div>
|
||||
<span class="small fw-semibold" title="${item.patientName || 'Неизвестно'}">${item.patientName || 'Неизвестно'}</span>
|
||||
${recipientsHtml}
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle" style="max-width: 350px;">
|
||||
<div class="documents-list" style="max-height: 100px; overflow-y: auto;">
|
||||
${documentsHtml}
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">${statusHtml}</td>
|
||||
<td class="align-middle"><span class="small" title="${item.userName || ''}">${item.userName || '—'}</span></td>
|
||||
<td class="align-middle">
|
||||
<span class="badge ${signatureTypeClass}">${getSignatureTypeText(item)}</span>
|
||||
${finalAction}
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
// Bootstrap классы для статусов
|
||||
@@ -620,30 +629,88 @@
|
||||
|
||||
// Показать историю статусов (Bootstrap модалка)
|
||||
function showStatusHistory(statuses) {
|
||||
const statusItems = statuses.map(status => {
|
||||
const statusInfo = status || {
|
||||
name: `Статус ${status.status}`,
|
||||
description: '',
|
||||
category: 'warning'
|
||||
};
|
||||
if (!statuses || statuses.length === 0) {
|
||||
document.getElementById('statusHistoryBody').innerHTML = '<p class="text-muted text-center">Нет истории статусов</p>';
|
||||
if (statusModal) statusModal.show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Разделяем статусы на общие и по получателям
|
||||
const commonStatuses = statuses.filter(s => s.idPatientMis === null);
|
||||
const recipientStatuses = 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 createStatusHtml = (status) => {
|
||||
return `
|
||||
<div class="d-flex gap-3 mb-3 pb-2 border-bottom">
|
||||
<div class="flex-shrink-0">
|
||||
<span class="badge ${getStatusBadgeClass(statusInfo.category)} p-2">
|
||||
<i class="bi ${getStatusIcon(statusInfo.category)}"></i>
|
||||
<span class="badge ${getStatusBadgeClass(status.category)} p-2">
|
||||
<i class="bi ${getStatusIcon(status.category)}"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-semibold">${statusInfo.name}</div>
|
||||
<div class="small text-muted mb-1">${statusInfo.description || 'Нет описания'}</div>
|
||||
<div class="fw-semibold">${status.name || status.description || 'Неизвестно'}</div>
|
||||
<div class="small text-muted mb-1">${status.description || ''}</div>
|
||||
<div class="small text-muted">${formatDateTime(status.created_at)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
// Генерируем HTML для общих статусов (сортируем по id для хронологии)
|
||||
const commonStatusesHtml = commonStatuses
|
||||
.sort((a, b) => a.id - b.id)
|
||||
.map(createStatusHtml)
|
||||
.join('');
|
||||
|
||||
// Генерируем HTML для статусов получателей
|
||||
const recipientStatusesHtml = sortedRecipients.map(recipientName => {
|
||||
const recipientStatusesList = statusesByRecipient[recipientName]
|
||||
.sort((a, b) => a.id - b.id)
|
||||
.map(createStatusHtml)
|
||||
.join('');
|
||||
|
||||
return `
|
||||
<div class="mt-3 mb-2">
|
||||
<div class="bg-light p-2 rounded">
|
||||
<i class="bi bi-person-circle me-1 text-primary"></i>
|
||||
<span class="fw-semibold">${recipientName}</span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
${recipientStatusesList}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
document.getElementById('statusHistoryBody').innerHTML = statusItems || '<p class="text-muted text-center">Нет истории статусов</p>';
|
||||
const fullHtml = `
|
||||
<div class="status-history">
|
||||
${commonStatusesHtml}
|
||||
${recipientStatusesHtml ? `
|
||||
<div class="mt-3 pt-2 border-top">
|
||||
<h6 class="mb-3">Статусы получателей</h6>
|
||||
${recipientStatusesHtml}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('statusHistoryBody').innerHTML = fullHtml;
|
||||
|
||||
if (statusModal) {
|
||||
statusModal.show();
|
||||
@@ -663,8 +730,12 @@
|
||||
|
||||
// Создаем тост
|
||||
const toastId = 'toast-' + Date.now();
|
||||
const bgClass = type === 'success' ? 'bg-success' :
|
||||
type === 'danger' ? 'bg-danger' :
|
||||
type === 'warning' ? 'bg-warning' : 'bg-info';
|
||||
|
||||
const toastHtml = `
|
||||
<div id="${toastId}" class="toast align-items-center text-white bg-${type} border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div id="${toastId}" class="toast align-items-center text-white ${bgClass} border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">
|
||||
${message}
|
||||
@@ -677,7 +748,7 @@
|
||||
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
|
||||
|
||||
const toastElement = document.getElementById(toastId);
|
||||
const toast = new bootstrap.Toast(toastElement, { autohide: true, delay: 5000 });
|
||||
const toast = new bootstrap.Toast(toastElement, { autohide: true, delay: 3000 });
|
||||
toast.show();
|
||||
|
||||
// Удаляем после скрытия
|
||||
|
||||
+389
-23
@@ -197,6 +197,19 @@
|
||||
const inProgress = preSingingData.inProgress || [];
|
||||
const complete = preSingingData.complete || [];
|
||||
const daysRemainingControl = preSingingData.daysRemainingControl || 30;
|
||||
const deliveryType = preSingingData.deliveryType || ["sms"];
|
||||
const multipleSending = preSingingData.multipleSending || false;
|
||||
|
||||
// Массив получателей (только если multipleSending = true)
|
||||
let recipients = [];
|
||||
|
||||
// Маппинг типов доставки на отображаемые названия
|
||||
const deliveryTypeLabels = {
|
||||
'sms': 'СМС-сообщение',
|
||||
'max': 'МАХ-сообщение',
|
||||
'mila': 'Mila-сообщение',
|
||||
'goskey': 'Goskey-сообщение'
|
||||
};
|
||||
|
||||
// Определяем тип документов
|
||||
const hasIds = documents.some(doc => doc.title.includes('ИДС'));
|
||||
@@ -309,7 +322,7 @@
|
||||
|
||||
const dialog = document.createElement('div');
|
||||
dialog.className = 'el-dialog';
|
||||
dialog.style.cssText = 'margin-top: 15vh; width: 700px; z-index: 2002; position: relative; margin: 15vh auto 50px; background: #fff; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);';
|
||||
dialog.style.cssText = 'margin-top: 15vh; width: 750px; z-index: 2002; position: relative; margin: 15vh auto 50px; background: #fff; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);';
|
||||
|
||||
// Создаем заголовок
|
||||
const dialogHeader = document.createElement('div');
|
||||
@@ -335,7 +348,7 @@
|
||||
// Создаем тело диалога
|
||||
const dialogBody = document.createElement('div');
|
||||
dialogBody.className = 'el-dialog__body';
|
||||
dialogBody.style.cssText = 'padding: 15px; color: #606266; font-size: 14px;';
|
||||
dialogBody.style.cssText = 'padding: 15px; padding-bottom: 0; color: #606266; font-size: 14px;';
|
||||
|
||||
// Создаем контент
|
||||
const dialogContent = document.createElement('div');
|
||||
@@ -492,12 +505,275 @@
|
||||
documentsSection.appendChild(documentsList);
|
||||
|
||||
dialogContent.appendChild(documentsSection);
|
||||
|
||||
// ========== НАЧАЛО: Блок дополнительных получателей (только при multipleSending=true) ==========
|
||||
if (multipleSending) {
|
||||
// Функция рендеринга блока получателей (будет вызываться при изменениях)
|
||||
function renderRecipientsBlock() {
|
||||
// Удаляем старый контейнер, если он есть
|
||||
const oldContainer = dialogContent.querySelector('.recipients-container');
|
||||
if (oldContainer) {
|
||||
oldContainer.remove();
|
||||
}
|
||||
|
||||
// Если получателей нет - не отображаем блок
|
||||
if (recipients.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recipientsContainer = document.createElement('div');
|
||||
recipientsContainer.className = 'recipients-container';
|
||||
recipientsContainer.style.cssText = 'margin-top: 15px; margin-bottom: 0px;';
|
||||
|
||||
// Заголовок с количеством получателей
|
||||
const recipientsHeader = document.createElement('div');
|
||||
recipientsHeader.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;';
|
||||
|
||||
const recipientsTitle = document.createElement('h4');
|
||||
recipientsTitle.style.cssText = 'margin: 0; color: #303133; font-size: 14px; font-weight: 500;';
|
||||
recipientsTitle.textContent = 'Дополнительные получатели:';
|
||||
|
||||
const recipientsCount = document.createElement('span');
|
||||
recipientsCount.className = 'el-tag el-tag--info el-tag--mini';
|
||||
recipientsCount.style.cssText = 'margin-left: 6px;';
|
||||
recipientsCount.textContent = recipients.length;
|
||||
|
||||
const titleWrapper = document.createElement('div');
|
||||
titleWrapper.style.cssText = 'display: flex; align-items: center;';
|
||||
titleWrapper.appendChild(recipientsTitle);
|
||||
titleWrapper.appendChild(recipientsCount);
|
||||
|
||||
recipientsHeader.appendChild(titleWrapper);
|
||||
|
||||
// Компактный список получателей
|
||||
const recipientsList = document.createElement('div');
|
||||
recipientsList.className = 'recipients-list';
|
||||
recipientsList.style.cssText = 'display: flex; flex-wrap: wrap; gap: 6px; margin-top: 5px;';
|
||||
|
||||
recipients.forEach((recipient, index) => {
|
||||
const recipientTag = document.createElement('span');
|
||||
recipientTag.className = 'el-tag el-tag--info el-tag--small';
|
||||
recipientTag.style.cssText = 'display: inline-flex; align-items: center; height: 24px; line-height: 22px; padding: 0 8px; margin-right: 4px; margin-bottom: 4px;';
|
||||
|
||||
const nameSpan = document.createElement('span');
|
||||
nameSpan.textContent = `${recipient.surname} ${recipient.name.charAt(0)}. ${recipient.secondName ? recipient.secondName.charAt(0) + '.' : ''}`;
|
||||
|
||||
const closeIcon = document.createElement('i');
|
||||
closeIcon.className = 'el-icon-close';
|
||||
closeIcon.style.cssText = 'margin-left: 4px; cursor: pointer; font-size: 12px;';
|
||||
closeIcon.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
if (window.confirm(`Удалить получателя ${recipient.surname} ${recipient.name}?`)) {
|
||||
recipients.splice(index, 1);
|
||||
renderRecipientsBlock(); // Перерисовываем блок
|
||||
}
|
||||
});
|
||||
|
||||
recipientTag.appendChild(nameSpan);
|
||||
recipientTag.appendChild(closeIcon);
|
||||
recipientsList.appendChild(recipientTag);
|
||||
});
|
||||
|
||||
recipientsContainer.appendChild(recipientsHeader);
|
||||
recipientsContainer.appendChild(recipientsList);
|
||||
|
||||
// Вставляем после списка документов
|
||||
dialogContent.insertBefore(recipientsContainer, documentsSection.nextSibling);
|
||||
}
|
||||
|
||||
// Кнопка добавления получателя
|
||||
const addButtonContainer = document.createElement('div');
|
||||
addButtonContainer.style.cssText = 'margin: 10px 0;';
|
||||
|
||||
const addRecipientBtn = document.createElement('button');
|
||||
addRecipientBtn.type = 'button';
|
||||
addRecipientBtn.className = 'el-button m-button el-button--success el-button--small is-plain button-with-icon';
|
||||
if (!canSend) {
|
||||
addRecipientBtn.classList.add('is-disabled');
|
||||
addRecipientBtn.disabled = true;
|
||||
}
|
||||
addRecipientBtn.innerHTML = '<i class="m-icon fa-plus button-icon fad fa-fw" style="font-size: 17px;"></i> Добавить получателя';
|
||||
|
||||
addButtonContainer.appendChild(addRecipientBtn);
|
||||
dialogContent.appendChild(addButtonContainer);
|
||||
|
||||
// Контейнер для аккордеона (поиск получателей)
|
||||
const searchAccordion = document.createElement('div');
|
||||
searchAccordion.className = 'search-accordion';
|
||||
searchAccordion.style.cssText = 'margin-top: 10px; display: none;';
|
||||
|
||||
const searchCard = document.createElement('div');
|
||||
searchCard.className = 'el-card';
|
||||
searchCard.style.cssText = 'border: 1px solid #EBEEF5; border-radius: 4px;';
|
||||
|
||||
const searchBody = document.createElement('div');
|
||||
searchBody.className = 'el-card__body';
|
||||
searchBody.style.cssText = 'padding: 12px;';
|
||||
|
||||
// Поле ввода номера телефона и кнопка поиска
|
||||
const searchRow = document.createElement('div');
|
||||
searchRow.style.cssText = 'display: flex; gap: 8px; margin-bottom: 10px;';
|
||||
|
||||
const phoneInput = document.createElement('input');
|
||||
phoneInput.type = 'text';
|
||||
phoneInput.placeholder = 'Введите номер телефона';
|
||||
phoneInput.className = 'el-input__inner';
|
||||
phoneInput.style.cssText = 'flex: 1; height: 28px; line-height: 28px; border: 1px solid #DCDFE6; border-radius: 4px; padding: 0 8px; font-size: 12px;';
|
||||
|
||||
const searchBtn = document.createElement('button');
|
||||
searchBtn.type = 'button';
|
||||
searchBtn.className = 'el-button m-button el-button--primary el-button--small is-plain button-with-icon';
|
||||
searchBtn.innerHTML = '<i class="m-icon fa-search button-icon fad fa-fw" style="font-size: 17px;"></i> Найти';
|
||||
searchBtn.style.cssText = 'height: 28px;';
|
||||
|
||||
searchRow.appendChild(phoneInput);
|
||||
searchRow.appendChild(searchBtn);
|
||||
|
||||
// Контейнер для результатов поиска
|
||||
const searchResults = document.createElement('div');
|
||||
searchResults.className = 'search-results';
|
||||
searchResults.style.cssText = 'max-height: 150px; overflow-y: auto; font-size: 12px;';
|
||||
|
||||
// Кнопка отмены
|
||||
const cancelSearchBtn = document.createElement('button');
|
||||
cancelSearchBtn.type = 'button';
|
||||
cancelSearchBtn.className = 'el-button m-button el-button--small is-plain button-with-icon cancel-button';
|
||||
|
||||
const cancelSearchIcon = document.createElement('i');
|
||||
cancelSearchIcon.className = 'm-icon fa-times button-icon fad';
|
||||
cancelSearchIcon.style.cssText = 'font-size: 14px; margin-right: 6px;';
|
||||
|
||||
const cancelSearchText = document.createElement('span');
|
||||
cancelSearchText.textContent = 'Отмена';
|
||||
|
||||
cancelSearchBtn.appendChild(cancelSearchIcon);
|
||||
cancelSearchBtn.appendChild(cancelSearchText);
|
||||
|
||||
searchBody.appendChild(searchRow);
|
||||
searchBody.appendChild(searchResults);
|
||||
searchBody.appendChild(cancelSearchBtn);
|
||||
searchCard.appendChild(searchBody);
|
||||
searchAccordion.appendChild(searchCard);
|
||||
|
||||
dialogContent.appendChild(searchAccordion);
|
||||
|
||||
// Функция поиска получателей по номеру телефона
|
||||
async function searchRecipients(phone) {
|
||||
if (!phone || phone.trim() === '') {
|
||||
showMessage('Введите номер телефона', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
// Показываем загрузку
|
||||
searchResults.innerHTML = '<div style="text-align: center; padding: 15px;"><i class="el-icon-loading" style="font-size: 18px;"></i><p style="margin: 5px 0 0; color: #909399;">Поиск...</p></div>';
|
||||
|
||||
try {
|
||||
const result = await sendMessageToContent('searchRecipients', {
|
||||
phone: phone.trim()
|
||||
});
|
||||
|
||||
searchResults.innerHTML = '';
|
||||
|
||||
if (result && result.success && result.data && result.data.length > 0) {
|
||||
result.data.forEach(person => {
|
||||
const personItem = document.createElement('div');
|
||||
personItem.style.cssText = 'display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid #F0F0F0;';
|
||||
|
||||
const personInfo = document.createElement('div');
|
||||
personInfo.style.cssText = 'flex: 1;';
|
||||
|
||||
const fullName = document.createElement('span');
|
||||
fullName.style.cssText = 'font-size: 12px; color: #606266;';
|
||||
fullName.textContent = `${person.surname} ${person.name} ${person.secondName || ''}`.trim();
|
||||
|
||||
const birthdate = document.createElement('span');
|
||||
birthdate.style.cssText = 'font-size: 11px; color: #909399; margin-left: 6px;';
|
||||
birthdate.textContent = person.birthDate ? `(${person.birthDate})` : '';
|
||||
|
||||
personInfo.appendChild(fullName);
|
||||
personInfo.appendChild(birthdate);
|
||||
|
||||
const addBtn = document.createElement('button');
|
||||
addBtn.type = 'button';
|
||||
addBtn.className = 'el-button el-button--primary el-button--mini';
|
||||
addBtn.innerHTML = 'Добавить';
|
||||
addBtn.style.cssText = 'height: 24px; padding: 0 8px;';
|
||||
|
||||
// Проверяем, не добавлен ли уже этот получатель
|
||||
const alreadyAdded = recipients.some(r => r.id === person.id);
|
||||
if (alreadyAdded) {
|
||||
addBtn.disabled = true;
|
||||
addBtn.classList.add('is-disabled');
|
||||
addBtn.innerHTML = 'Добавлен';
|
||||
}
|
||||
|
||||
addBtn.addEventListener('click', () => {
|
||||
if (!alreadyAdded) {
|
||||
recipients.push(person);
|
||||
renderRecipientsBlock(); // Обновляем отображение блока получателей
|
||||
// Закрываем аккордеон
|
||||
searchAccordion.style.display = 'none';
|
||||
// Очищаем поле ввода и результаты
|
||||
phoneInput.value = '';
|
||||
searchResults.innerHTML = '';
|
||||
}
|
||||
});
|
||||
|
||||
personItem.appendChild(personInfo);
|
||||
personItem.appendChild(addBtn);
|
||||
searchResults.appendChild(personItem);
|
||||
});
|
||||
} else {
|
||||
searchResults.innerHTML = '<div style="text-align: center; padding: 15px; color: #909399;">Ничего не найдено</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка поиска получателей:', error);
|
||||
searchResults.innerHTML = '<div class="el-alert el-alert--error" style="margin: 5px 0;"><div class="el-alert__content"><span class="el-alert__title">Ошибка поиска</span><p class="el-alert__description">' + error.message + '</p></div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Обработчик кнопки добавления получателя
|
||||
addRecipientBtn.addEventListener('click', () => {
|
||||
if (searchAccordion.style.display === 'none' || searchAccordion.style.display === '') {
|
||||
searchAccordion.style.display = 'block';
|
||||
phoneInput.value = '';
|
||||
searchResults.innerHTML = '';
|
||||
phoneInput.focus();
|
||||
} else {
|
||||
searchAccordion.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик кнопки поиска
|
||||
searchBtn.addEventListener('click', () => {
|
||||
searchRecipients(phoneInput.value);
|
||||
});
|
||||
|
||||
// Поиск при нажатии Enter
|
||||
phoneInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
searchRecipients(phoneInput.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик кнопки отмены
|
||||
cancelSearchBtn.addEventListener('click', () => {
|
||||
searchAccordion.style.display = 'none';
|
||||
phoneInput.value = '';
|
||||
searchResults.innerHTML = '';
|
||||
});
|
||||
|
||||
// Инициализация - рендерим блок (если есть получатели)
|
||||
renderRecipientsBlock();
|
||||
}
|
||||
// ========== КОНЕЦ: Блок дополнительных получателей ==========
|
||||
|
||||
dialogBody.appendChild(dialogContent);
|
||||
|
||||
// Создаем футер
|
||||
const dialogFooter = document.createElement('div');
|
||||
dialogFooter.className = 'el-dialog__footer';
|
||||
dialogFooter.style.cssText = 'display: flex; justify-content: space-between; align-items: center;';
|
||||
dialogFooter.style.cssText = 'display: flex; justify-content: space-between; align-items: flex-start; padding: 15px; padding-top: 10px;';
|
||||
|
||||
// Левая часть футера (информация о сроке действия разрешения на подпись)
|
||||
const footerLeft = document.createElement('div');
|
||||
@@ -506,7 +782,7 @@
|
||||
if (signature.expiration && signature.type) {
|
||||
// Тип разрешения
|
||||
const typeContainer = document.createElement('div');
|
||||
typeContainer.style.cssText = 'margin-bottom: 5px; margin-top: 0px;';
|
||||
typeContainer.style.cssText = 'margin-bottom: 5px; margin-top: 10px;';
|
||||
|
||||
const typeText = document.createElement('span');
|
||||
typeText.style.cssText = 'font-size: 12px; font-weight: 500; color: #606266;';
|
||||
@@ -534,8 +810,8 @@
|
||||
expirationContainer.appendChild(expirationLabel);
|
||||
expirationContainer.appendChild(expirationDate);
|
||||
|
||||
// Остаточный срок (показываем справа от даты если меньше 30 дней)
|
||||
if (daysRemaining >= 0 && daysRemaining < 30) {
|
||||
// Остаточный срок (показываем справа от даты если недостаточно дней)
|
||||
if (daysRemaining >= 0 && daysRemaining < daysRemainingControl) {
|
||||
const warningIcon = document.createElement('i');
|
||||
warningIcon.className = 'el-icon-warning';
|
||||
warningIcon.style.cssText = 'font-size: 14px; color: #e6a23c; margin-left: 4px; margin-right: 4px;';
|
||||
@@ -570,10 +846,88 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Правая часть футера (кнопки)
|
||||
// Правая часть футера (выбор способа доставки и кнопки)
|
||||
const footerRight = document.createElement('div');
|
||||
footerRight.className = 'dialog-footer';
|
||||
footerRight.style.cssText = 'display: flex; align-items: center; padding-top: 10px;';
|
||||
footerRight.style.cssText = 'display: flex; flex-direction: column; align-items: flex-end;';
|
||||
|
||||
const deliveryBlock = document.createElement('div');
|
||||
deliveryBlock.style.cssText = 'margin-bottom: 10px; width: 210px;';
|
||||
|
||||
const selectWrapper = document.createElement('div');
|
||||
selectWrapper.style.cssText = `
|
||||
position: relative;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background: ${canSend ? 'white' : '#f5f7fa'};
|
||||
transition: all 0.2s;
|
||||
width: 100%;
|
||||
opacity: ${canSend ? '1' : '0.6'};
|
||||
pointer-events: ${canSend ? 'auto' : 'none'};
|
||||
`;
|
||||
|
||||
// Создаем select элемент
|
||||
const deliverySelect = document.createElement('select');
|
||||
deliverySelect.style.cssText = `
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
padding: 3px 24px 3px 8px;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 12px;
|
||||
color: ${canSend ? '#606266' : '#c0c4cc'};
|
||||
outline: none;
|
||||
cursor: ${canSend ? 'pointer' : 'not-allowed'};
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
font-family: Verdana, Arial, Helvetica, sans-serif;
|
||||
`;
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = null;
|
||||
option.textContent = "Способ отправки";
|
||||
option.selected = true;
|
||||
option.disabled = true;
|
||||
deliverySelect.appendChild(option);
|
||||
|
||||
// Добавляем опции выбора на основе доступных типов доставки
|
||||
deliveryType.forEach(type => {
|
||||
const option = document.createElement('option');
|
||||
option.value = type;
|
||||
option.textContent = deliveryTypeLabels[type] || type;
|
||||
deliverySelect.appendChild(option);
|
||||
});
|
||||
|
||||
// Сохраняем выбранное значение
|
||||
let selectedDeliveryType = null;
|
||||
|
||||
// Обработчик изменения выбора
|
||||
deliverySelect.addEventListener('change', (e) => {
|
||||
selectedDeliveryType = e.target.value;
|
||||
updateSendButtonState();
|
||||
});
|
||||
|
||||
const selectArrow = document.createElement('span');
|
||||
selectArrow.style.cssText = `
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
pointer-events: none;
|
||||
color: ${canSend ? '#c0c4cc' : '#dcdfe6'};
|
||||
font-size: 12px;
|
||||
`;
|
||||
selectArrow.textContent = '▼';
|
||||
|
||||
selectWrapper.appendChild(deliverySelect);
|
||||
selectWrapper.appendChild(selectArrow);
|
||||
|
||||
deliveryBlock.appendChild(selectWrapper);
|
||||
|
||||
// Блок кнопок
|
||||
const buttonsBlock = document.createElement('div');
|
||||
buttonsBlock.className = 'dialog-footer';
|
||||
buttonsBlock.style.cssText = 'display: flex; align-items: center; gap: 5px;';
|
||||
|
||||
// Кнопка "Отмена" - стиль как у кнопки на главной странице
|
||||
const cancelBtn = document.createElement('button');
|
||||
@@ -594,10 +948,19 @@
|
||||
const sendBtn = document.createElement('button');
|
||||
sendBtn.type = 'button';
|
||||
|
||||
if (canSend) {
|
||||
sendBtn.className = 'el-button m-button el-button--small is-plain send-button button-with-icon';
|
||||
} else {
|
||||
sendBtn.className = 'el-button m-button el-button--small is-plain send-button button-with-icon is-disabled';
|
||||
// Функция обновления состояния кнопки отправки
|
||||
function updateSendButtonState() {
|
||||
const canSendWithDelivery = canSend && selectedDeliveryType !== null;
|
||||
|
||||
if (canSendWithDelivery) {
|
||||
sendBtn.className = 'el-button m-button el-button--small is-plain send-button button-with-icon';
|
||||
sendBtn.disabled = false;
|
||||
sendBtn.classList.remove('is-disabled');
|
||||
} else {
|
||||
sendBtn.className = 'el-button m-button el-button--small is-plain send-button button-with-icon is-disabled';
|
||||
sendBtn.disabled = true;
|
||||
sendBtn.classList.add('is-disabled');
|
||||
}
|
||||
}
|
||||
|
||||
const sendIcon = document.createElement('i');
|
||||
@@ -610,10 +973,14 @@
|
||||
sendBtn.appendChild(sendIcon);
|
||||
sendBtn.appendChild(sendText);
|
||||
|
||||
sendBtn.disabled = !canSend;
|
||||
// Устанавливаем начальное состояние кнопки
|
||||
updateSendButtonState();
|
||||
|
||||
footerRight.appendChild(cancelBtn);
|
||||
footerRight.appendChild(sendBtn);
|
||||
buttonsBlock.appendChild(cancelBtn);
|
||||
buttonsBlock.appendChild(sendBtn);
|
||||
|
||||
footerRight.appendChild(deliveryBlock);
|
||||
footerRight.appendChild(buttonsBlock);
|
||||
|
||||
dialogFooter.appendChild(footerLeft);
|
||||
dialogFooter.appendChild(footerRight);
|
||||
@@ -630,16 +997,18 @@
|
||||
|
||||
// Обработчик отправки
|
||||
async function handleSend() {
|
||||
if (!canSend) return;
|
||||
if (!canSend || selectedDeliveryType === null) return;
|
||||
|
||||
closeModal();
|
||||
|
||||
// Отправляем документы на подписание
|
||||
// Отправляем документы на подписание с выбранным типом доставки
|
||||
try {
|
||||
const docNumbers = documents.map(doc => parseInt(doc.number, 10));
|
||||
|
||||
const result = await sendMessageToContent('sendDocuments', {
|
||||
docNumbers: docNumbers
|
||||
docNumbers: docNumbers,
|
||||
deliveryType: selectedDeliveryType,
|
||||
recipients: recipients
|
||||
});
|
||||
|
||||
if (result && result.response) {
|
||||
@@ -664,9 +1033,7 @@
|
||||
cancelBtn.addEventListener('click', closeModal);
|
||||
backdrop.addEventListener('click', closeModal);
|
||||
|
||||
if (canSend) {
|
||||
sendBtn.addEventListener('click', handleSend);
|
||||
}
|
||||
sendBtn.addEventListener('click', handleSend);
|
||||
|
||||
// Закрытие по ESC
|
||||
const escHandler = (e) => {
|
||||
@@ -724,7 +1091,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Функция добавления кнопки
|
||||
function addSendBtn() {
|
||||
const sendBtn = document.createElement('button');
|
||||
|
||||
Reference in New Issue
Block a user