{# ── Full table body (thead + tbody + empty state + pagination) Rendered by both list.html (initial page load) and list_rows.html (HTMX fragment swap on search / sort / page). The outer div "#pv-table-container" lives in list.html and is the htmx.ajax() swap target (swap: 'innerHTML'). #}
{# Header row — checkbox gutter + column titles + sort + actions. #} {# Leftmost checkbox column — placeholder for future bulk selection. #} {% if sequence_field %} {# Drag-handle column: no title, narrow gutter. #} {% endif %} {% for h in headers %} {# Drag icon removed from column headers per Nuru spec; draggable attribute kept so JS column-reorder still works. #} {% endfor %} {# Grouped path — pair of s per group: 1. A header tbody with the clickable group title + count. 2. A body tbody holding the actual rows, toggled by `open`. Splitting them lets us use a single Alpine scope for both while keeping the header always visible. Flat path falls through below when `groups` is None. #} {% if groups is not none %} {% for grp in groups %} {# One per group. The Alpine `open` scope wraps the header + body rows so they all see the same toggle. #} {% for row in grp.rows %} {% with row_extra='x-show="open"' %} {% include "list_row.html" %} {% endwith %} {% endfor %} {% endfor %} {% else %} {% for row in rows %} {% include "list_row.html" %} {% endfor %} {% endif %}
Actions
{{ grp.label }} ({{ grp.count }})
{# Empty state #} {% if groups is not none %} {% set _is_empty = not groups %} {% else %} {% set _is_empty = not rows %} {% endif %} {% if _is_empty %}

No records found

{% endif %}
{# Pagination #}
{% include "list_pagination.html" %}