feat(crm): Finder-style sortable column headers, default sort by issue date

Replace the sort <select> dropdowns on the invoice, quote and contract
list pages with clickable column headers that toggle asc/desc and show a
chevron indicator. Adds a shared SortableHeader component + useColumnSort
hook that maps clickable columns onto the server-side sort enum.
Make issue date (newest first) the standard sort on all three lists,
set at the frontend, route and service layers. Adds issue_asc/issue_desc
to invoices and an "Issued" column to the bills table so the default is
visible and toggleable. Extends sort coverage so every clickable column
has both directions (+customer_desc on all; +issue_asc/desc on
quotes/contracts). Storno rows remain listed.
This commit is contained in:
Luca
2026-06-02 10:59:36 +02:00
parent 095edfe06d
commit d9251c0850
16 changed files with 197 additions and 61 deletions
+12 -1
View File
@@ -621,7 +621,7 @@ async function buildRenderContext(contract, inclusions) {
// Public API
// ---------------------------------------------------------------------
async function listContracts({ filters = {}, sort = 'newest', page = 1, pageSize = 25 } = {}) {
async function listContracts({ filters = {}, sort = 'issue_desc', page = 1, pageSize = 25 } = {}) {
return await withRetry(async () => {
let query = db('contracts')
.leftJoin('customer_accounts', 'contracts.customer_account_id', 'customer_accounts.id')
@@ -658,11 +658,22 @@ async function listContracts({ filters = {}, sort = 'newest', page = 1, pageSize
case 'oldest':
query = query.orderBy('contracts.created_at', 'asc').orderBy('contracts.id', 'asc');
break;
case 'issue_asc':
query = query.orderBy('contracts.issue_date', 'asc').orderBy('contracts.id', 'asc');
break;
case 'issue_desc':
query = query.orderBy('contracts.issue_date', 'desc').orderBy('contracts.id', 'desc');
break;
case 'customer_asc':
query = query
.orderByRaw('COALESCE(customer_accounts.company_name, customer_accounts.last_name, customer_accounts.email) asc')
.orderBy('contracts.id', 'desc');
break;
case 'customer_desc':
query = query
.orderByRaw('COALESCE(customer_accounts.company_name, customer_accounts.last_name, customer_accounts.email) desc')
.orderBy('contracts.id', 'desc');
break;
case 'newest':
default:
query = query.orderBy('contracts.created_at', 'desc').orderBy('contracts.id', 'desc');