fix: use server-side sorting for paginated tables
Upgrade @cameleer/design-system to v0.1.1 which adds onSortChange callback to DataTable. Wire it up in Dashboard (exchanges), AuditLog, and RouteDetail (recent executions) so sorting triggers a new API request with sortField/sortDir instead of only sorting the current page. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
8
ui/package-lock.json
generated
8
ui/package-lock.json
generated
@@ -8,7 +8,7 @@
|
|||||||
"name": "ui",
|
"name": "ui",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cameleer/design-system": "^0.0.3",
|
"@cameleer/design-system": "^0.1.1",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
@@ -276,9 +276,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@cameleer/design-system": {
|
"node_modules/@cameleer/design-system": {
|
||||||
"version": "0.0.3",
|
"version": "0.1.1",
|
||||||
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.0.3/design-system-0.0.3.tgz",
|
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.1/design-system-0.1.1.tgz",
|
||||||
"integrity": "sha512-x1mZvgYz7j57xFB26pMh9hn5waSJA1CcRWTgkzleLfaO/CmhekLup1HHlbh0b9SxVci6g2HzbcJldr4kvM1yzg==",
|
"integrity": "sha512-5PNIDuw9vcM0BVQIdiJQalWoMJPUZj01f6rX3unHDCF1gE1mBHCFZ41RPy2QsuKmjgZ1azenXXdnHL/XGVostQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts"
|
"generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cameleer/design-system": "^0.0.3",
|
"@cameleer/design-system": "^0.1.1",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Badge, DateRangePicker, Input, Select, MonoText, CodeBlock, DataTable,
|
Badge, DateRangePicker, Input, Select, MonoText, CodeBlock, DataTable,
|
||||||
} from '@cameleer/design-system';
|
} from '@cameleer/design-system';
|
||||||
@@ -60,6 +60,14 @@ export default function AuditLogPage() {
|
|||||||
const [categoryFilter, setCategoryFilter] = useState('');
|
const [categoryFilter, setCategoryFilter] = useState('');
|
||||||
const [searchFilter, setSearchFilter] = useState('');
|
const [searchFilter, setSearchFilter] = useState('');
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
|
const [sortField, setSortField] = useState<string>('timestamp');
|
||||||
|
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc');
|
||||||
|
|
||||||
|
const handleSortChange = useCallback((key: string, dir: 'asc' | 'desc') => {
|
||||||
|
setSortField(key);
|
||||||
|
setSortDir(dir);
|
||||||
|
setPage(0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { data } = useAuditLog({
|
const { data } = useAuditLog({
|
||||||
username: userFilter || undefined,
|
username: userFilter || undefined,
|
||||||
@@ -67,6 +75,8 @@ export default function AuditLogPage() {
|
|||||||
search: searchFilter || undefined,
|
search: searchFilter || undefined,
|
||||||
from: dateRange.start.toISOString(),
|
from: dateRange.start.toISOString(),
|
||||||
to: dateRange.end.toISOString(),
|
to: dateRange.end.toISOString(),
|
||||||
|
sort: sortField,
|
||||||
|
order: sortDir,
|
||||||
page,
|
page,
|
||||||
size: 25,
|
size: 25,
|
||||||
});
|
});
|
||||||
@@ -122,6 +132,7 @@ export default function AuditLogPage() {
|
|||||||
sortable
|
sortable
|
||||||
flush
|
flush
|
||||||
pageSize={25}
|
pageSize={25}
|
||||||
|
onSortChange={handleSortChange}
|
||||||
rowAccent={(row) => row.result === 'FAILURE' ? 'error' : undefined}
|
rowAccent={(row) => row.result === 'FAILURE' ? 'error' : undefined}
|
||||||
expandedContent={(row) => (
|
expandedContent={(row) => (
|
||||||
<div className={styles.expandedDetail}>
|
<div className={styles.expandedDetail}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useMemo } from 'react'
|
import { useState, useMemo, useCallback } from 'react'
|
||||||
import { useParams, useNavigate } from 'react-router'
|
import { useParams, useNavigate } from 'react-router'
|
||||||
import {
|
import {
|
||||||
DataTable,
|
DataTable,
|
||||||
@@ -176,12 +176,19 @@ export default function Dashboard() {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [selectedId, setSelectedId] = useState<string | undefined>()
|
const [selectedId, setSelectedId] = useState<string | undefined>()
|
||||||
const [panelOpen, setPanelOpen] = useState(false)
|
const [panelOpen, setPanelOpen] = useState(false)
|
||||||
|
const [sortField, setSortField] = useState<string>('startTime')
|
||||||
|
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc')
|
||||||
|
|
||||||
const { timeRange, statusFilters } = useGlobalFilters()
|
const { timeRange, statusFilters } = useGlobalFilters()
|
||||||
const timeFrom = timeRange.start.toISOString()
|
const timeFrom = timeRange.start.toISOString()
|
||||||
const timeTo = timeRange.end.toISOString()
|
const timeTo = timeRange.end.toISOString()
|
||||||
const timeWindowSeconds = (timeRange.end.getTime() - timeRange.start.getTime()) / 1000
|
const timeWindowSeconds = (timeRange.end.getTime() - timeRange.start.getTime()) / 1000
|
||||||
|
|
||||||
|
const handleSortChange = useCallback((key: string, dir: 'asc' | 'desc') => {
|
||||||
|
setSortField(key)
|
||||||
|
setSortDir(dir)
|
||||||
|
}, [])
|
||||||
|
|
||||||
// ─── API hooks ───────────────────────────────────────────────────────────
|
// ─── API hooks ───────────────────────────────────────────────────────────
|
||||||
const { data: stats } = useExecutionStats(timeFrom, timeTo, routeId, appId)
|
const { data: stats } = useExecutionStats(timeFrom, timeTo, routeId, appId)
|
||||||
const { data: timeseries } = useStatsTimeseries(timeFrom, timeTo, routeId, appId)
|
const { data: timeseries } = useStatsTimeseries(timeFrom, timeTo, routeId, appId)
|
||||||
@@ -191,6 +198,8 @@ export default function Dashboard() {
|
|||||||
timeTo,
|
timeTo,
|
||||||
routeId: routeId || undefined,
|
routeId: routeId || undefined,
|
||||||
application: appId || undefined,
|
application: appId || undefined,
|
||||||
|
sortField,
|
||||||
|
sortDir,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
},
|
},
|
||||||
@@ -385,6 +394,7 @@ export default function Dashboard() {
|
|||||||
selectedId={selectedId}
|
selectedId={selectedId}
|
||||||
sortable
|
sortable
|
||||||
flush
|
flush
|
||||||
|
onSortChange={handleSortChange}
|
||||||
rowAccent={handleRowAccent}
|
rowAccent={handleRowAccent}
|
||||||
expandedContent={(row: Row) =>
|
expandedContent={(row: Row) =>
|
||||||
row.errorMessage ? (
|
row.errorMessage ? (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo, useCallback } from 'react';
|
||||||
import { useParams, useNavigate, Link } from 'react-router';
|
import { useParams, useNavigate, Link } from 'react-router';
|
||||||
import {
|
import {
|
||||||
KpiStrip,
|
KpiStrip,
|
||||||
@@ -260,6 +260,13 @@ export default function RouteDetail() {
|
|||||||
const timeTo = timeRange.end.toISOString();
|
const timeTo = timeRange.end.toISOString();
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState('performance');
|
const [activeTab, setActiveTab] = useState('performance');
|
||||||
|
const [recentSortField, setRecentSortField] = useState<string>('startTime');
|
||||||
|
const [recentSortDir, setRecentSortDir] = useState<'asc' | 'desc'>('desc');
|
||||||
|
|
||||||
|
const handleRecentSortChange = useCallback((key: string, dir: 'asc' | 'desc') => {
|
||||||
|
setRecentSortField(key);
|
||||||
|
setRecentSortDir(dir);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// ── API queries ────────────────────────────────────────────────────────────
|
// ── API queries ────────────────────────────────────────────────────────────
|
||||||
const { data: catalog } = useRouteCatalog();
|
const { data: catalog } = useRouteCatalog();
|
||||||
@@ -272,6 +279,8 @@ export default function RouteDetail() {
|
|||||||
timeTo,
|
timeTo,
|
||||||
routeId: routeId || undefined,
|
routeId: routeId || undefined,
|
||||||
application: appId || undefined,
|
application: appId || undefined,
|
||||||
|
sortField: recentSortField,
|
||||||
|
sortDir: recentSortDir,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
});
|
});
|
||||||
@@ -560,6 +569,7 @@ export default function RouteDetail() {
|
|||||||
onRowClick={(row) => navigate(`/exchanges/${row.executionId}`)}
|
onRowClick={(row) => navigate(`/exchanges/${row.executionId}`)}
|
||||||
sortable
|
sortable
|
||||||
pageSize={20}
|
pageSize={20}
|
||||||
|
onSortChange={handleRecentSortChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user