mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-28 11:17:07 +00:00
feat: sortable resource tables and richer dashboard stack-health columns (#1498)
Make the Resources Images, Volumes, and Networks tables sortable with a shared useTableSort hook and SortableTableHead, move the Images scan-history control into the Images tab header, and keep the network List/Topology toggle anchored with Create Network visible in both modes. Rework the dashboard Stack health table: drop the redundant Host column, add sortable Stack/Up/CPU/Mem headers, and add Source (local/git) and Port columns. The status endpoint now labels each stack with its git/local source, computed outside the cache so linking changes show immediately. Extract a reusable CreateNetworkDialog and add a create-network action to the stack-detail Networking tab.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { useTableSort } from '../useTableSort';
|
||||
|
||||
interface Row { name: string; size: number }
|
||||
|
||||
const ITEMS: Row[] = [
|
||||
{ name: 'beta', size: 30 },
|
||||
{ name: 'alpha', size: 10 },
|
||||
{ name: 'gamma', size: 20 },
|
||||
];
|
||||
|
||||
const COMPARATORS = {
|
||||
name: (a: Row, b: Row) => a.name.localeCompare(b.name),
|
||||
size: (a: Row, b: Row) => a.size - b.size,
|
||||
};
|
||||
|
||||
describe('useTableSort', () => {
|
||||
it('sorts by the initial key and direction', () => {
|
||||
const { result } = renderHook(() => useTableSort(ITEMS, COMPARATORS, 'name'));
|
||||
expect(result.current.sorted.map(r => r.name)).toEqual(['alpha', 'beta', 'gamma']);
|
||||
expect(result.current.sortKey).toBe('name');
|
||||
expect(result.current.sortDir).toBe('asc');
|
||||
});
|
||||
|
||||
it('flips direction when toggling the active key', () => {
|
||||
const { result } = renderHook(() => useTableSort(ITEMS, COMPARATORS, 'name'));
|
||||
act(() => result.current.toggleSort('name'));
|
||||
expect(result.current.sortDir).toBe('desc');
|
||||
expect(result.current.sorted.map(r => r.name)).toEqual(['gamma', 'beta', 'alpha']);
|
||||
});
|
||||
|
||||
it('switches key and resets direction to asc', () => {
|
||||
const { result } = renderHook(() => useTableSort(ITEMS, COMPARATORS, 'name', 'desc'));
|
||||
act(() => result.current.toggleSort('size'));
|
||||
expect(result.current.sortKey).toBe('size');
|
||||
expect(result.current.sortDir).toBe('asc');
|
||||
expect(result.current.sorted.map(r => r.size)).toEqual([10, 20, 30]);
|
||||
});
|
||||
|
||||
it('does not mutate the input array', () => {
|
||||
const input = [...ITEMS];
|
||||
renderHook(() => useTableSort(input, COMPARATORS, 'size'));
|
||||
expect(input.map(r => r.name)).toEqual(['beta', 'alpha', 'gamma']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
export type SortDir = 'asc' | 'desc';
|
||||
|
||||
/**
|
||||
* Shared client-side table sort. Pass a stable `comparators` map (define it at
|
||||
* module scope so the memo does not re-sort every render). This is the standard
|
||||
* sort behavior for data tables, mirroring the Security Images tab.
|
||||
*/
|
||||
export function useTableSort<T, K extends string>(
|
||||
items: T[],
|
||||
comparators: Record<K, (a: T, b: T) => number>,
|
||||
// NoInfer so K is inferred from `comparators` only; otherwise `initialKey`
|
||||
// collapses K to a single literal and the column keys fail to type-check.
|
||||
initialKey: NoInfer<K>,
|
||||
initialDir: SortDir = 'asc',
|
||||
) {
|
||||
const [sortKey, setSortKey] = useState<K>(initialKey);
|
||||
const [sortDir, setSortDir] = useState<SortDir>(initialDir);
|
||||
|
||||
const sorted = useMemo(() => {
|
||||
const cmp = comparators[sortKey];
|
||||
const dir = sortDir === 'asc' ? 1 : -1;
|
||||
return [...items].sort((a, b) => cmp(a, b) * dir);
|
||||
}, [items, comparators, sortKey, sortDir]);
|
||||
|
||||
const toggleSort = (key: K) => {
|
||||
if (key === sortKey) setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));
|
||||
else { setSortKey(key); setSortDir('asc'); }
|
||||
};
|
||||
|
||||
return { sorted, sortKey, sortDir, toggleSort };
|
||||
}
|
||||
Reference in New Issue
Block a user