Загрузка данных


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 %}