Загрузка данных
reports.html
{% extends 'base.html' %}
{% block head %}
<title>Список отчётов</title>
{% endblock %}
{% block main %}
<!--
Карточка со списком отчетов: содержит заголовок, навигационные кнопки и таблицу.
data-page-limit используется js виджетом для запросов нужного количества записей
-->
<section class="bg-white rounded-lg shadow p-6 space-y-4" data-page-limit="{{ page_limit or 20 }}">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<h2 class="text-2xl font-semibold text-gray-900">Доступные отчёты</h2>
<div class="flex items-center gap-3">
<!-- Пагинация -->
<button id="reports-prev"
class="px-4 py-2 text-sm font-medium bg-gray-200 text-gray-700 rounded disabled:opacity-50 disabled:cursor-not-allowed">
▲
</button>
<button id="reports-next"
class="px-4 py-2 text-sm font-medium bg-gray-200 text-gray-700 rounded disabled:opacity-50 disabled:cursor-not-allowed">
▼
</button>
</div>
</div>
<div class="relative overflow-x-auto overflow-y-visible border border-gray-200 rounded-lg"
data-reports-table-wrapper>
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider"></th>
<th class="relative px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider" data-filter-key="date">
<button type="button"
id="date-filter-trigger"
class="flex items-center gap-1 text-left text-xs font-semibold uppercase tracking-wider text-gray-600 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded">
<span>Начало запуска</span>
<span id="date-filter-counter"
class="hidden rounded-full bg-blue-100 px-1.5 text-[10px] font-semibold text-blue-700"></span>
<svg class="h-3 w-3 text-gray-500" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
</button>
<div id="date-filter-panel"
class="absolute left-0 top-full z-20 mt-2 w-64 rounded-lg border border-gray-200 bg-white p-3 shadow-lg hidden">
<div class="space-y-2">
<label class="flex items-center gap-2 text-xs text-gray-700">
<span class="whitespace-nowrap">С:</span>
<input type="date" id="date-from"
class="flex-1 px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
</label>
<label class="flex items-center gap-2 text-xs text-gray-700">
<span class="whitespace-nowrap">По:</span>
<input type="date" id="date-to"
class="flex-1 px-2 py-1.5 text-sm border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
</label>
<p id="date-error" class="text-xs text-red-600 hidden"></p>
</div>
<div class="mt-3 flex gap-2">
<button type="button"
id="date-filter-reset"
class="flex-1 rounded-md border border-gray-300 px-3 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500">
Сбросить
</button>
<button type="button"
id="date-filter-apply"
class="flex-1 rounded-md bg-blue-600 px-3 py-2 text-xs font-semibold text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
Применить
</button>
</div>
</div>
</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Завершение</th>
<th class="relative px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider" data-filter-key="stand">
<button type="button"
class="flex items-center gap-1 text-left text-xs font-semibold uppercase tracking-wider text-gray-600 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded"
data-filter-trigger>
<span>Окружение</span>
<span data-filter-counter
class="hidden rounded-full bg-blue-100 px-1.5 text-[10px] font-semibold text-blue-500"></span>
<svg class="h-3 w-3 text-gray-500" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
</button>
<div data-filter-panel
class="absolute left-0 top-full z-20 mt-2 w-60 rounded-lg border border-gray-200 bg-white p-3 shadow-lg hidden">
<p class="mb-2 text-xs font-medium text-gray-500">Доступные окружения</p>
<div data-filter-options class="space-y-2 overflow-y-auto max-h-60"></div>
<div class="mt-3 flex gap-2">
<button type="button"
class="flex-1 rounded-md border border-gray-300 px-3 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500"
data-filter-reset>Сбросить</button>
<button type="button"
class="flex-1 rounded-md bg-blue-600 px-3 py-2 text-xs font-semibold text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
data-filter-apply>Применить</button>
</div>
</div>
</th>
<th class="relative px-4 py-2 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider" data-filter-key="status">
<button type="button"
class="flex items-center gap-1 text-left text-xs font-semibold uppercase tracking-wider text-gray-600 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded"
data-filter-trigger>
<span>Статус</span>
<span data-filter-counter
class="hidden rounded-full bg-blue-100 px-1.5 text-[10px] font-semibold text-blue-700"></span>
<svg class="h-3 w-3 text-gray-500" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
</button>
<div data-filter-panel
class="absolute left-0 top-full z-20 mt-2 w-60 rounded-lg border border-gray-200 bg-white p-3 shadow-lg hidden">
<p class="mb-2 text-xs font-medium text-gray-500">Доступные статусы</p>
<div data-filter-options class="space-y-2 overflow-y-auto max-h-60"></div>
<div class="mt-3 flex gap-2">
<button type="button"
class="flex-1 rounded-md border border-gray-300 px-3 py-2 text-xs font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500"
data-filter-reset>Сбросить</button>
<button type="button"
class="flex-1 rounded-md bg-blue-700 px-3 py-2 text-xs font-semibold text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
data-filter-apply>Применить</button>
</div>
</div>
</th>
</tr>
</thead>
<tbody id="reports-body" class="divide-y divide-gray-100 bg-white">
<tr>
<td colspan="5" class="px-4 py-4 text-center text-sm text-gray-500">
Данные загружаются...
</td>
</tr>
</tbody>
</table>
</div>
<div id="reports-loading" class="text-sm text-gray-500 hidden">Загрузка...</div>
<div id="reports-message" class="text-sm text-gray-500 hidden" aria-live="polite"></div>
</section>
{% endblock %}
{% block scripts %}
{{ super() }}
<!-- Загрузка данных и управление таблицей -->
<script src="{{ url_for('static', filename='js/reports.js', v=reports_js_version) }}" defer></script>
<script defer>
document.addEventListener('DOMContentLoaded', function () {
const container = document.querySelector('section[data-page-limit]');
const limitAttr =
container && container.dataset
? Number(container.dataset.pageLimit)
: null;
const resolvedLimit = limitAttr || 20;
if (typeof window.ReportsPage !== 'function') {
const body = document.getElementById('reports-body');
if (body) {
body.innerHTML = `
<tr>
<td colspan="5" class="px-4 py-6 text-center text-sm text-red-600">
Не удалось инициализировать страницу отчётов. Обновите страницу.
</td>
</tr>`;
}
return;
}
window.reportsPage = new window.ReportsPage({
dataUrl: "{{ url_for('routes.get_reports_data') }}",
limit: resolvedLimit
});
});
</script>
{% endblock %}