отчет своё
This commit is contained in:
@@ -335,15 +335,16 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Секция отчётов (обновлена) -->
|
||||||
<section id="reports-section" class="section">
|
<section id="reports-section" class="section">
|
||||||
<h2><i class="fas fa-chart-pie"></i> Отчёт по задачам</h2>
|
<h2><i class="fas fa-chart-pie"></i> Отчёт по задачам</h2>
|
||||||
|
|
||||||
<div class="reports-filters">
|
<div class="reports-filters">
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<label for="report-user-filter">Исполнитель:</label>
|
<label for="report-task-id-filter">Номер задачи:</label>
|
||||||
<select id="report-user-filter" onchange="applyFilters()">
|
<select id="report-task-id-filter" onchange="applyFilters()">
|
||||||
<option value="">Все пользователи</option>
|
<option value="">Все номера</option>
|
||||||
<!-- будут загружены динамически -->
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
@@ -358,6 +359,12 @@
|
|||||||
<option value="deleted">Удалена</option>
|
<option value="deleted">Удалена</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="report-user-filter">Исполнитель:</label>
|
||||||
|
<select id="report-user-filter" onchange="applyFilters()">
|
||||||
|
<option value="">Все пользователи</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<label for="report-type-filter">Тип задачи:</label>
|
<label for="report-type-filter">Тип задачи:</label>
|
||||||
<select id="report-type-filter" onchange="applyFilters()">
|
<select id="report-type-filter" onchange="applyFilters()">
|
||||||
@@ -373,12 +380,15 @@
|
|||||||
<option value="e_journal">Эл. журнал</option>
|
<option value="e_journal">Эл. журнал</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-group">
|
<div class="filter-group buttons-group">
|
||||||
<button class="btn-primary" onclick="printReport()">
|
<button class="btn-primary" onclick="printReport()" title="Печать">
|
||||||
<i class="fas fa-print"></i> Печать
|
<i class="fas fa-print"></i> Печать
|
||||||
</button>
|
</button> <!--
|
||||||
<button class="btn-secondary" onclick="loadReportData()">
|
<button class="btn-secondary" onclick="loadReportData()" title="Обновить данные">
|
||||||
<i class="fas fa-sync-alt"></i> Обновить
|
<i class="fas fa-sync-alt"></i> Обновить
|
||||||
|
</button> -->
|
||||||
|
<button class="btn-primary" onclick="resetReportFilters()" title="Сбросить все фильтры">
|
||||||
|
<i class="fas fa-undo-alt"></i> Сбросить
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -386,14 +396,13 @@
|
|||||||
<!-- Сводка по статусам -->
|
<!-- Сводка по статусам -->
|
||||||
<div id="report-summary" class="report-summary"></div>
|
<div id="report-summary" class="report-summary"></div>
|
||||||
|
|
||||||
<!-- Таблица с задачами -->
|
<!-- Таблица с задачами (без описания) -->
|
||||||
<div class="table-container report-table-container">
|
<div class="table-container report-table-container">
|
||||||
<table id="report-table" class="report-table">
|
<table id="report-table" class="report-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>№ задачи</th>
|
<th>№ задачи</th>
|
||||||
<th>Название</th>
|
<th>Название</th>
|
||||||
<th>Описание</th>
|
|
||||||
<th>Срок выполнения</th>
|
<th>Срок выполнения</th>
|
||||||
<th>Исполнитель</th>
|
<th>Исполнитель</th>
|
||||||
<th>Автор</th>
|
<th>Автор</th>
|
||||||
@@ -402,7 +411,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="report-table-body">
|
<tbody id="report-table-body">
|
||||||
<tr><td colspan="8" class="loading">Загрузка данных...</td></tr>
|
<tr><td colspan="7" class="loading">Загрузка данных...</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -410,7 +419,7 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Модальное окно редактирования задачи -->
|
<!-- Модальные окна (без изменений) -->
|
||||||
<div id="edit-task-modal" class="modal">
|
<div id="edit-task-modal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<span class="close" onclick="closeEditModal()">×</span>
|
<span class="close" onclick="closeEditModal()">×</span>
|
||||||
@@ -421,16 +430,13 @@
|
|||||||
<label for="edit-title">Название задачи:</label>
|
<label for="edit-title">Название задачи:</label>
|
||||||
<input type="text" id="edit-title" name="title" required>
|
<input type="text" id="edit-title" name="title" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="edit-description">Описание:</label>
|
<label for="edit-description">Описание:</label>
|
||||||
<textarea id="edit-description" name="description" rows="4"></textarea>
|
<textarea id="edit-description" name="description" rows="4"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="edit-due-date">Дата выполнения:</label>
|
<label for="edit-due-date">Дата выполнения:</label>
|
||||||
<input type="date" id="edit-due-date" name="dueDate" required>
|
<input type="date" id="edit-due-date" name="dueDate" required>
|
||||||
|
|
||||||
<div class="time-buttons">
|
<div class="time-buttons">
|
||||||
<button type="button" class="edit-time-btn" onclick="setEditTaskTime('12:00')">
|
<button type="button" class="edit-time-btn" onclick="setEditTaskTime('12:00')">
|
||||||
<i class="fas fa-sun"></i> До обеда (12:00)
|
<i class="fas fa-sun"></i> До обеда (12:00)
|
||||||
@@ -441,22 +447,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<input type="hidden" id="edit-due-time" name="dueTime" value="12:00">
|
<input type="hidden" id="edit-due-time" name="dueTime" value="12:00">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Исполнители:</label>
|
<label>Исполнители:</label>
|
||||||
<div class="user-search">
|
<div class="user-search">
|
||||||
<div id="edit-users-checklist" class="checkbox-group"></div>
|
<div id="edit-users-checklist" class="checkbox-group"></div>
|
||||||
<input type="text" id="edit-user-search" placeholder="Поиск исполнителей..." oninput="filterEditUsers()">
|
<input type="text" id="edit-user-search" placeholder="Поиск исполнителей..." oninput="filterEditUsers()">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="edit-files">Добавить файлы:</label>
|
<label for="edit-files">Добавить файлы:</label>
|
||||||
<input type="file" id="edit-files" name="files" multiple>
|
<input type="file" id="edit-files" name="files" multiple>
|
||||||
<div id="edit-file-list"></div>
|
<div id="edit-file-list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn-primary">
|
<button type="submit" class="btn-primary">
|
||||||
<i class="fas fa-save"></i> Сохранить изменения
|
<i class="fas fa-save"></i> Сохранить изменения
|
||||||
</button>
|
</button>
|
||||||
@@ -464,21 +466,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Модальное окно копирования задачи -->
|
|
||||||
<div id="copy-task-modal" class="modal">
|
<div id="copy-task-modal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<span class="close" onclick="closeCopyModal()">×</span>
|
<span class="close" onclick="closeCopyModal()">×</span>
|
||||||
<h3><i class="fas fa-copy"></i> Создать копию задачи</h3>
|
<h3><i class="fas fa-copy"></i> Создать копию задачи</h3>
|
||||||
<form id="copy-task-form">
|
<form id="copy-task-form">
|
||||||
<input type="hidden" id="copy-task-id">
|
<input type="hidden" id="copy-task-id">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="copy-due-date">Дата выполнения:</label>
|
<label for="copy-due-date">Дата выполнения:</label>
|
||||||
<input type="date" class="date-btn" id="copy-due-date" name="dueDate" required>
|
<input type="date" class="date-btn" id="copy-due-date" name="dueDate" required>
|
||||||
<input type="hidden" id="copy-due-time" name="dueTime" value="19:00">
|
<input type="hidden" id="copy-due-time" name="dueTime" value="19:00">
|
||||||
<input type="text" id="copy-user-search" placeholder="Поиск исполнителей..." oninput="filterCopyUsers()">
|
<input type="text" id="copy-user-search" placeholder="Поиск исполнителей..." oninput="filterCopyUsers()">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div id="copy-users-checklist" class="checkbox-group"></div>
|
<div id="copy-users-checklist" class="checkbox-group"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -489,7 +488,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Остальные модальные окна остаются без изменений -->
|
|
||||||
<div id="edit-assignment-modal" class="modal">
|
<div id="edit-assignment-modal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<span class="close" onclick="closeEditAssignmentModal()">×</span>
|
<span class="close" onclick="closeEditAssignmentModal()">×</span>
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ let currentReportFiltered = []; // отфильтрованные данные
|
|||||||
const STATUS_OPTIONS = [
|
const STATUS_OPTIONS = [
|
||||||
{ value: '', label: 'Все статусы' },
|
{ value: '', label: 'Все статусы' },
|
||||||
{ value: 'assigned', label: 'Назначена' },
|
{ value: 'assigned', label: 'Назначена' },
|
||||||
|
{ value: 'assigned_overdue', label: 'Назначена (просрочена)' },
|
||||||
{ value: 'in_progress', label: 'В работе' },
|
{ value: 'in_progress', label: 'В работе' },
|
||||||
|
{ value: 'in_progress_overdue', label: 'В работе (просрочена)' },
|
||||||
{ value: 'completed', label: 'Выполнена' },
|
{ value: 'completed', label: 'Выполнена' },
|
||||||
{ value: 'overdue', label: 'Просрочена' },
|
{ value: 'completed_after_due', label: 'Выполнена после срока' },
|
||||||
|
{ value: 'overdue', label: 'Просрочена (системная)' },
|
||||||
{ value: 'rework', label: 'На доработке' },
|
{ value: 'rework', label: 'На доработке' },
|
||||||
{ value: 'deleted', label: 'Удалена' }
|
{ value: 'deleted', label: 'Удалена' }
|
||||||
];
|
];
|
||||||
@@ -29,33 +32,75 @@ const TASK_TYPE_OPTIONS = [
|
|||||||
|
|
||||||
// Функция показа секции отчёта
|
// Функция показа секции отчёта
|
||||||
function showReportsSection() {
|
function showReportsSection() {
|
||||||
|
// Используем глобальный currentUser из auth.js
|
||||||
|
if (typeof currentUser === 'undefined' || !currentUser) {
|
||||||
|
console.error('currentUser не определён');
|
||||||
|
return;
|
||||||
|
}
|
||||||
showSection('reports');
|
showSection('reports');
|
||||||
// Если данные ещё не загружены – загружаем
|
|
||||||
if (reportData.length === 0) {
|
if (reportData.length === 0) {
|
||||||
loadReportData();
|
loadReportData();
|
||||||
} else {
|
} else {
|
||||||
applyFilters(); // применяем текущие фильтры
|
applyFilters();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Загрузка данных для отчёта (используем существующее API)
|
// Проверка, имеет ли пользователь право видеть все задачи
|
||||||
|
function canViewAllTasks() {
|
||||||
|
if (!currentUser) return false;
|
||||||
|
// Администратор
|
||||||
|
if (currentUser.role === 'admin') return true;
|
||||||
|
// Проверка группы "Руководители" (предполагаем, что группы хранятся в currentUser.groups)
|
||||||
|
if (currentUser.groups && Array.isArray(currentUser.groups)) {
|
||||||
|
if (currentUser.groups.some(g => g === 'Руководители' || g.includes('Руководители'))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вычисление уточнённого статуса с учётом просрочки
|
||||||
|
function computeDisplayStatus(item) {
|
||||||
|
const now = new Date();
|
||||||
|
const due = item.due_date ? new Date(item.due_date) : null;
|
||||||
|
const completedAt = item.status === 'completed' && item.status_updated_at ? new Date(item.status_updated_at) : null;
|
||||||
|
|
||||||
|
if (item.status === 'assigned') {
|
||||||
|
if (due && due < now) return 'assigned_overdue';
|
||||||
|
return 'assigned';
|
||||||
|
}
|
||||||
|
if (item.status === 'in_progress') {
|
||||||
|
if (due && due < now) return 'in_progress_overdue';
|
||||||
|
return 'in_progress';
|
||||||
|
}
|
||||||
|
if (item.status === 'completed') {
|
||||||
|
if (due && completedAt && completedAt > due) return 'completed_after_due';
|
||||||
|
return 'completed';
|
||||||
|
}
|
||||||
|
return item.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загрузка данных для отчёта
|
||||||
async function loadReportData() {
|
async function loadReportData() {
|
||||||
try {
|
try {
|
||||||
const tbody = document.getElementById('report-table-body');
|
const tbody = document.getElementById('report-table-body');
|
||||||
tbody.innerHTML = '<tr><td colspan="8" class="loading">Загрузка данных...</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="7" class="loading">Загрузка данных...</td></tr>';
|
||||||
|
|
||||||
// Загружаем все задачи (или через специальный эндпоинт)
|
|
||||||
const response = await fetch('/api/tasks?status=all&limit=1000');
|
const response = await fetch('/api/tasks?status=all&limit=1000');
|
||||||
if (!response.ok) throw new Error('Ошибка загрузки задач');
|
if (!response.ok) throw new Error('Ошибка загрузки задач');
|
||||||
|
|
||||||
const tasks = await response.json();
|
const tasks = await response.json();
|
||||||
|
|
||||||
// Преобразуем задачи в плоский список назначений
|
|
||||||
reportData = [];
|
reportData = [];
|
||||||
|
const canViewAll = canViewAllTasks();
|
||||||
tasks.forEach(task => {
|
tasks.forEach(task => {
|
||||||
|
// Если не админ и не руководитель, показываем только задачи, где пользователь - автор
|
||||||
|
if (!canViewAll && task.created_by !== currentUser.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (task.assignments && task.assignments.length) {
|
if (task.assignments && task.assignments.length) {
|
||||||
task.assignments.forEach(ass => {
|
task.assignments.forEach(ass => {
|
||||||
reportData.push({
|
const item = {
|
||||||
task_id: task.id,
|
task_id: task.id,
|
||||||
task_title: task.title,
|
task_title: task.title,
|
||||||
task_description: task.description || '',
|
task_description: task.description || '',
|
||||||
@@ -65,22 +110,23 @@ async function loadReportData() {
|
|||||||
user_name: ass.user_name,
|
user_name: ass.user_name,
|
||||||
status: ass.status,
|
status: ass.status,
|
||||||
status_updated_at: ass.updated_at || task.updated_at,
|
status_updated_at: ass.updated_at || task.updated_at,
|
||||||
creator_name: task.creator_name
|
creator_name: task.creator_name,
|
||||||
});
|
created_by: task.created_by
|
||||||
|
};
|
||||||
|
item.displayStatus = computeDisplayStatus(item);
|
||||||
|
reportData.push(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Заполняем фильтр пользователей
|
|
||||||
populateUserFilter(reportData);
|
populateUserFilter(reportData);
|
||||||
|
populateTaskIdFilter(reportData);
|
||||||
// Применяем фильтры и отображаем
|
|
||||||
applyFilters();
|
applyFilters();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка загрузки отчёта:', error);
|
console.error('Ошибка загрузки отчёта:', error);
|
||||||
document.getElementById('report-table-body').innerHTML =
|
document.getElementById('report-table-body').innerHTML =
|
||||||
'<tr><td colspan="8" class="error">Ошибка загрузки данных</td></tr>';
|
'<tr><td colspan="7" class="error">Ошибка загрузки данных</td></tr>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,13 +135,11 @@ function populateUserFilter(data) {
|
|||||||
const select = document.getElementById('report-user-filter');
|
const select = document.getElementById('report-user-filter');
|
||||||
const usersMap = new Map();
|
const usersMap = new Map();
|
||||||
data.forEach(item => {
|
data.forEach(item => {
|
||||||
// Убедимся, что имя не null
|
|
||||||
const name = item.user_name || 'Без имени';
|
const name = item.user_name || 'Без имени';
|
||||||
usersMap.set(item.user_id, name);
|
usersMap.set(item.user_id, name);
|
||||||
});
|
});
|
||||||
|
|
||||||
let options = '<option value="">Все пользователи</option>';
|
let options = '<option value="">Все пользователи</option>';
|
||||||
// Сортируем по имени, защищая от null/undefined
|
|
||||||
const sorted = Array.from(usersMap.entries()).sort((a, b) => {
|
const sorted = Array.from(usersMap.entries()).sort((a, b) => {
|
||||||
const nameA = a[1] || '';
|
const nameA = a[1] || '';
|
||||||
const nameB = b[1] || '';
|
const nameB = b[1] || '';
|
||||||
@@ -107,44 +151,72 @@ function populateUserFilter(data) {
|
|||||||
select.innerHTML = options;
|
select.innerHTML = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Заполнение выпадающего списка номеров задач (по убыванию)
|
||||||
|
function populateTaskIdFilter(data) {
|
||||||
|
const select = document.getElementById('report-task-id-filter');
|
||||||
|
const taskIds = [...new Set(data.map(item => item.task_id))];
|
||||||
|
taskIds.sort((a, b) => b - a);
|
||||||
|
|
||||||
|
let options = '<option value="">Все номера</option>';
|
||||||
|
taskIds.forEach(id => {
|
||||||
|
options += `<option value="${id}">${id}</option>`;
|
||||||
|
});
|
||||||
|
select.innerHTML = options;
|
||||||
|
}
|
||||||
|
|
||||||
// Применение всех фильтров и рендеринг
|
// Применение всех фильтров и рендеринг
|
||||||
function applyFilters() {
|
function applyFilters() {
|
||||||
const userId = document.getElementById('report-user-filter').value;
|
const userId = document.getElementById('report-user-filter').value;
|
||||||
const statusFilter = document.getElementById('report-status-filter').value;
|
const statusFilter = document.getElementById('report-status-filter').value;
|
||||||
const typeFilter = document.getElementById('report-type-filter').value;
|
const typeFilter = document.getElementById('report-type-filter').value;
|
||||||
|
const taskIdFilter = document.getElementById('report-task-id-filter').value;
|
||||||
|
|
||||||
currentReportFiltered = reportData.filter(item => {
|
currentReportFiltered = reportData.filter(item => {
|
||||||
if (userId && item.user_id != userId) return false;
|
if (userId && item.user_id != userId) return false;
|
||||||
if (statusFilter && item.status !== statusFilter) return false;
|
|
||||||
if (typeFilter && item.task_type !== typeFilter) return false;
|
if (typeFilter && item.task_type !== typeFilter) return false;
|
||||||
|
if (taskIdFilter && item.task_id != taskIdFilter) return false;
|
||||||
|
if (statusFilter) {
|
||||||
|
if (item.displayStatus !== statusFilter) return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
renderReport(currentReportFiltered);
|
renderReport(currentReportFiltered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Сброс всех фильтров
|
||||||
|
function resetReportFilters() {
|
||||||
|
document.getElementById('report-user-filter').value = '';
|
||||||
|
document.getElementById('report-status-filter').value = '';
|
||||||
|
document.getElementById('report-type-filter').value = '';
|
||||||
|
document.getElementById('report-task-id-filter').value = '';
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
|
||||||
// Рендеринг таблицы и сводки
|
// Рендеринг таблицы и сводки
|
||||||
function renderReport(data) {
|
function renderReport(data) {
|
||||||
const tbody = document.getElementById('report-table-body');
|
const tbody = document.getElementById('report-table-body');
|
||||||
const summaryDiv = document.getElementById('report-summary');
|
const summaryDiv = document.getElementById('report-summary');
|
||||||
|
|
||||||
if (!data.length) {
|
if (!data.length) {
|
||||||
tbody.innerHTML = '<tr><td colspan="8" class="no-data">Нет данных для отображения</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="7" class="no-data">Нет данных для отображения</td></tr>';
|
||||||
summaryDiv.innerHTML = '';
|
summaryDiv.innerHTML = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сводка по статусам
|
|
||||||
const statusCounts = {};
|
const statusCounts = {};
|
||||||
data.forEach(item => {
|
data.forEach(item => {
|
||||||
statusCounts[item.status] = (statusCounts[item.status] || 0) + 1;
|
statusCounts[item.displayStatus] = (statusCounts[item.displayStatus] || 0) + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
const statusLabels = {
|
const statusLabels = {
|
||||||
'assigned': 'Назначена',
|
'assigned': 'Назначена',
|
||||||
|
'assigned_overdue': 'Назначена (просрочена)',
|
||||||
'in_progress': 'В работе',
|
'in_progress': 'В работе',
|
||||||
|
'in_progress_overdue': 'В работе (просрочена)',
|
||||||
'completed': 'Выполнена',
|
'completed': 'Выполнена',
|
||||||
'overdue': 'Просрочена',
|
'completed_after_due': 'Выполнена после срока',
|
||||||
|
'overdue': 'Просрочена (системная)',
|
||||||
'rework': 'На доработке',
|
'rework': 'На доработке',
|
||||||
'deleted': 'Удалена'
|
'deleted': 'Удалена'
|
||||||
};
|
};
|
||||||
@@ -160,16 +232,14 @@ function renderReport(data) {
|
|||||||
}
|
}
|
||||||
summaryDiv.innerHTML = summaryHtml;
|
summaryDiv.innerHTML = summaryHtml;
|
||||||
|
|
||||||
// Таблица детальных записей
|
|
||||||
tbody.innerHTML = data.map(item => `
|
tbody.innerHTML = data.map(item => `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${item.task_id}</td>
|
<td>${item.task_id}</td>
|
||||||
<td>${escapeHtml(item.task_title)}</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>${formatDateTime(item.due_date) || '—'}</td>
|
||||||
<td>${escapeHtml(item.user_name || 'Неизвестно')}</td>
|
<td>${escapeHtml(item.user_name || 'Неизвестно')}</td>
|
||||||
<td>${escapeHtml(item.creator_name || 'Неизвестно')}</td>
|
<td>${escapeHtml(item.creator_name || 'Неизвестно')}</td>
|
||||||
<td><span class="status-badge status-${item.status}">${statusLabels[item.status] || item.status}</span></td>
|
<td><span class="status-badge status-${item.displayStatus}">${statusLabels[item.displayStatus] || item.displayStatus}</span></td>
|
||||||
<td>${formatDateTime(item.status_updated_at) || '—'}</td>
|
<td>${formatDateTime(item.status_updated_at) || '—'}</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
@@ -200,13 +270,13 @@ function formatDateTime(dateStr) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Печать отчёта
|
|
||||||
function printReport() {
|
function printReport() {
|
||||||
window.print();
|
window.print();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Экспортируем функции
|
// Экспорт
|
||||||
window.showReportsSection = showReportsSection;
|
window.showReportsSection = showReportsSection;
|
||||||
window.loadReportData = loadReportData;
|
window.loadReportData = loadReportData;
|
||||||
window.applyFilters = applyFilters;
|
window.applyFilters = applyFilters;
|
||||||
|
window.resetReportFilters = resetReportFilters;
|
||||||
window.printReport = printReport;
|
window.printReport = printReport;
|
||||||
@@ -5171,3 +5171,34 @@ button.btn-primary {
|
|||||||
.status-overdue { background: #dc3545; color: white; }
|
.status-overdue { background: #dc3545; color: white; }
|
||||||
.status-rework { background: #fd7e14; color: white; }
|
.status-rework { background: #fd7e14; color: white; }
|
||||||
.status-deleted { background: #6c757d; color: white; }
|
.status-deleted { background: #6c757d; color: white; }
|
||||||
|
.reports-filters {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.filter-group {
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
.filter-group.buttons-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.btn-reset {
|
||||||
|
background: #6c757d;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.btn-reset:hover {
|
||||||
|
background: #5a6268;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user