From 11e256ac70e43d389fe1c2877a18f9b94ca3265b Mon Sep 17 00:00:00 2001 From: jgor20 <102353650+jgor20@users.noreply.github.com> Date: Sun, 11 Jan 2026 13:40:31 +0000 Subject: [PATCH] feat(ui): add sortable columns to models table Add sorting functionality to the models table with clickable headers for columns like Stat, Model Identity, Global Quota, Next Reset, and Account Distribution. Includes dynamic sort icons and logic to handle ascending/descending order with appropriate defaults. --- public/js/data-store.js | 42 ++++++++++++++++++++++--- public/views/models.html | 68 +++++++++++++++++++++++++++++++++------- 2 files changed, 95 insertions(+), 15 deletions(-) diff --git a/public/js/data-store.js b/public/js/data-store.js index 1eb496d..a55f8aa 100644 --- a/public/js/data-store.js +++ b/public/js/data-store.js @@ -22,7 +22,9 @@ document.addEventListener('alpine:init', () => { filters: { account: 'all', family: 'all', - search: '' + search: '', + sortCol: 'avgQuota', + sortAsc: true }, // Settings for calculation @@ -177,20 +179,52 @@ document.addEventListener('alpine:init', () => { resetIn: minResetTime ? window.utils.formatTimeUntil(minResetTime) : '-', quotaInfo, pinned: !!config.pinned, - hidden: !!isHidden // Use computed visibility + hidden: !!isHidden, // Use computed visibility + activeCount: quotaInfo.filter(q => q.pct > 0).length }); }); - // Sort: Pinned first, then by avgQuota (descending) + // Sort: Pinned first, then by selected column + const sortCol = this.filters.sortCol; + const sortAsc = this.filters.sortAsc; + this.quotaRows = rows.sort((a, b) => { if (a.pinned !== b.pinned) return a.pinned ? -1 : 1; - return b.avgQuota - a.avgQuota; + + let valA = a[sortCol]; + let valB = b[sortCol]; + + // Handle nulls (always push to bottom) + if (valA === valB) return 0; + if (valA === null || valA === undefined) return 1; + if (valB === null || valB === undefined) return -1; + + if (typeof valA === 'string' && typeof valB === 'string') { + return sortAsc ? valA.localeCompare(valB) : valB.localeCompare(valA); + } + + return sortAsc ? valA - valB : valB - valA; }); // Trigger Dashboard Update if active // Ideally dashboard watches this store. }, + setSort(col) { + if (this.filters.sortCol === col) { + this.filters.sortAsc = !this.filters.sortAsc; + } else { + this.filters.sortCol = col; + // Default sort direction: Descending for numbers/stats, Ascending for text/time + if (['avgQuota', 'activeCount'].includes(col)) { + this.filters.sortAsc = false; + } else { + this.filters.sortAsc = true; + } + } + this.computeQuotaRows(); + }, + getModelFamily(modelId) { const lower = modelId.toLowerCase(); if (lower.includes('claude')) return 'claude'; diff --git a/public/views/models.html b/public/views/models.html index 8b2b1d6..5efcde1 100644 --- a/public/views/models.html +++ b/public/views/models.html @@ -94,14 +94,60 @@ :class="{'table-xs': $store.settings.compact, 'table-sm': !$store.settings.compact}"> - Stat - Model Identity - Global - Quota - Next Reset + +
+ Stat +
+ + +
+ Model Identity + + + +
+ + +
+ Global Quota + + + +
+ + +
+ Next Reset + + + +
+ + +
+ Account Distribution + + + +
- Account - Distribution Actions @@ -143,20 +189,20 @@ -
+
-
+