Skip to content

Commit 1281a82

Browse files
lovincyrusmindspank
authored andcommitted
[ENG-755] Sorting table indicator in admin pages (#7372)
* disalbe sorting removal * move infinite scroll table to web-common table * default sort column in basic table * wip * fix sorting state in infinite scroll table * refactor infinite table in public urls
1 parent 337ec13 commit 1281a82

File tree

7 files changed

+95
-225
lines changed

7 files changed

+95
-225
lines changed

web-admin/src/features/organizations/users/OrgGroupsTable.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import type { ColumnDef } from "@tanstack/svelte-table";
88
import OrgGroupsTableActionsCell from "./OrgGroupsTableActionsCell.svelte";
99
import OrgGroupsTableGroupCompositeCell from "./OrgGroupsTableGroupCompositeCell.svelte";
10-
import InfiniteScrollTable from "@rilldata/web-admin/components/InfiniteScrollTable.svelte";
10+
import InfiniteScrollTable from "@rilldata/web-common/components/table/InfiniteScrollTable.svelte";
1111
1212
export let data: V1MemberUsergroup[];
1313
export let currentUserEmail: string;
@@ -28,6 +28,7 @@
2828
accessorKey: "groupName",
2929
header: "Group",
3030
enableSorting: true,
31+
sortDescFirst: true,
3132
cell: ({ row }) =>
3233
flexRender(OrgGroupsTableGroupCompositeCell, {
3334
name: row.original.groupName?.startsWith("autogroup:")

web-admin/src/features/organizations/users/OrgUsersTable.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
InfiniteQueryObserverResult,
1616
} from "@tanstack/svelte-query";
1717
import { ExternalLinkIcon } from "lucide-svelte";
18-
import InfiniteScrollTable from "@rilldata/web-admin/components/InfiniteScrollTable.svelte";
18+
import InfiniteScrollTable from "@rilldata/web-common/components/table/InfiniteScrollTable.svelte";
1919
2020
interface OrgUser extends V1OrganizationMemberUser, V1OrganizationInvite {
2121
invitedBy?: string;

web-admin/src/features/projects/environment-variables/EnvironmentVariablesTable.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
},
3636
{
3737
header: "Activity",
38+
sortDescFirst: true,
3839
accessorFn: (row) => row.createdOn,
3940
cell: ({ row }) => {
4041
return flexRender(ActivityCell, {

web-admin/src/features/projects/status/ProjectResourcesTable.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
{
5555
accessorFn: (row) => row.meta.stateUpdatedOn,
5656
header: "Last refresh",
57+
sortDescFirst: true,
5758
cell: (info) =>
5859
flexRender(RefreshCell, {
5960
date: info.getValue() as string,
Lines changed: 36 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,34 @@
11
<script lang="ts">
2-
import { writable } from "svelte/store";
3-
import {
4-
createSvelteTable,
5-
flexRender,
6-
getCoreRowModel,
7-
getSortedRowModel,
8-
} from "@tanstack/svelte-table";
9-
import type {
10-
ColumnDef,
11-
OnChangeFn,
12-
SortingState,
13-
TableOptions,
14-
} from "@tanstack/svelte-table";
2+
import type { ColumnDef } from "@tanstack/svelte-table";
3+
import { flexRender } from "@tanstack/svelte-table";
154
import PublicURLsActionsRow from "./PublicURLsActionsRow.svelte";
165
import DashboardLink from "./DashboardLink.svelte";
17-
import ArrowDown from "@rilldata/web-common/components/icons/ArrowDown.svelte";
18-
import type { V1MagicAuthToken } from "@rilldata/web-admin/client";
19-
import { createVirtualizer } from "@tanstack/svelte-virtual";
6+
import type {
7+
V1MagicAuthToken,
8+
RpcStatus,
9+
V1ListMagicAuthTokensResponse,
10+
} from "@rilldata/web-admin/client";
11+
import InfiniteScrollTable from "@rilldata/web-common/components/table/InfiniteScrollTable.svelte";
12+
import type {
13+
InfiniteData,
14+
InfiniteQueryObserverResult,
15+
} from "@tanstack/svelte-query";
2016
2117
interface MagicAuthTokenProps extends V1MagicAuthToken {
2218
dashboardTitle: string;
2319
}
2420
25-
const ROW_HEIGHT = 40;
26-
const OVERSCAN = 5;
27-
2821
export let data: MagicAuthTokenProps[];
29-
export let query: any;
22+
export let query: InfiniteQueryObserverResult<
23+
InfiniteData<V1ListMagicAuthTokensResponse, unknown>,
24+
RpcStatus
25+
>;
3026
export let onDelete: (deletedTokenId: string) => void;
3127
32-
let virtualListEl: HTMLDivElement;
33-
let sorting: SortingState = [];
34-
35-
function formatDate(value: string) {
36-
return new Date(value).toLocaleDateString(undefined, {
37-
year: "numeric",
38-
month: "short",
39-
day: "numeric",
40-
hour: "numeric",
41-
minute: "numeric",
42-
});
43-
}
44-
45-
// Re-render table when data changes
4628
$: safeData = Array.isArray(data) ? data : [];
47-
$: {
48-
if (safeData) {
49-
options.update((old) => ({
50-
...old,
51-
data: safeData,
52-
}));
53-
}
54-
}
29+
30+
$: dynamicTableMaxHeight =
31+
safeData.length > 12 ? `calc(100dvh - 300px)` : "auto";
5532
5633
const columns: ColumnDef<MagicAuthTokenProps, any>[] = [
5734
{
@@ -83,6 +60,7 @@
8360
{
8461
accessorKey: "usedOn",
8562
header: "Last acccesed",
63+
sortDescFirst: true,
8664
cell: (info) => {
8765
if (!info.getValue()) return "-";
8866
const date = formatDate(info.getValue() as string);
@@ -102,175 +80,23 @@
10280
},
10381
];
10482
105-
const setSorting: OnChangeFn<SortingState> = (updater) => {
106-
if (updater instanceof Function) {
107-
sorting = updater(sorting);
108-
} else {
109-
sorting = updater;
110-
}
111-
112-
options.update((old) => ({
113-
...old,
114-
state: {
115-
...old.state,
116-
sorting,
117-
},
118-
}));
119-
};
120-
121-
const options = writable<TableOptions<MagicAuthTokenProps>>({
122-
data: safeData,
123-
columns,
124-
state: {
125-
sorting,
126-
},
127-
onSortingChange: setSorting,
128-
getCoreRowModel: getCoreRowModel(),
129-
getSortedRowModel: getSortedRowModel(),
130-
});
131-
132-
const table = createSvelteTable(options);
133-
134-
$: rows = $table.getRowModel().rows;
135-
136-
$: virtualizer = createVirtualizer<HTMLDivElement, HTMLDivElement>({
137-
count: 0,
138-
getScrollElement: () => virtualListEl,
139-
estimateSize: () => ROW_HEIGHT,
140-
overscan: OVERSCAN,
141-
});
142-
143-
$: {
144-
$virtualizer.setOptions({
145-
count: query.hasNextPage ? safeData.length + 1 : safeData.length,
83+
function formatDate(value: string) {
84+
return new Date(value).toLocaleDateString(undefined, {
85+
year: "numeric",
86+
month: "short",
87+
day: "numeric",
88+
hour: "numeric",
89+
minute: "numeric",
14690
});
147-
148-
const [lastItem] = [...$virtualizer.getVirtualItems()].reverse();
149-
150-
if (
151-
lastItem &&
152-
lastItem.index > safeData.length - 1 &&
153-
query.hasNextPage &&
154-
!query.isFetchingNextPage
155-
) {
156-
query.fetchNextPage();
157-
}
15891
}
15992
</script>
16093

161-
<div class="list scroll-container" bind:this={virtualListEl}>
162-
<!-- FIXME: hide the bleeding corner in the sticky header -->
163-
<div
164-
class="table-wrapper"
165-
style="position: relative; height: {$virtualizer.getTotalSize()}px;"
166-
>
167-
<table>
168-
<thead>
169-
{#each $table.getHeaderGroups() as headerGroup}
170-
<tr class="h-10">
171-
{#each headerGroup.headers as header}
172-
<th
173-
colSpan={header.colSpan}
174-
class="px-4 py-2 text-left"
175-
on:click={header.column.getToggleSortingHandler()}
176-
>
177-
{#if !header.isPlaceholder}
178-
<div
179-
class:cursor-pointer={header.column.getCanSort()}
180-
class:select-none={header.column.getCanSort()}
181-
class="font-semibold text-gray-500 flex flex-row items-center gap-x-1 text-sm"
182-
>
183-
<svelte:component
184-
this={flexRender(
185-
header.column.columnDef.header,
186-
header.getContext(),
187-
)}
188-
/>
189-
{#if header.column.getIsSorted().toString() === "asc"}
190-
<span>
191-
<ArrowDown flip size="12px" />
192-
</span>
193-
{:else if header.column.getIsSorted().toString() === "desc"}
194-
<span>
195-
<ArrowDown size="12px" />
196-
</span>
197-
{/if}
198-
</div>
199-
{/if}
200-
</th>
201-
{/each}
202-
</tr>
203-
{/each}
204-
</thead>
205-
<tbody>
206-
{#each $virtualizer.getVirtualItems() as virtualRow, idx (virtualRow.index)}
207-
<tr
208-
style="height: {virtualRow.size}px; transform: translateY({virtualRow.start -
209-
idx * virtualRow.size}px);"
210-
>
211-
{#each rows[virtualRow.index]?.getVisibleCells() ?? [] as cell (cell.id)}
212-
<td
213-
class={`px-4 py-2 max-w-[200px] truncate ${cell.column.id === "actions" ? "w-1" : ""}`}
214-
data-label={cell.column.columnDef.header}
215-
>
216-
<svelte:component
217-
this={flexRender(
218-
cell.column.columnDef.cell,
219-
cell.getContext(),
220-
)}
221-
/>
222-
</td>
223-
{/each}
224-
</tr>
225-
{/each}
226-
</tbody>
227-
</table>
228-
</div>
229-
</div>
230-
231-
<style lang="postcss">
232-
table {
233-
@apply border-separate border-spacing-0 w-full;
234-
}
235-
table th,
236-
table td {
237-
@apply border-b border-gray-200;
238-
}
239-
thead {
240-
@apply sticky top-0 z-30 bg-white;
241-
}
242-
thead tr th {
243-
@apply border-t border-gray-200;
244-
}
245-
thead tr th:first-child {
246-
@apply border-l;
247-
@apply rounded-tl-sm;
248-
}
249-
thead tr th:last-child {
250-
@apply border-r;
251-
@apply rounded-tr-sm;
252-
}
253-
thead tr:last-child th {
254-
@apply border-b;
255-
}
256-
tbody tr:first-child {
257-
@apply border-t-0;
258-
}
259-
tbody td:first-child {
260-
@apply border-l;
261-
}
262-
tbody td:last-child {
263-
@apply border-r;
264-
}
265-
tbody tr:last-child td:first-child {
266-
@apply rounded-bl-sm;
267-
}
268-
tbody tr:last-child td:last-child {
269-
@apply rounded-br-sm;
270-
}
271-
.scroll-container {
272-
height: 680px;
273-
width: 100%;
274-
overflow-y: auto;
275-
}
276-
</style>
94+
<InfiniteScrollTable
95+
data={safeData}
96+
{columns}
97+
hasNextPage={query.hasNextPage}
98+
isFetchingNextPage={query.isFetchingNextPage}
99+
onLoadMore={() => query.fetchNextPage()}
100+
maxHeight={dynamicTableMaxHeight}
101+
rowHeight={40}
102+
/>

web-common/src/components/table/BasicTable.svelte

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,26 @@
2323
2424
let sorting: SortingState = [];
2525
26+
// Initialize sorting for sortDescFirst column
27+
const sortDescFirstColumn = columns.find((col) => col.sortDescFirst);
28+
if (sortDescFirstColumn) {
29+
const columnId =
30+
"id" in sortDescFirstColumn
31+
? sortDescFirstColumn.id
32+
: "accessorKey" in sortDescFirstColumn
33+
? sortDescFirstColumn.accessorKey
34+
: "accessorFn" in sortDescFirstColumn
35+
? (sortDescFirstColumn.header as string)
36+
: Object.keys(sortDescFirstColumn)[0];
37+
38+
sorting = [
39+
{
40+
id: columnId as string,
41+
desc: true,
42+
},
43+
];
44+
}
45+
2646
$: safeData = Array.isArray(data) ? data : [];
2747
$: {
2848
if (safeData) {
@@ -50,14 +70,15 @@
5070
};
5171
5272
const options = writable<TableOptions<any>>({
53-
data: data,
73+
data: safeData,
5474
columns: columns,
5575
state: {
5676
sorting,
5777
},
5878
onSortingChange: setSorting,
5979
getCoreRowModel: getCoreRowModel(),
6080
getSortedRowModel: getSortedRowModel(),
81+
enableSortingRemoval: false,
6182
});
6283
6384
const table = createSvelteTable(options);
@@ -92,13 +113,12 @@
92113
)}
93114
/>
94115
</span>
95-
{#if header.column.getIsSorted().toString() === "asc"}
96-
<span>
97-
<ArrowDown flip size="12px" />
98-
</span>
99-
{:else if header.column.getIsSorted().toString() === "desc"}
116+
{#if header.column.getIsSorted()}
100117
<span>
101-
<ArrowDown size="12px" />
118+
<ArrowDown
119+
flip={header.column.getIsSorted().toString() === "asc"}
120+
size="12px"
121+
/>
102122
</span>
103123
{/if}
104124
{/if}

0 commit comments

Comments
 (0)