// search.js - адаптированный под Bootstrap (function () { // Состояние приложения let state = { filters: { period: 'today', dateFrom: null, dateTo: null, status: 'all', signatureType: 'all', senderName: '', patientName: '', documentNumber: '' }, results: [], pagination: { currentPage: 1, totalPages: 1, totalItems: 0, itemsPerPage: 10 }, loading: false, serverConfig: { ip: null, port: null } }; // 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'), customDateRange: document.getElementById('customDateRange'), dateFrom: document.getElementById('dateFrom'), dateTo: document.getElementById('dateTo'), statusSelect: document.getElementById('statusSelect'), signatureTypeSelect: document.getElementById('signatureTypeSelect'), senderInput: document.getElementById('senderInput'), patientInput: document.getElementById('patientInput'), documentNumberInput: document.getElementById('documentNumberInput'), advancedToggle: document.getElementById('advancedToggle'), advancedFilters: document.getElementById('advancedFilters'), advancedIcon: document.getElementById('advancedIcon'), searchBtn: document.getElementById('searchBtn'), clearFiltersBtn: document.getElementById('clearFiltersBtn'), tableBody: document.getElementById('tableBody'), emptyState: document.getElementById('emptyState'), resultsStats: document.getElementById('resultsStats'), pagination: document.getElementById('pagination'), paginationInfo: document.getElementById('paginationInfo'), prevPage: document.getElementById('prevPage'), nextPage: document.getElementById('nextPage') }; // Инициализация async function init() { // Инициализация Bootstrap модального окна const modalElement = document.getElementById('statusHistoryModal'); if (modalElement) { statusModal = new bootstrap.Modal(modalElement); } await loadServerConfig(); await loadFiltersFromStorage(); setupEventListeners(); setDefaultDates(); // Автоматически выполняем поиск при загрузке setTimeout(() => performSearch(), 100); } // Загрузка конфигурации сервера async function loadServerConfig() { return new Promise((resolve) => { chrome.storage.local.get(['serverIp', 'serverPort'], (result) => { if (result.serverIp && result.serverPort) { state.serverConfig.ip = result.serverIp; state.serverConfig.port = result.serverPort; } else { showAlert('Сервер не настроен. Перейдите в настройки расширения.', 'warning'); } resolve(); }); }); } // Загрузка фильтров из storage async function loadFiltersFromStorage() { return new Promise((resolve) => { chrome.storage.local.get(['advancedSearchFilters'], (result) => { if (result.advancedSearchFilters) { state.filters = { ...state.filters, ...result.advancedSearchFilters }; // Применяем фильтры к элементам управления elements.periodSelect.value = state.filters.period; elements.statusSelect.value = state.filters.status; elements.signatureTypeSelect.value = state.filters.signatureType; elements.senderInput.value = state.filters.senderName || ''; elements.patientInput.value = state.filters.patientName || ''; elements.documentNumberInput.value = state.filters.documentNumber || ''; if (state.filters.dateFrom) elements.dateFrom.value = state.filters.dateFrom; if (state.filters.dateTo) elements.dateTo.value = state.filters.dateTo; // Если период произвольный - показываем поля дат if (state.filters.period === 'custom') { elements.customDateRange.style.display = 'block'; } } resolve(); }); }); } // Сохранение фильтров в storage function saveFiltersToStorage() { chrome.storage.local.set({ advancedSearchFilters: state.filters }); } // Установка обработчиков событий function setupEventListeners() { // Период elements.periodSelect.addEventListener('change', handlePeriodChange); // Фильтры elements.statusSelect.addEventListener('change', updateFilters); elements.signatureTypeSelect.addEventListener('change', updateFilters); elements.senderInput.addEventListener('input', debounce(updateFilters, 500)); elements.patientInput.addEventListener('input', debounce(updateFilters, 500)); elements.documentNumberInput.addEventListener('input', debounce(updateFilters, 500)); // Произвольные даты elements.dateFrom.addEventListener('change', updateFilters); elements.dateTo.addEventListener('change', updateFilters); // Кнопки elements.searchBtn.addEventListener('click', performSearch); elements.clearFiltersBtn.addEventListener('click', clearFilters); // Расширенные фильтры elements.advancedToggle.addEventListener('click', toggleAdvancedFilters); // Пагинация elements.prevPage.addEventListener('click', () => changePage(-1)); elements.nextPage.addEventListener('click', () => changePage(1)); } // Установка дат по умолчанию function setDefaultDates() { if (!state.filters.dateFrom || !state.filters.dateTo) { const today = new Date(); const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1); elements.dateTo.value = formatDate(today); elements.dateFrom.value = formatDate(yesterday); state.filters.dateFrom = elements.dateFrom.value; state.filters.dateTo = elements.dateTo.value; } } // Форматирование даты для input function formatDate(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } // Обработчик изменения периода function handlePeriodChange() { const period = elements.periodSelect.value; if (period === 'custom') { elements.customDateRange.style.display = 'block'; elements.advancedFilters.style.display = 'block'; elements.advancedIcon.className = 'bi bi-chevron-up me-1'; } else { elements.customDateRange.style.display = 'none'; updateDateRangeFromPeriod(period); } updateFilters(); } // Обновление диапазона дат на основе периода function updateDateRangeFromPeriod(period) { const today = new Date(); let from = new Date(today); switch (period) { case 'today': break; case 'yesterday': from.setDate(from.getDate() - 1); today.setDate(today.getDate() - 1); break; case 'week': from.setDate(from.getDate() - 7); break; case 'month': from.setMonth(from.getMonth() - 1); break; case 'quarter': from.setMonth(from.getMonth() - 3); break; } elements.dateFrom.value = formatDate(from); elements.dateTo.value = formatDate(today); state.filters.dateFrom = elements.dateFrom.value; state.filters.dateTo = elements.dateTo.value; } // Обновление состояния фильтров function updateFilters() { state.filters = { period: elements.periodSelect.value, dateFrom: elements.dateFrom.value, dateTo: elements.dateTo.value, status: elements.statusSelect.value, signatureType: elements.signatureTypeSelect.value, senderName: elements.senderInput.value.trim(), patientName: elements.patientInput.value.trim(), documentNumber: elements.documentNumberInput.value.trim() }; saveFiltersToStorage(); } // Переключение расширенных фильтров function toggleAdvancedFilters() { const isVisible = elements.advancedFilters.style.display !== 'none'; if (isVisible) { elements.advancedFilters.style.display = 'none'; elements.advancedIcon.className = 'bi bi-chevron-down me-1'; if (state.filters.period !== 'custom') { elements.customDateRange.style.display = 'none'; } } else { elements.advancedFilters.style.display = 'block'; elements.advancedIcon.className = 'bi bi-chevron-up me-1'; if (state.filters.period === 'custom') { elements.customDateRange.style.display = 'block'; } } } // Сброс фильтров function clearFilters() { elements.periodSelect.value = 'today'; elements.customDateRange.style.display = 'none'; elements.statusSelect.value = 'all'; elements.signatureTypeSelect.value = 'all'; elements.senderInput.value = ''; elements.patientInput.value = ''; elements.documentNumberInput.value = ''; setDefaultDates(); updateFilters(); elements.advancedFilters.style.display = 'none'; elements.advancedIcon.className = 'bi bi-chevron-down me-1'; // Выполняем поиск после сброса performSearch(); } // Выполнение поиска async function performSearch() { if (!validateServerConfig()) { return; } setLoading(true); try { updateFilters(); const response = await sendMessageToBackground('advancedSearch', { filters: state.filters }); if (response.success) { // Обогащаем статусы state.results = enrichStatuses(response.data || []); state.pagination.totalItems = state.results.length; state.pagination.totalPages = Math.ceil(state.pagination.totalItems / state.pagination.itemsPerPage); state.pagination.currentPage = 1; renderResults(); } else { showAlert(response.message || 'Ошибка при поиске', 'danger'); } } catch (error) { console.error('Search error:', error); showAlert('Ошибка при выполнении поиска', 'danger'); } finally { setLoading(false); } } // Обогащение статусов 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'; } function getSignatureTypeText(item) { return getSignatureType(item) === 'esia' ? 'УКЭП для ЕСИА' : 'МЧД для других'; } function getSignatureTypeClass(item) { return getSignatureType(item) === 'esia' ? 'bg-primary-subtle text-primary' : 'bg-secondary-subtle text-secondary'; } // Отправка сообщения в background function sendMessageToBackground(action, data) { return new Promise((resolve) => { chrome.runtime.sendMessage({ action, data }, (response) => { resolve(response || { success: false, message: 'Нет ответа от background' }); }); }); } // Валидация конфигурации сервера function validateServerConfig() { if (!state.serverConfig.ip || !state.serverConfig.port) { showAlert('Сервер не настроен. Перейдите в настройки расширения.', 'warning'); return false; } return true; } // Установка состояния загрузки function setLoading(isLoading) { state.loading = isLoading; if (isLoading) { elements.searchBtn.disabled = true; elements.tableBody.innerHTML = `
Нет истории статусов
'; if (statusModal) { statusModal.show(); } } // Отображение уведомления (Bootstrap Toast) function showAlert(message, type = 'info') { // Создаем контейнер для тостов, если его нет let toastContainer = document.querySelector('.toast-container'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3'; toastContainer.style.zIndex = '1100'; document.body.appendChild(toastContainer); } // Создаем тост const toastId = 'toast-' + Date.now(); const toastHtml = `