{# Reusable Jinja macros for paginated + sortable tables on /ui/. Caller passes a ``SortState`` and a ``PageState`` from ``bty.web._table_state``. The macros render Bootstrap-flavoured table-header links and a pagination footer that preserve every other query param via ``preserved`` so an active filter survives a click on a column header. #} {# Sortable column header. - ``label``: human text shown in the header cell. - ``column``: SQL column key matching one of the page's allowlist entries (see _table_state.parse_sort). - ``sort``: the SortState parsed for this request. - ``preserved``: query-param dict to keep (filters, per_page, ...). - ``th_attrs`` (optional): extra attributes for the ```` (e.g. ``style="white-space: nowrap;"``) so narrow columns stay narrow. The active column gets an arrow indicator (up triangle = asc, down triangle = desc). All other columns show a faded up-down glyph to hint the header is clickable. #} {% macro sort_header(label, column, sort, preserved, th_attrs='') %} {% set qs = build_query_string( preserved, {'sort': column, 'dir': sort.next_direction(column), 'page': None} ) %} {{ label }} {% if sort.is_active(column) %} {% else %} {% endif %} {% endmacro %} {# Pagination footer. - ``page``: the PageState (see _table_state.parse_pagination). - ``preserved``: query-param dict to keep across the nav links. - ``label`` (optional): noun for the "Showing X-Y of Z" line; defaults to "rows". Renders nothing when there's only one page and no per-page choice the operator could change. Always renders the totals line when there are rows. #} {% macro pagination_footer(page, preserved, label='rows') %} {# Always render the footer so an empty (filtered / searched) view shows the operator a "No