// 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;