- Added TableSortProvider to manage sorting state in AdminsTable, CmsTable, CompaniesTable, CustomersTable, TendersTable, and CompanyCategoriesTable. - Enhanced TableHead component to support sortable columns with visual indicators for sort direction. - Introduced columnSortKeys and columnDefaultOrder mappings in respective table presenters to define sortable fields and default sorting behavior. - Updated useCompanyListPresenter, useCmsTablePresenter, useCustomerListPresenter, useTenderListPresenter, and useCompanyCategoriesPresenter to handle sorting logic and state management. - Improved user experience by allowing dynamic sorting of table data based on user interactions.
7.5 KiB
BP Panel — List sorting (frontend)
Guide for Business Partner (BP) list screens under /api/v1. Use these query parameters to control item order on paginated GET endpoints.
This does not apply to Kanban board card order (see board-api.md — cards use order on move/reorder APIs).
Quick reference
| Parameter | Type | Default | Allowed values |
|---|---|---|---|
sort_by |
string | created_at |
MongoDB field name (see per-endpoint tables) |
sort_order |
string | desc |
asc or desc |
Example (newest tenders first — also the default if omitted):
GET /api/v1/tenders?limit=20&sort_by=created_at&sort_order=desc
Authorization: Bearer <customer_access_token>
Example (soonest deadline first):
GET /api/v1/tenders?limit=20&sort_by=tender_deadline&sort_order=asc
Authentication
All BP list endpoints below require a customer JWT from:
POST /api/v1/profile/login
Authorization: Bearer <access_token>
Base URL (local): http://localhost:8081/api/v1
How sorting works
sort_bymust match the BSON field name stored in MongoDB (snake_case), not the JSON display name in the UI.sort_orderdesc— highest / latest values first (default).asc— lowest / earliest values first.
- If you omit both parameters, the API uses
sort_by=created_atandsort_order=desc. - Lists use keyset (cursor) pagination. When requesting page 2+ with
cursor, you must keep the samesort_by,sort_order,limit, and filters as the request that producedmeta.next_cursor. Changing sort mid-pagination returns 400 (cursor does not match requested sort). - Do not send
cursorandoffsettogether — the API returns 400.
Pagination parameters (used with sorting)
| Parameter | Default | Max | Notes |
|---|---|---|---|
limit |
10 |
100 |
Page size |
offset |
0 |
— | First page only; prefer cursor for deep pages |
cursor |
— | — | Copy from meta.next_cursor on the previous response |
Response metadata
Sorted list responses include meta on the envelope:
{
"success": true,
"message": "Tenders retrieved successfully",
"data": { "tenders": [] },
"meta": {
"total": 240,
"limit": 20,
"offset": 0,
"page": 1,
"pages": 12,
"has_more": true,
"next_cursor": "eyJ2IjoxLCJzb3J0X2ZpZWxkIjoiY3JlYXRlZF9hdCIs..."
}
}
Endpoints that support sort_by / sort_order
Tenders — main panel list
| Method / path | GET /api/v1/tenders |
| Also | GET /api/v1/tenders/recommend (same sorting rules) |
| Default sort | created_at desc |
Recommended sort_by values (field exists on tender documents):
sort_by |
Use case |
|---|---|
created_at |
Newest ingested / default |
updated_at |
Recently changed tenders |
publication_date |
Publication date on TED notice |
tender_deadline |
Submission / tender deadline |
submission_deadline |
Alternative deadline field when set |
estimated_value |
Value high → low or low → high |
title |
Alphabetical (locale-dependent) |
status |
Group by lifecycle status |
country_code |
Group by country |
The backend does not whitelist tender
sort_byvalues. Use only documented fields; unknown fields may sort incorrectly or behave unpredictably.
Example with filters + sort:
GET /api/v1/tenders?limit=20&country_codes=DE,FR&sort_by=publication_date&sort_order=desc&deadline_from=1717200000
TypeScript helper:
type SortOrder = "asc" | "desc";
interface ListSortParams {
sort_by?: string;
sort_order?: SortOrder;
limit?: number;
offset?: number;
cursor?: string;
}
function buildTenderListQuery(
filters: Record<string, string | number | string[] | undefined>,
sort: ListSortParams = { sort_by: "created_at", sort_order: "desc", limit: 20 }
): string {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(filters)) {
if (value === undefined) continue;
if (Array.isArray(value)) value.forEach((v) => params.append(key, v));
else params.set(key, String(value));
}
params.set("sort_by", sort.sort_by ?? "created_at");
params.set("sort_order", sort.sort_order ?? "desc");
if (sort.limit) params.set("limit", String(sort.limit));
if (sort.cursor) params.set("cursor", sort.cursor);
else if (sort.offset) params.set("offset", String(sort.offset));
return params.toString();
}
Notifications
| Method / path | GET /api/v1/notifications |
| Default sort | created_at desc |
Recommended sort_by values:
sort_by |
Use case |
|---|---|
created_at |
Newest notifications (default) |
updated_at |
Last updated |
seen_at |
Seen / unread ordering |
priority |
Important first (with sort_order=desc) |
schedule_at |
Scheduled notifications |
Feedback (company list)
| Method / path | GET /api/v1/feedback |
| Default sort | created_at desc |
| Scope | Company-scoped from the logged-in customer |
Recommended sort_by values:
sort_by |
Use case |
|---|---|
created_at |
Newest feedback (default) |
updated_at |
Recently changed |
feedback_type |
Group likes / dislikes |
Endpoints without sort_by / sort_order (BP)
| Endpoint | Notes |
|---|---|
GET /api/v1/tender-approvals |
Uses limit / offset only; no sort_by on the public handler today |
GET /api/v1/companies |
Single “my company” resource, not a sortable list |
Kanban (/admin/v1/kanban/...) |
Admin-only; card order is order + move/reorder APIs |
If you need sorted tender approvals in BP, coordinate with backend to add sort_by / sort_order to PublicGetTenderApprovals (admin list already supports them).
UI patterns
Column header → query params
| UI label | sort_by |
Typical sort_order |
|---|---|---|
| Date added | created_at |
desc |
| Deadline | tender_deadline |
asc |
| Value | estimated_value |
desc |
| Published | publication_date |
desc |
Toggle the same column: flip asc ↔ desc. Changing column: set new sort_by and reset to page 1 (offset=0, drop cursor).
Infinite scroll / “Load more”
- First request:
sort_by,sort_order,limit, filters — nocursor. - Next page: repeat the same sort and filters; pass
cursor=meta.next_cursor. - Stop when
meta.has_more === false.
Errors
| Situation | HTTP | error.code (typical) |
|---|---|---|
Invalid sort_order (not asc/desc) |
400 | BAD_REQUEST |
cursor + offset together |
400 | BAD_REQUEST |
cursor with different sort_by / sort_order |
400 | BAD_REQUEST |
limit < 1 or > 100 |
400 | BAD_REQUEST |
Admin panel (reference)
Admin list routes under /admin/v1/* use the same sort_by / sort_order conventions where response.NewPagination is used. Some admin forms restrict values (e.g. tender approvals: created_at, updated_at, status, submission_mode). BP tender lists are less restricted than admin tender-approval lists.
Source: pkg/response/pagination.go, pkg/mongo/list_pagination.go, internal/tender, internal/notification, internal/feedback · Last updated: 2026-05-31