Files
minicrm/public/reports.js
2026-03-19 10:20:55 +05:00

206 lines
7.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 = '<tr><td colspan="8" class="loading">Загрузка данных...</td></tr>';
// Загружаем все задачи (или через специальный эндпоинт)
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 =
'<tr><td colspan="8" class="error">Ошибка загрузки данных</td></tr>';
}
}
// Заполнение выпадающего списка пользователей
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 = '<option value="">Все пользователи</option>';
// Сортируем по имени
const sorted = Array.from(usersMap.entries()).sort((a, b) => a[1].localeCompare(b[1]));
for (let [id, name] of sorted) {
options += `<option value="${id}">${escapeHtml(name)}</option>`;
}
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 = '<tr><td colspan="8" class="no-data">Нет данных для отображения</td></tr>';
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 += `
<div class="summary-item">
<span class="status-badge">${count}</span>
<span class="status-label">${statusLabels[status] || status}</span>
</div>
`;
}
summaryDiv.innerHTML = summaryHtml;
// Таблица детальных записей
tbody.innerHTML = data.map(item => `
<tr>
<td>${item.task_id}</td>
<td>${escapeHtml(item.task_title)}</td>
<td class="task-description" title="${escapeHtml(item.task_description)}">${escapeHtml(truncateText(item.task_description, 50))}</td>
<td>${formatDateTime(item.due_date) || '—'}</td>
<td>${escapeHtml(item.user_name)}</td>
<td>${escapeHtml(item.creator_name)}</td>
<td><span class="status-badge status-${item.status}">${statusLabels[item.status] || item.status}</span></td>
<td>${formatDateTime(item.status_updated_at) || '—'}</td>
</tr>
`).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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
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;