{# Table helpers -- ported from bty's ``_table_macros.html`` shape so an operator moving between the two consoles reads the same toolbar layout. Callers pass a :class:`SortState`, a :class:`PageState`, and a ``preserved`` dict of query params to carry across every link (filter survives sort clicks; sort survives pagination clicks; per_page survives everything). The macros are shape-agnostic: they don't care whether the underlying rows come from an in-memory list or a SQL LIMIT/OFFSET query. Layout intent: * The filter form + the pagination live TOGETHER on the card header row -- NOT in a separate subnav or a card footer. That way the operator's eye lands on "who am I looking at" (the title label), "what am I looking for" (the filter), and "how many are there" (the pagination) in one horizontal sweep. * Column headers are clickable ````s inside ````: click to sort, click again to reverse. Active column shows a solid up/down triangle; inactive columns show a faded up-down glyph to hint they are sortable. Macros: * ``search_input(name_or_url, q, preserved, placeholder)`` -- inline search-and-clear form; sits on the LEFT of the card header row. * ``sort_header(label, column, sort, preserved, th_attrs)`` -- a ```` cell whose contents click to sort. * ``pagination_inline(page, preserved, label)`` -- the totals line + numbered nav + per-page selector; sits on the RIGHT of the card header row via ``ms-auto``. #} {% macro search_input(action, q, preserved, placeholder='freeform filter') -%} {# Inline search + Search + Clear. Uses ``type="search"`` so browsers render the built-in clear-X inside the box; the ``m-0`` + ``flex-grow-1`` shape survives a narrow viewport by wrapping below the pagination. ``preserved`` is a dict of {name: value} query params to preserve as hidden inputs. ``page`` is intentionally NOT preserved -- a fresh query resets to page 1. #}
{% for k, v in preserved.items() %} {% if v and k not in ('q', 'page') %} {% endif %} {% endfor %} {% if q %} Clear {% endif %}
{%- endmacro %} {% macro sort_header(label, column, sort, preserved, th_attrs='') -%} {# Sortable column header. Clicking flips direction on the active column or starts a fresh asc on any other column. Arrow indicator convention (matches bty): * Active + asc -> filled ▲ in accent colour * Active + desc -> filled ▼ in accent colour * Inactive -> faded ↕ so the header still telegraphs "this is sortable" without competing with the active one. #} {% set qs = _qs(preserved, {'sort': column, 'dir': sort.next_direction(column), 'page': None}) %} {{ label }} {% if sort.is_active(column) %} {% else %} {% endif %} {%- endmacro %} {% macro pagination_inline(page, preserved, label='rows') -%} {# Compact pagination that fits on one card-header row alongside a filter form. Renders the "1-25 of 200 events" line as inline muted text, then the Bootstrap ``pagination pagination-sm`` list (« « ‹ 1 2 [3] 4 5 › » »), then the per-page selector. The pagination list follows bty's shape: first/prev/window/next/ last with ``…`` ellipses when the window doesn't touch the ends. Numbered ``.page-link`` inside ``
  • `` is the Bootstrap-native shape (not a btn-group). #}
    {% if page.total == 0 %} No {{ label }}. {% else %} {{ page.first_row }}{{ page.last_row }} of {{ page.total }} {{ label }} {% endif %} {% if page.last_page > 1 %}
    • ««
    • «
    • {% set nums = page.numbered_pages() %} {% if nums[0] > 1 %}
    • 1
    • {% if nums[0] > 2 %}
    • {% endif %} {% endif %} {% for n in nums %}
    • {{ n }}
    • {% endfor %} {% if nums[-1] < page.last_page %} {% if nums[-1] < page.last_page - 1 %}
    • {% endif %}
    • {{ page.last_page }}
    • {% endif %}
    • »
    • »»
    {% endif %} {# Per-page selector as a GET-form: submits with all preserved params as hidden inputs but drops ``page`` so the choice always lands on page 1. #}
    {% for k, v in preserved.items() %} {% if v and k not in ('per_page', 'page') %} {% endif %} {% endfor %}
    {%- endmacro %}