From 2d37ca8fb59cc30d4fb96afc68101c9f26008864 Mon Sep 17 00:00:00 2001 From: Trezy Date: Tue, 17 Feb 2026 15:00:28 +0000 Subject: [PATCH] feat: add column visibility and better scrolling to records table --- web/src/components/data-table/data-table-toolbar.tsx | 5 +---- web/src/components/data-table/data-table-view-options.tsx | 16 ++++++---------- web/src/components/data-table/data-table.tsx | 24 ++++++++++++++++++------ web/src/components/ui/sidebar.tsx | 2 +- web/src/app/(dashboard)/records/page.tsx | 282 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------------------------------------------------------------------------------------------------- 5 file(s) changed, 149 insertion(s)(+), 180 deletion(s)(-) diff --git a/web/src/components/data-table/data-table-toolbar.tsx b/web/src/components/data-table/data-table-toolbar.tsx --- a/web/src/components/data-table/data-table-toolbar.tsx +++ b/web/src/components/data-table/data-table-toolbar.tsx @@ -25,10 +25,7 @@ }: DataTableToolbarProps) { const isFiltered = table.getState().columnFilters.length > 0; - const columns = React.useMemo( - () => table.getAllColumns().filter((column) => column.getCanFilter()), - [table], - ); + const columns = table.getAllColumns().filter((column) => column.getCanFilter()); const onReset = React.useCallback(() => { table.resetColumnFilters(); diff --git a/web/src/components/data-table/data-table-view-options.tsx b/web/src/components/data-table/data-table-view-options.tsx --- a/web/src/components/data-table/data-table-view-options.tsx +++ b/web/src/components/data-table/data-table-view-options.tsx @@ -31,16 +31,12 @@ disabled, ...props }: DataTableViewOptionsProps) { - const columns = React.useMemo( - () => - table - .getAllColumns() - .filter( - (column) => - typeof column.accessorFn !== "undefined" && column.getCanHide(), - ), - [table], - ); + const columns = table + .getAllColumns() + .filter( + (column) => + typeof column.accessorFn !== "undefined" && column.getCanHide(), + ); return ( diff --git a/web/src/components/data-table/data-table.tsx b/web/src/components/data-table/data-table.tsx --- a/web/src/components/data-table/data-table.tsx +++ b/web/src/components/data-table/data-table.tsx @@ -18,11 +18,15 @@ interface DataTableProps extends React.ComponentProps<"div"> { table: TanstackTable; actionBar?: React.ReactNode; + showPagination?: boolean; + onRowClick?: (row: TData) => void; } export function DataTable({ table, actionBar, + showPagination = true, + onRowClick, children, className, ...props @@ -63,6 +67,12 @@ onRowClick(row.original) + : undefined + } > {row.getVisibleCells().map((cell) => ( -
- - {actionBar && - table.getFilteredSelectedRowModel().rows.length > 0 && - actionBar} -
+ {showPagination && ( +
+ + {actionBar && + table.getFilteredSelectedRowModel().rows.length > 0 && + actionBar} +
+ )} ); } diff --git a/web/src/components/ui/sidebar.tsx b/web/src/components/ui/sidebar.tsx --- a/web/src/components/ui/sidebar.tsx +++ b/web/src/components/ui/sidebar.tsx @@ -309,7 +309,7 @@
([]) - const [selectedCollection, setSelectedCollection] = useState("") - const [records, setRecords] = useState([]) - const [cursorStack, setCursorStack] = useState([]) - const [nextCursor, setNextCursor] = useState() - const [loading, setLoading] = useState(false) - const [error, setError] = useState(null) - const [viewRecord, setViewRecord] = useState(null) + const { getToken } = useAuth(); + const [collections, setCollections] = useState([]); + const [selectedCollection, setSelectedCollection] = useState(""); + const [records, setRecords] = useState([]); + const [cursorStack, setCursorStack] = useState([]); + const [nextCursor, setNextCursor] = useState(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [viewRecord, setViewRecord] = useState(null); + + const [columnVisibility, setColumnVisibility] = useState({}); useEffect(() => { getStats(getToken) .then((stats) => setCollections(stats.collections)) - .catch((e) => setError(e.message)) - }, [getToken]) + .catch((e) => setError(e.message)); + }, [getToken]); const fetchRecords = useCallback( async (collection: string, cursor?: string) => { - setLoading(true) - setError(null) + setLoading(true); + setError(null); try { - const data = await getAdminRecords(getToken, collection, 20, cursor) - setRecords(data.records) - setNextCursor(data.cursor) + const data = await getAdminRecords(getToken, collection, 20, cursor); + setRecords(data.records); + setNextCursor(data.cursor); } catch (e: unknown) { - setError(e instanceof Error ? e.message : String(e)) - setRecords([]) - setNextCursor(undefined) + setError(e instanceof Error ? e.message : String(e)); + setRecords([]); + setNextCursor(undefined); } finally { - setLoading(false) + setLoading(false); } }, - [getToken] - ) + [getToken], + ); // Build columns dynamically from the union of all record keys const columns = useMemo[]>(() => { - const keySet = new Set() + const keySet = new Set(); for (const r of records) { for (const key of Object.keys(r.record)) { - keySet.add(key) + keySet.add(key); } } const cols: ColumnDef[] = [ { id: "did", - header: "DID", accessorFn: (row) => parseAtUri(row.uri).did, + header: "DID", cell: ({ getValue }) => ( {getValue()} ), + enableSorting: false, + enableHiding: false, + meta: { label: "DID" }, }, { id: "rkey", - header: "Rkey", accessorFn: (row) => parseAtUri(row.uri).rkey, + header: "Record Key", cell: ({ getValue }) => ( {getValue()} ), + enableSorting: false, + enableHiding: false, + meta: { label: "Record Key" }, }, - ] + ]; for (const key of keySet) { cols.push({ id: key, - header: key, accessorFn: (row) => row.record[key], + header: key, + enableSorting: false, cell: ({ getValue }) => { - const val = getValue() - const str = formatCellValue(val) + const val = getValue(); + const str = formatCellValue(val); return ( {str} - ) + ); }, - }) + meta: { label: key }, + }); } - return cols - }, [records]) + return cols; + }, [records]); const table = useReactTable({ data: records, columns, + state: { + columnVisibility, + }, + onColumnVisibilityChange: setColumnVisibility, getCoreRowModel: getCoreRowModel(), getRowId: (row) => row.uri, - }) + }); function handleSelectCollection(collection: string) { - setSelectedCollection(collection) - setCursorStack([]) - setNextCursor(undefined) - fetchRecords(collection) + setSelectedCollection(collection); + setCursorStack([]); + setNextCursor(undefined); + setColumnVisibility({}); + fetchRecords(collection); } function handleNext() { - if (!nextCursor || !selectedCollection) return - setCursorStack((prev) => [...prev, nextCursor]) - fetchRecords(selectedCollection, nextCursor) + if (!nextCursor || !selectedCollection) return; + setCursorStack((prev) => [...prev, nextCursor]); + fetchRecords(selectedCollection, nextCursor); } function handlePrevious() { - if (cursorStack.length === 0 || !selectedCollection) return - const stack = [...cursorStack] - stack.pop() - const prevCursor = stack.length > 0 ? stack[stack.length - 1] : undefined - setCursorStack(stack) - fetchRecords(selectedCollection, prevCursor) + if (cursorStack.length === 0 || !selectedCollection) return; + const stack = [...cursorStack]; + stack.pop(); + const prevCursor = stack.length > 0 ? stack[stack.length - 1] : undefined; + setCursorStack(stack); + fetchRecords(selectedCollection, prevCursor); } return ( @@ -178,105 +188,59 @@
{error &&

{error}

} -
- -
+ +
+ + +
+
{selectedCollection && ( - <> -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ))} - - ))} - - - {loading && ( - - - Loading... - - - )} - {!loading && table.getRowModel().rows.length === 0 && ( - - - No records found. - - - )} - {!loading && - table.getRowModel().rows.map((row) => ( - setViewRecord(row.original)} - > - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - ))} - -
-
- -
+
+

+ {records.length} record(s) on this page. +

+
- +
)} {viewRecord && ( @@ -295,5 +259,5 @@ )}
- ) + ); } -- tangled.sh