// reports.js – Отчёт по задачам с фильтрацией и группировкой let reportData = []; // все назначения для отчёта let currentReportFiltered = []; // отфильтрованные данные // Конфигурация статусов и типов для фильтров const STATUS_OPTIONS = [ { value: '', label: 'Все статусы' }, { value: 'assigned', label: 'Назначена' }, { value: 'in_progress', label: 'В работе' }, { value: 'completed', label: 'Выполнена' }, { value: 'overdue', label: 'Просрочена' }, { value: 'rework', label: 'На доработке' }, { value: 'deleted', label: 'Удалена' } ]; const TASK_TYPE_OPTIONS = [ { value: '', label: 'Все типы' }, { value: 'regular', label: 'Обычная задача' }, { value: 'document', label: 'Согласование документа' }, { value: 'it', label: 'ИТ отдел' }, { value: 'ahch', label: 'АХЧ' }, { value: 'psychologist', label: 'Психолог' }, { value: 'speech_therapist', label: 'Логопед' }, { value: 'hr', label: 'Диспетчер расписания' }, { value: 'certificate', label: 'Справка' }, { value: 'e_journal', label: 'Эл. журнал' } ]; // Функция показа секции отчёта function showReportsSection() { showSection('reports'); // Если данные ещё не загружены – загружаем if (reportData.length === 0) { loadReportData(); } else { applyFilters(); // применяем текущие фильтры } } // Загрузка данных для отчёта (используем существующее API) async function loadReportData() { try { const tbody = document.getElementById('report-table-body'); tbody.innerHTML = 'Загрузка данных...'; // Загружаем все задачи (или через специальный эндпоинт) const response = await fetch('/api/tasks?status=all&limit=1000'); if (!response.ok) throw new Error('Ошибка загрузки задач'); const tasks = await response.json(); // Преобразуем задачи в плоский список назначений reportData = []; tasks.forEach(task => { if (task.assignments && task.assignments.length) { task.assignments.forEach(ass => { reportData.push({ task_id: task.id, task_title: task.title, task_description: task.description || '', task_type: task.task_type || 'regular', due_date: task.due_date, user_id: ass.user_id, user_name: ass.user_name, status: ass.status, status_updated_at: ass.updated_at || task.updated_at, creator_name: task.creator_name }); }); } }); // Заполняем фильтр пользователей populateUserFilter(reportData); // Применяем фильтры и отображаем applyFilters(); } catch (error) { console.error('Ошибка загрузки отчёта:', error); document.getElementById('report-table-body').innerHTML = 'Ошибка загрузки данных'; } } // Заполнение выпадающего списка пользователей function populateUserFilter(data) { const select = document.getElementById('report-user-filter'); const usersMap = new Map(); data.forEach(item => { usersMap.set(item.user_id, item.user_name); }); let options = ''; // Сортируем по имени const sorted = Array.from(usersMap.entries()).sort((a, b) => a[1].localeCompare(b[1])); for (let [id, name] of sorted) { options += ``; } select.innerHTML = options; } // Применение всех фильтров и рендеринг function applyFilters() { const userId = document.getElementById('report-user-filter').value; const statusFilter = document.getElementById('report-status-filter').value; const typeFilter = document.getElementById('report-type-filter').value; currentReportFiltered = reportData.filter(item => { if (userId && item.user_id != userId) return false; if (statusFilter && item.status !== statusFilter) return false; if (typeFilter && item.task_type !== typeFilter) return false; return true; }); renderReport(currentReportFiltered); } // Рендеринг таблицы и сводки function renderReport(data) { const tbody = document.getElementById('report-table-body'); const summaryDiv = document.getElementById('report-summary'); if (!data.length) { tbody.innerHTML = 'Нет данных для отображения'; summaryDiv.innerHTML = ''; return; } // Сводка по статусам const statusCounts = {}; data.forEach(item => { statusCounts[item.status] = (statusCounts[item.status] || 0) + 1; }); const statusLabels = { 'assigned': 'Назначена', 'in_progress': 'В работе', 'completed': 'Выполнена', 'overdue': 'Просрочена', 'rework': 'На доработке', 'deleted': 'Удалена' }; let summaryHtml = ''; for (let [status, count] of Object.entries(statusCounts)) { summaryHtml += `
${count} ${statusLabels[status] || status}
`; } summaryDiv.innerHTML = summaryHtml; // Таблица детальных записей tbody.innerHTML = data.map(item => ` ${item.task_id} ${escapeHtml(item.task_title)} ${escapeHtml(truncateText(item.task_description, 50))} ${formatDateTime(item.due_date) || '—'} ${escapeHtml(item.user_name)} ${escapeHtml(item.creator_name)} ${statusLabels[item.status] || item.status} ${formatDateTime(item.status_updated_at) || '—'} `).join(''); } // Вспомогательные функции function truncateText(text, maxLen) { if (!text) return ''; return text.length > maxLen ? text.substr(0, maxLen) + '…' : text; } function escapeHtml(unsafe) { if (!unsafe) return ''; return String(unsafe) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function formatDateTime(dateStr) { if (!dateStr) return ''; const d = new Date(dateStr); return d.toLocaleString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); } // Печать отчёта function printReport() { window.print(); } // Экспортируем функции window.showReportsSection = showReportsSection; window.loadReportData = loadReportData; window.applyFilters = applyFilters; window.printReport = printReport;