From a03314af506d40f63ccf342d3e327bebf565fd7f Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Sat, 18 Jul 2026 13:42:47 +0530 Subject: [PATCH] Add pagination to service catalogue --- app/modules/services/services.py | 17 +++- .../templates/services/catalogue_list.html | 83 ++++++++++++++++++- app/modules/services/ui.py | 4 +- 3 files changed, 100 insertions(+), 4 deletions(-) diff --git a/app/modules/services/services.py b/app/modules/services/services.py index 0a13c60..c3320fd 100644 --- a/app/modules/services/services.py +++ b/app/modules/services/services.py @@ -65,6 +65,13 @@ def get_category(db: Session, category_id: int) -> ServiceCategory | None: def list_catalogue_payload(db: Session, *, q: str = "", category_id: int | None = None, recurrence_type: str = "", engagement_type: str = "", page: int = 1, per_page: int = 20): + # Keep catalogue paging predictable and prevent invalid/very large requests. + page = max(1, int(page or 1)) + allowed_page_sizes = {20, 50, 100} + per_page = int(per_page or 20) + if per_page not in allowed_page_sizes: + per_page = 20 + query = select(ServiceCatalogue).where( ServiceCatalogue.is_active.is_(True) ).options( @@ -90,12 +97,18 @@ def list_catalogue_payload(db: Session, *, q: str = "", category_id: int | None query = query.where(ServiceCatalogue.engagement_type == normalize_engagement_type(engagement_type)) total = db.execute(select(func.count()).select_from(query.subquery())).scalar_one() + pages = max(1, (total + per_page - 1) // per_page) + page = min(page, pages) + rows = db.execute( query.order_by(ServiceCatalogue.sort_order.asc(), ServiceCatalogue.service_name.asc()) .offset((page - 1) * per_page) .limit(per_page) ).scalars().all() + start_item = ((page - 1) * per_page + 1) if total else 0 + end_item = min(page * per_page, total) + return { "rows": rows, "q": q, @@ -105,7 +118,9 @@ def list_catalogue_payload(db: Session, *, q: str = "", category_id: int | None "page": page, "per_page": per_page, "total": total, - "pages": max(1, (total + per_page - 1) // per_page), + "pages": pages, + "start_item": start_item, + "end_item": end_item, } diff --git a/app/modules/services/templates/services/catalogue_list.html b/app/modules/services/templates/services/catalogue_list.html index 829c924..7d9714a 100644 --- a/app/modules/services/templates/services/catalogue_list.html +++ b/app/modules/services/templates/services/catalogue_list.html @@ -41,7 +41,22 @@ {% for value, label in engagement_type_choices %}{% endfor %} -
+
+ +
+ {% if q or category_id or recurrence_type or engagement_type or per_page != 20 %} + Clear + {% endif %} + +
+
@@ -107,6 +122,72 @@ {% endfor %} + +
+
+ {% if total %} + Showing {{ start_item }}{{ end_item }} + of {{ total }} active services + {% else %} + No active services found + {% endif %} +
+ + {% if pages > 1 %} +
+
+ + + + + + + +
+ + {% set first_page = 1 if page <= 3 else page - 2 %} + {% set last_page = pages if page + 2 >= pages else page + 2 %} + + {% if first_page > 1 %} +
+ + +
+ {% if first_page > 2 %}{% endif %} + {% endif %} + + {% for page_number in range(first_page, last_page + 1) %} +
+ + + + + + + +
+ {% endfor %} + + {% if last_page < pages %} + {% if last_page < pages - 1 %}{% endif %} +
+ + +
+ {% endif %} + +
+ + + + + + + +
+
+ {% endif %} +
{% endblock %} diff --git a/app/modules/services/ui.py b/app/modules/services/ui.py index 336a367..732afd2 100644 --- a/app/modules/services/ui.py +++ b/app/modules/services/ui.py @@ -1,6 +1,7 @@ -from __future__ import annotations +from __future__ import annotations from datetime import datetime, timezone + from fastapi import APIRouter, File, Form, Request, UploadFile from fastapi.responses import FileResponse, RedirectResponse, StreamingResponse from sqlalchemy import select @@ -1621,4 +1622,3 @@ def upload_firm_task_templates(request: Request, file: UploadFile = File(...), u return _render(request, "modules/services/templates/services/bulk_imports/result.html", db, user, title="Firm Task Templates Import Result", result=result, back_url="/services/bulk-imports") finally: db.close() -