forked from enviPath/enviPy
[Feature] Server pagination implementation (#243)
## Major Changes - Implement a REST style API app in epapi - Currently implements a GET method for all entity types in the browse menu (both package level and global) - Provides paginated results per default with query style filtering for reviewed vs unreviewed. - Provides new paginated templates with thin wrappers per entity types for easier maintainability - Implements e2e tests for the API ## Minor changes - Added more comprehensive gitignore to cover coverage reports and other test/node.js etc. data. - Add additional CI file for API tests that only gets triggered on API relevant changes. ## ⚠️ Currently only works with session-based authentication. Token based will be added in new PR. Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Co-authored-by: jebus <lorsbach@envipath.com> Reviewed-on: enviPath/enviPy#243 Co-authored-by: Tobias O <tobias.olenyi@envipath.com> Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
This commit is contained in:
134
templates/collections/paginated_base.html
Normal file
134
templates/collections/paginated_base.html
Normal file
@ -0,0 +1,134 @@
|
||||
{% extends "framework_modern.html" %}
|
||||
{% load static %}
|
||||
|
||||
{# List title for empty text - defaults to "items", should be overridden by child templates #}
|
||||
{% block list_title %}items{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block action_modals %}
|
||||
{% endblock action_modals %}
|
||||
|
||||
<div class="px-8 py-4">
|
||||
<!-- Header Section -->
|
||||
<div class="card bg-base-100">
|
||||
<div class="card-body px-0 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="card-title text-2xl">
|
||||
{% block page_title %}{{ page_title|default:"Items" }}{% endblock %}
|
||||
</h2>
|
||||
{% block action_button %}
|
||||
{# Can be overridden by including action buttons for entity type #}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
{% block description %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if list_mode == "combined" %}
|
||||
{# ===== COMBINED MODE: Single list without tabs ===== #}
|
||||
<div
|
||||
class="mt-6 w-full"
|
||||
x-data="remotePaginatedList({
|
||||
endpoint: '{{ api_endpoint }}',
|
||||
instanceId: '{{ entity_type }}_combined',
|
||||
perPage: {{ per_page|default:50 }}
|
||||
})"
|
||||
>
|
||||
{% include "collections/_paginated_list_partial.html" with empty_text=list_title|default:"items" show_review_badge=True %}
|
||||
</div>
|
||||
{% else %}
|
||||
{# ===== TABBED MODE: Reviewed/Unreviewed tabs (default) ===== #}
|
||||
<div
|
||||
class="mt-6 w-full"
|
||||
x-data="{
|
||||
activeTab: 'reviewed',
|
||||
reviewedCount: 0,
|
||||
unreviewedCount: 0,
|
||||
reviewedLoaded: false,
|
||||
unreviewedLoaded: false,
|
||||
updateTabSelection() {
|
||||
// Only auto-select unreviewed tab if both have loaded and there are no reviewed items
|
||||
if (this.reviewedLoaded && this.unreviewedLoaded && this.reviewedCount === 0 && this.unreviewedCount > 0) {
|
||||
this.activeTab = 'unreviewed';
|
||||
}
|
||||
}
|
||||
}"
|
||||
>
|
||||
{# No items found message #}
|
||||
<div
|
||||
x-show="reviewedCount === 0 && unreviewedCount === 0"
|
||||
class="text-base-content/70 py-8 text-center"
|
||||
>
|
||||
<p>No items found.</p>
|
||||
</div>
|
||||
|
||||
{# Tabs Navigation #}
|
||||
<div
|
||||
role="tablist"
|
||||
class="tabs tabs-border"
|
||||
x-show="reviewedCount > 0 || unreviewedCount > 0"
|
||||
>
|
||||
<button
|
||||
role="tab"
|
||||
class="tab"
|
||||
:class="{ 'tab-active': activeTab === 'reviewed' }"
|
||||
@click="activeTab = 'reviewed'"
|
||||
x-show="reviewedCount > 0"
|
||||
>
|
||||
Reviewed
|
||||
<span
|
||||
class="badge badge-xs badge-dash badge-info mb-2 ml-2"
|
||||
x-text="reviewedCount"
|
||||
></span>
|
||||
</button>
|
||||
<button
|
||||
role="tab"
|
||||
class="tab"
|
||||
:class="{ 'tab-active': activeTab === 'unreviewed' }"
|
||||
@click="activeTab = 'unreviewed'"
|
||||
x-show="unreviewedCount > 0"
|
||||
>
|
||||
Unreviewed
|
||||
<span
|
||||
class="badge badge-xs badge-dash badge-info mb-2 ml-2"
|
||||
x-text="unreviewedCount"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{# Reviewed Tab Content #}
|
||||
<div
|
||||
class="mt-6"
|
||||
x-show="activeTab === 'reviewed' && (reviewedCount > 0 || unreviewedCount > 0)"
|
||||
x-data="remotePaginatedList({
|
||||
endpoint: '{{ api_endpoint }}?review_status=true',
|
||||
instanceId: '{{ entity_type }}_reviewed',
|
||||
isReviewed: true,
|
||||
perPage: {{ per_page|default:50 }}
|
||||
})"
|
||||
@items-loaded="reviewedCount = totalItems; reviewedLoaded = true; updateTabSelection()"
|
||||
>
|
||||
{% include "collections/_paginated_list_partial.html" with empty_text="reviewed "|add:list_title|default:"items" show_review_badge=True always_show_badge=True %}
|
||||
</div>
|
||||
|
||||
{# Unreviewed Tab Content #}
|
||||
<div
|
||||
class="mt-6"
|
||||
x-show="activeTab === 'unreviewed' && (reviewedCount > 0 || unreviewedCount > 0)"
|
||||
x-data="remotePaginatedList({
|
||||
endpoint: '{{ api_endpoint }}?review_status=false',
|
||||
instanceId: '{{ entity_type }}_unreviewed',
|
||||
isReviewed: false,
|
||||
perPage: {{ per_page|default:50 }}
|
||||
})"
|
||||
@items-loaded="unreviewedCount = totalItems; unreviewedLoaded = true; updateTabSelection()"
|
||||
>
|
||||
{% include "collections/_paginated_list_partial.html" with empty_text="unreviewed "|add:list_title|default:"items" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
Reference in New Issue
Block a user