diff --git a/components/txs-table/index.tsx b/components/txs-table/index.tsx new file mode 100644 index 0000000..674a01c --- /dev/null +++ b/components/txs-table/index.tsx @@ -0,0 +1,96 @@ +"use client"; + +import { useTxs } from "@/hooks/api"; +import { useDebounce } from "@/hooks/use-debounce"; +import { Tx } from "@/types/txs"; +import { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { useState } from "react"; +import { DataTable } from "../data-table"; +import { DataTableColumnHeader } from "../data-table/data-table-column-header"; +import { DataTablePagination } from "../data-table/data-table-pagination"; +import { txsColumns } from "./txs-columns"; +import { DataTableToolbar } from "./txs-table-toolbar"; + +export function TxsTable() { + const [columnVisibility, setColumnVisibility] = useState({ + tags: false, + }); + const [columnFilters, setColumnFilters] = useState([]); + const [sorting, setSorting] = useState([]); + + const operationNameValue = columnFilters.find( + (filter) => filter.id === "operationName", + )?.value; + const operationName = useDebounce( + typeof operationNameValue === "string" && operationNameValue.length + ? operationNameValue + : "execute_tx", + ); + + const tags = + (columnFilters.find((filter) => filter.id === "tags")?.value as + | Map + | undefined) ?? undefined; + + const tagsColumns: ColumnDef[] = Array.from(tags?.keys() ?? []).map( + (tagKey) => ({ + accessorKey: tagKey, + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {(row.getValue("tags") as Map).get(tagKey)} +
+ ), + //NOTE - Don't UI filter, query API + filterFn: () => true, + }), + ); + + const columns = [...txsColumns, ...tagsColumns]; + + const { isPending, error, data: txs } = useTxs(operationName, tags); + const data = (txs ?? []) as Tx[]; + + const table = useReactTable({ + columns, + data, + state: { columnVisibility, columnFilters, sorting }, + onColumnVisibilityChange: setColumnVisibility, + onColumnFiltersChange: setColumnFilters, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + }); + + return ( +
+ +
+ +
+ +
+ ); +} diff --git a/components/txs-table/txs-columns.tsx b/components/txs-table/txs-columns.tsx new file mode 100644 index 0000000..e9400f3 --- /dev/null +++ b/components/txs-table/txs-columns.tsx @@ -0,0 +1,57 @@ +import { Tx } from "@/types/txs"; +import { ColumnDef } from "@tanstack/react-table"; +import { DataTableColumnHeader } from "../data-table/data-table-column-header"; + +export const txsColumns: ColumnDef[] = [ + { + accessorKey: "traceId", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
{row.getValue("traceId")}
+ ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "spanId", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
{row.getValue("spanId")}
+ ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "operationName", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {row.getValue("operationName")} +
+ ), + //NOTE - Don't UI filter, query API + filterFn: () => true, + }, + { + accessorKey: "tags", + header: ({ column }) => ( + + ), + cell: ({ row }) => ( +
+ {Array.from((row.getValue("tags") as Map).keys()).join( + ", ", + )} +
+ ), + //NOTE - Don't UI filter, query API + filterFn: () => true, + enableHiding: false, + }, +]; diff --git a/components/txs-table/txs-table-toolbar.tsx b/components/txs-table/txs-table-toolbar.tsx new file mode 100644 index 0000000..d713b29 --- /dev/null +++ b/components/txs-table/txs-table-toolbar.tsx @@ -0,0 +1,132 @@ +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Cross2Icon } from "@radix-ui/react-icons"; +import { Table } from "@tanstack/react-table"; +import { DataTableKvFilter } from "../data-table/data-table-kv-filter"; +import { DataTableViewOptions } from "../data-table/data-table-view-options"; + +const getSelectedValue = (table: Table) => { + if ( + !table.getColumn("operationName")?.getFilterValue() && + !table.getColumn("tags")?.getFilterValue() + ) { + return "empty"; + } + + if ((table.getColumn("operationName")?.getFilterValue() as string) ?? "") { + return "custom"; + } + + const tags = table.getColumn("tags")?.getFilterValue() as + | Map + | undefined; + + if (tags?.size === 1 && tags?.get("raw_tx") === "*") { + return "simulations"; + } + + if (tags?.size === 1 && tags?.get("tx_hash") === "*") { + return "broadcasted"; + } + + return "custom"; +}; + +interface DataTableToolbarProps { + table: Table; +} + +export function DataTableToolbar({ + table, +}: DataTableToolbarProps) { + return ( +
+
+ + + table.getColumn("operationName")?.setFilterValue(event.target.value) + } + className="h-8 w-[150px] lg:w-[250px]" + /> + {table.getColumn("tags") && ( + + )} + {table.getState().columnFilters.length > 0 && ( + + )} +
+ +
+ ); +} diff --git a/hooks/use-debounce.ts b/hooks/use-debounce.ts new file mode 100644 index 0000000..7091ff6 --- /dev/null +++ b/hooks/use-debounce.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from "react"; + +export function useDebounce(value: T, delay = 1000): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timeout = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(timeout); + }; + }, [value, delay]); + + return debouncedValue; +}