Skip to content

Commit 2f2c250

Browse files
authored
Restructured tables (stack-auth#330)
1 parent 6b5a528 commit 2f2c250

File tree

3 files changed

+102
-54
lines changed

3 files changed

+102
-54
lines changed

apps/dashboard/src/components/data-table/api-key-table.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,6 @@ export function ApiKeyTable(props: { apiKeys: ApiKey[] }) {
115115
data={extendedApiKeys}
116116
columns={columns}
117117
toolbarRender={toolbarRender}
118-
defaultFilters={[{ id: 'status', value: ['valid'] }]}
118+
defaultColumnFilters={[{ id: 'status', value: ['valid'] }]}
119119
/>;
120120
}

apps/dashboard/src/components/data-table/user-table.tsx

Lines changed: 23 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
import { useAdminApp } from '@/app/(main)/(protected)/projects/[projectId]/use-admin-app';
33
import { ServerUser } from '@stackframe/stack';
44
import { deindent } from '@stackframe/stack-shared/dist/utils/strings';
5-
import { ActionCell, ActionDialog, AvatarCell, BadgeCell, CopyField, DataTableColumnHeader, DataTableManual, DateCell, SearchToolbarItem, SimpleTooltip, TextCell, Typography } from "@stackframe/stack-ui";
5+
import { ActionCell, ActionDialog, AvatarCell, BadgeCell, CopyField, DataTableColumnHeader, DataTableManualPagination, DateCell, SearchToolbarItem, SimpleTooltip, TextCell, Typography } from "@stackframe/stack-ui";
66
import { ColumnDef, ColumnFiltersState, Row, SortingState, Table } from "@tanstack/react-table";
7-
import React, { useEffect, useState } from "react";
7+
import { useState } from "react";
88
import { UserDialog } from '../user-dialog';
99

1010
export type ExtendedServerUser = ServerUser & {
@@ -185,63 +185,40 @@ export function extendUsers(users: ServerUser[]): ExtendedServerUser[] {
185185
export function UserTable() {
186186
const stackAdminApp = useAdminApp();
187187
const [users, setUsers] = useState<ExtendedServerUser[]>([]);
188-
const [sorting, setSorting] = React.useState<SortingState>([{id: 'signedUpAt', desc: true}]);
189-
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
190-
const [cursors, setCursors] = useState<Record<number, string>>({});
191-
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
192-
const [globalFilter, setGlobalFilter] = useState<any>();
193-
const [refreshCounter, setRefreshCounter] = useState(0);
194188

195-
useEffect(() => {
189+
const onUpdate = async (options: {
190+
cursor: string,
191+
limit: number,
192+
sorting: SortingState,
193+
columnFilters: ColumnFiltersState,
194+
globalFilters: any,
195+
}) => {
196196
let filters: any = {};
197197

198198
const orderMap = {
199199
signedUpAt: "signedUpAt",
200200
} as const;
201-
if (sorting.length > 0 && sorting[0].id in orderMap) {
202-
filters.orderBy = orderMap[sorting[0].id as keyof typeof orderMap];
203-
filters.desc = sorting[0].desc;
201+
if (options.sorting.length > 0 && options.sorting[0].id in orderMap) {
202+
filters.orderBy = orderMap[options.sorting[0].id as keyof typeof orderMap];
203+
filters.desc = options.sorting[0].desc;
204204
}
205205

206-
stackAdminApp.listUsers({
207-
cursor: cursors[pagination.pageIndex],
208-
limit: pagination.pageSize,
209-
query: globalFilter,
206+
const users = await stackAdminApp.listUsers({
207+
cursor: options.cursor,
208+
limit: options.limit,
209+
query: options.globalFilters,
210210
...filters,
211-
}).then((users) => {
212-
setUsers(extendUsers(users));
213-
setCursors(c => users.nextCursor ? { ...c, [pagination.pageIndex + 1]: users.nextCursor } : c);
214-
}).catch(console.error);
215-
// eslint-disable-next-line react-hooks/exhaustive-deps
216-
}, [pagination, stackAdminApp, sorting, columnFilters, refreshCounter]);
211+
});
212+
setUsers(extendUsers(users));
213+
return { nextCursor: users.nextCursor };
214+
};
217215

218-
// Reset to first page when filters change
219-
useEffect(() => {
220-
setPagination(pagination => ({ ...pagination, pageIndex: 0 }));
221-
setCursors({});
222-
}, [columnFilters, sorting, pagination.pageSize]);
223-
224-
// Refresh the users when the global filter changes. Delay to prevent unnecessary re-renders.
225-
useEffect(() => {
226-
const timer = setTimeout(() => {
227-
setRefreshCounter(x => x + 1);
228-
}, 500);
229-
return () => clearTimeout(timer);
230-
}, [globalFilter]);
231-
232-
return <DataTableManual
216+
return <DataTableManualPagination
233217
columns={columns}
234218
data={users}
235219
toolbarRender={userToolbarRender}
236-
sorting={sorting}
237-
setSorting={setSorting}
238-
pagination={pagination}
239-
setPagination={setPagination}
240-
columnFilters={columnFilters}
241-
setColumnFilters={setColumnFilters}
220+
onUpdate={onUpdate}
242221
defaultVisibility={{ emailVerified: false }}
243-
rowCount={pagination.pageSize * Object.keys(cursors).length + (cursors[pagination.pageIndex + 1] ? 1 : 0)}
244-
globalFilter={globalFilter}
245-
setGlobalFilter={setGlobalFilter}
222+
defaultSorting={[{ id: 'signedUpAt', desc: true }]}
246223
/>;
247224
}

packages/stack-ui/src/components/data-table/data-table.tsx

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ type DataTableProps<TData, TValue> = {
9898
data: TData[],
9999
toolbarRender?: (table: TableType<TData>) => React.ReactNode,
100100
defaultVisibility?: VisibilityState,
101-
defaultFilters?: ColumnFiltersState,
101+
defaultColumnFilters?: ColumnFiltersState,
102102
defaultSorting?: SortingState,
103103
}
104104

@@ -107,18 +107,18 @@ export function DataTable<TData, TValue>({
107107
data,
108108
toolbarRender,
109109
defaultVisibility,
110-
defaultFilters,
110+
defaultColumnFilters,
111111
defaultSorting,
112112
}: DataTableProps<TData, TValue>) {
113113
const [sorting, setSorting] = React.useState<SortingState>(defaultSorting || []);
114-
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(defaultFilters || []);
114+
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(defaultColumnFilters || []);
115115
const [pagination, setPagination] = React.useState<PaginationState>({
116116
pageIndex: 0,
117117
pageSize: 10,
118118
});
119119
const [globalFilter, setGlobalFilter] = React.useState<any>();
120120

121-
return <DataTableManual
121+
return <DataTableBase
122122
columns={columns}
123123
data={data}
124124
toolbarRender={toolbarRender}
@@ -136,7 +136,78 @@ export function DataTable<TData, TValue>({
136136
/>;
137137
}
138138

139-
type DataTableServerProps<TData, TValue> = DataTableProps<TData, TValue> & {
139+
type DataTableManualPaginationProps<TData, TValue> = DataTableProps<TData, TValue> & {
140+
onUpdate: (options: {
141+
cursor: string,
142+
limit: number,
143+
sorting: SortingState,
144+
columnFilters: ColumnFiltersState,
145+
globalFilters: any,
146+
}) => Promise<{ nextCursor: string | null }>,
147+
}
148+
149+
export function DataTableManualPagination<TData, TValue>({
150+
columns,
151+
data,
152+
toolbarRender,
153+
defaultVisibility,
154+
defaultColumnFilters,
155+
defaultSorting,
156+
onUpdate,
157+
}: DataTableManualPaginationProps<TData, TValue>) {
158+
const [sorting, setSorting] = React.useState<SortingState>(defaultSorting || []);
159+
const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 10 });
160+
const [cursors, setCursors] = React.useState<Record<number, string>>({});
161+
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(defaultColumnFilters || []);
162+
const [globalFilter, setGlobalFilter] = React.useState<any>();
163+
const [refreshCounter, setRefreshCounter] = React.useState(0);
164+
165+
React.useEffect(() => {
166+
onUpdate({
167+
cursor: cursors[pagination.pageIndex],
168+
limit: pagination.pageSize,
169+
sorting,
170+
columnFilters,
171+
globalFilters: globalFilter,
172+
}).then(({ nextCursor }) => {
173+
setCursors(c => nextCursor ? { ...c, [pagination.pageIndex + 1]: nextCursor } : c);
174+
}).catch(console.error);
175+
}, [pagination, sorting, columnFilters, refreshCounter]);
176+
177+
// Reset to first page when filters change
178+
React.useEffect(() => {
179+
setPagination(pagination => ({ ...pagination, pageIndex: 0 }));
180+
setCursors({});
181+
}, [columnFilters, sorting, pagination.pageSize]);
182+
183+
// Refresh the users when the global filter changes. Delay to prevent unnecessary re-renders.
184+
React.useEffect(() => {
185+
const timer = setTimeout(() => {
186+
setRefreshCounter(x => x + 1);
187+
}, 500);
188+
return () => clearTimeout(timer);
189+
}, [globalFilter]);
190+
191+
return <DataTableBase
192+
columns={columns}
193+
data={data}
194+
toolbarRender={toolbarRender}
195+
sorting={sorting}
196+
setSorting={setSorting}
197+
pagination={pagination}
198+
setPagination={setPagination}
199+
columnFilters={columnFilters}
200+
setColumnFilters={setColumnFilters}
201+
rowCount={pagination.pageSize * Object.keys(cursors).length + (cursors[pagination.pageIndex + 1] ? 1 : 0)}
202+
globalFilter={globalFilter}
203+
setGlobalFilter={setGlobalFilter}
204+
defaultColumnFilters={defaultColumnFilters}
205+
defaultSorting={defaultSorting}
206+
defaultVisibility={defaultVisibility}
207+
/>;
208+
}
209+
210+
type DataTableBaseProps<TData, TValue> = DataTableProps<TData, TValue> & {
140211
sorting?: SortingState,
141212
setSorting?: OnChangeFn<SortingState>,
142213
pagination?: PaginationState,
@@ -150,7 +221,7 @@ type DataTableServerProps<TData, TValue> = DataTableProps<TData, TValue> & {
150221
setGlobalFilter?: OnChangeFn<any>,
151222
}
152223

153-
export function DataTableManual<TData, TValue>({
224+
function DataTableBase<TData, TValue>({
154225
columns,
155226
data,
156227
toolbarRender,
@@ -166,7 +237,7 @@ export function DataTableManual<TData, TValue>({
166237
setGlobalFilter,
167238
manualPagination = true,
168239
manualFiltering = true,
169-
}: DataTableServerProps<TData, TValue>) {
240+
}: DataTableBaseProps<TData, TValue>) {
170241
const [rowSelection, setRowSelection] = React.useState({});
171242
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(defaultVisibility || {});
172243

0 commit comments

Comments
 (0)