diff --git a/app/page.tsx b/app/page.tsx
index 46d4364..a6e2e43 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,503 +1,10 @@
-import { Badge } from "@/components/ui/badge";
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-import { Button } from "@/components/ui/button";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
-import {
- DropdownMenu,
- DropdownMenuCheckboxItem,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { Input } from "@/components/ui/input";
-import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import {
- File,
- Home,
- LineChart,
- ListFilter,
- MoreHorizontal,
- Package,
- Package2,
- PanelLeft,
- PlusCircle,
- Search,
- ShoppingCart,
- Users2,
-} from "lucide-react";
-import Link from "next/link";
+import { TxsTable } from "@/components/txs-data";
-export default function Dashboard() {
+export default async function Txs() {
return (
-
-
-
-
-
-
-
- All
- Active
- Draft
-
- Archived
-
-
-
-
-
-
-
-
- Filter by
-
-
- Active
-
- Draft
-
- Archived
-
-
-
-
-
-
-
-
-
-
- Products
-
- Manage your products and view their sales performance.
-
-
-
-
-
-
-
- Image
-
- Name
- Status
- Price
-
- Total Sales
-
-
- Created at
-
-
- Actions
-
-
-
-
-
-
-
-
-
- Laser Lemonade Machine
-
-
- Draft
-
- $499.99
-
- 25
-
-
- 2023-07-12 10:42 AM
-
-
-
-
-
-
-
- Actions
- Edit
- Delete
-
-
-
-
-
-
-
-
-
- Hypernova Headphones
-
-
- Active
-
- $129.99
-
- 100
-
-
- 2023-10-18 03:21 PM
-
-
-
-
-
-
-
- Actions
- Edit
- Delete
-
-
-
-
-
-
-
-
-
- AeroGlow Desk Lamp
-
-
- Active
-
- $39.99
-
- 50
-
-
- 2023-11-29 08:15 AM
-
-
-
-
-
-
-
- Actions
- Edit
- Delete
-
-
-
-
-
-
-
-
-
- TechTonic Energy Drink
-
-
- Draft
-
- $2.99
-
- 0
-
-
- 2023-12-25 11:59 PM
-
-
-
-
-
-
-
- Actions
- Edit
- Delete
-
-
-
-
-
-
-
-
-
- Gamer Gear Pro Controller
-
-
- Active
-
- $59.99
-
- 75
-
-
- 2024-01-01 12:00 AM
-
-
-
-
-
-
-
- Actions
- Edit
- Delete
-
-
-
-
-
-
-
-
-
- Luminous VR Headset
-
-
- Active
-
- $199.99
-
- 30
-
-
- 2024-02-14 02:14 PM
-
-
-
-
-
-
-
- Actions
- Edit
- Delete
-
-
-
-
-
-
-
-
-
- Showing 1-10 of 32{" "}
- products
-
-
-
-
-
-
-
+
+
CosmWasm Tracing UI
+
);
}
diff --git a/app/txs/page.tsx b/app/txs/page.tsx
index f705c74..54e967e 100644
--- a/app/txs/page.tsx
+++ b/app/txs/page.tsx
@@ -1,4 +1,4 @@
-import TxsData from "@/components/txs-data";
+import TxsData from "@/components/txs/txs-data";
export default function Txs() {
return (
diff --git a/components/data-table/data-table-column-header.tsx b/components/data-table/data-table-column-header.tsx
new file mode 100644
index 0000000..54881d8
--- /dev/null
+++ b/components/data-table/data-table-column-header.tsx
@@ -0,0 +1,70 @@
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { cn } from "@/lib/utils";
+import {
+ ArrowDownIcon,
+ ArrowUpIcon,
+ CaretSortIcon,
+ EyeNoneIcon,
+} from "@radix-ui/react-icons";
+import { Column } from "@tanstack/react-table";
+
+interface DataTableColumnHeaderProps
+ extends React.HTMLAttributes {
+ column: Column;
+ title: string;
+}
+
+export function DataTableColumnHeader({
+ column,
+ title,
+ className,
+}: DataTableColumnHeaderProps) {
+ if (!column.getCanSort()) {
+ return {title}
;
+ }
+
+ return (
+
+
+
+
+
+
+ column.toggleSorting(false)}>
+
+ Asc
+
+ column.toggleSorting(true)}>
+
+ Desc
+
+
+ column.toggleVisibility(false)}>
+
+ Hide
+
+
+
+
+ );
+}
diff --git a/components/data-table/data-table-faceted-filter.tsx b/components/data-table/data-table-faceted-filter.tsx
new file mode 100644
index 0000000..4fc1abf
--- /dev/null
+++ b/components/data-table/data-table-faceted-filter.tsx
@@ -0,0 +1,145 @@
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+} from "@/components/ui/command";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { Separator } from "@/components/ui/separator";
+import { cn } from "@/lib/utils";
+import { CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons";
+import { Column } from "@tanstack/react-table";
+
+interface DataTableFacetedFilterProps {
+ column?: Column;
+ title?: string;
+ options: {
+ label: string;
+ value: string;
+ icon?: React.ComponentType<{ className?: string }>;
+ }[];
+}
+
+export function DataTableFacetedFilter({
+ column,
+ title,
+ options,
+}: DataTableFacetedFilterProps) {
+ const facets = column?.getFacetedUniqueValues();
+ const selectedValues = new Set(column?.getFilterValue() as string[]);
+
+ return (
+
+
+
+
+
+
+
+
+ No results found.
+
+ {options.map((option) => {
+ const isSelected = selectedValues.has(option.value);
+ return (
+ {
+ if (isSelected) {
+ selectedValues.delete(option.value);
+ } else {
+ selectedValues.add(option.value);
+ }
+ const filterValues = Array.from(selectedValues);
+ column?.setFilterValue(
+ filterValues.length ? filterValues : undefined,
+ );
+ }}
+ >
+
+
+
+ {option.icon && (
+
+ )}
+ {option.label}
+ {facets?.get(option.value) && (
+
+ {facets.get(option.value)}
+
+ )}
+
+ );
+ })}
+
+ {selectedValues.size > 0 && (
+ <>
+
+
+ column?.setFilterValue(undefined)}
+ className="justify-center text-center"
+ >
+ Clear filters
+
+
+ >
+ )}
+
+
+
+
+ );
+}
diff --git a/components/data-table/data-table-pagination.tsx b/components/data-table/data-table-pagination.tsx
new file mode 100644
index 0000000..ac05079
--- /dev/null
+++ b/components/data-table/data-table-pagination.tsx
@@ -0,0 +1,96 @@
+import { Button } from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ DoubleArrowLeftIcon,
+ DoubleArrowRightIcon,
+} from "@radix-ui/react-icons";
+import { Table } from "@tanstack/react-table";
+
+interface DataTablePaginationProps {
+ table: Table;
+}
+
+export function DataTablePagination({
+ table,
+}: DataTablePaginationProps) {
+ return (
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of{" "}
+ {table.getFilteredRowModel().rows.length} row(s) selected.
+
+
+
+
Rows per page
+
+
+
+ Page {table.getState().pagination.pageIndex + 1} of{" "}
+ {table.getPageCount()}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/data-table/data-table-row.tsx b/components/data-table/data-table-row.tsx
new file mode 100644
index 0000000..697e2e3
--- /dev/null
+++ b/components/data-table/data-table-row.tsx
@@ -0,0 +1,39 @@
+import { TableCell, TableRow } from "@/components/ui/table";
+import { cn } from "@/lib/utils";
+import { Row, flexRender } from "@tanstack/react-table";
+import { useRouter } from "next/navigation";
+
+export type RowLink = {
+ url: string;
+ field: string;
+};
+
+interface DataTableRowProps {
+ row: Row;
+ rowLink?: RowLink;
+}
+
+export function DataTableRow({
+ row,
+ rowLink,
+}: DataTableRowProps) {
+ const router = useRouter();
+
+ return (
+ router.push(`${rowLink.url}${row.getValue(rowLink.field)}`)
+ : undefined
+ }
+ >
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+
+ ))}
+
+ );
+}
diff --git a/components/data-table/data-table-view-options.tsx b/components/data-table/data-table-view-options.tsx
new file mode 100644
index 0000000..73d1ec1
--- /dev/null
+++ b/components/data-table/data-table-view-options.tsx
@@ -0,0 +1,56 @@
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuCheckboxItem,
+ DropdownMenuContent,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { MixerHorizontalIcon } from "@radix-ui/react-icons";
+import { Table } from "@tanstack/react-table";
+
+interface DataTableViewOptionsProps {
+ table: Table;
+}
+
+export function DataTableViewOptions({
+ table,
+}: DataTableViewOptionsProps) {
+ return (
+
+
+
+
+
+ Toggle columns
+
+ {table
+ .getAllColumns()
+ .filter(
+ (column) =>
+ typeof column.accessorFn !== "undefined" && column.getCanHide(),
+ )
+ .map((column) => {
+ return (
+ column.toggleVisibility(!!value)}
+ >
+ {column.id}
+
+ );
+ })}
+
+
+ );
+}
diff --git a/components/data-table/index.tsx b/components/data-table/index.tsx
new file mode 100644
index 0000000..ad95023
--- /dev/null
+++ b/components/data-table/index.tsx
@@ -0,0 +1,58 @@
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { Table as ReactTable, flexRender } from "@tanstack/react-table";
+import { DataTableRow, RowLink } from "./data-table-row";
+
+interface DataTableProps {
+ table: ReactTable;
+ rowLink?: RowLink;
+}
+
+export function DataTable({ table, rowLink }: DataTableProps) {
+ return (
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ );
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table
+ .getRowModel()
+ .rows.map((row) => (
+
+ ))
+ ) : (
+
+
+ No results.
+
+
+ )}
+
+
+ );
+}
diff --git a/components/txs-data.tsx b/components/txs-data.tsx
deleted file mode 100644
index 20b4cc5..0000000
--- a/components/txs-data.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-"use client";
-
-import { useTxs } from "@/hooks/api";
-import { Tx } from "@/types/txs";
-import Link from "next/link";
-
-export default function TxsData() {
- const {
- isPending,
- error,
- data: txs,
- } = useTxs("execute_tx", new Map([["tx", "*Bank(Send*"]]));
-
- if (isPending) return "Loading...";
- if (error) return "An error has occurred: " + error.message;
-
- return (
-
-
Transactions
-
- {txs.map((tx) => (
- -
-
- {tx.traceId} - {tx.operationName} -{" "}
- {
- txs.filter(
- (txToFilter: Tx) => txToFilter.traceId === tx.traceId,
- ).length
- }{" "}
- spans - {tx.tags.size} tags
-
-
- ))}
-
-
- );
-}
diff --git a/components/txs-data/index.tsx b/components/txs-data/index.tsx
new file mode 100644
index 0000000..9bb3d63
--- /dev/null
+++ b/components/txs-data/index.tsx
@@ -0,0 +1,76 @@
+"use client";
+
+import { useTxs } from "@/hooks/api";
+import { Tx } from "@/types/txs";
+import {
+ ColumnFiltersState,
+ SortingState,
+ VisibilityState,
+ getCoreRowModel,
+ getFacetedRowModel,
+ getFacetedUniqueValues,
+ getFilteredRowModel,
+ getPaginationRowModel,
+ getSortedRowModel,
+ useReactTable,
+} from "@tanstack/react-table";
+import { useState } from "react";
+import { DataTable } from "../data-table";
+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({});
+ const [columnFilters, setColumnFilters] = useState([]);
+ const [sorting, setSorting] = useState([]);
+
+ const operationNameValue = columnFilters.find(
+ (filter) => filter.id === "operationName",
+ )?.value;
+ const operationName =
+ typeof operationNameValue === "string" && operationNameValue.length
+ ? operationNameValue
+ : "execute_tx";
+
+ const {
+ isPending,
+ error,
+ data: txs,
+ } = useTxs(
+ operationName,
+ operationName === "execute_tx"
+ ? new Map([["tx", "*Bank(Send*"]])
+ : undefined,
+ );
+
+ const data = (txs ?? []) as Tx[];
+
+ const table = useReactTable({
+ columns: txsColumns,
+ data,
+ state: { columnVisibility, columnFilters, sorting },
+ onColumnVisibilityChange: setColumnVisibility,
+ onColumnFiltersChange: setColumnFilters,
+ onSortingChange: setSorting,
+ getCoreRowModel: getCoreRowModel(),
+ getFacetedRowModel: getFacetedRowModel(),
+ getFacetedUniqueValues: getFacetedUniqueValues(),
+ getFilteredRowModel: getFilteredRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ });
+
+ // if (isPending) return "Loading...";
+ // if (error) return "An error has occurred: " + error.message;
+
+ return (
+
+ );
+}
diff --git a/components/txs-data/txs-columns.tsx b/components/txs-data/txs-columns.tsx
new file mode 100644
index 0000000..c3f490f
--- /dev/null
+++ b/components/txs-data/txs-columns.tsx
@@ -0,0 +1,41 @@
+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,
+ },
+];
diff --git a/components/txs-data/txs-table-toolbar.tsx b/components/txs-data/txs-table-toolbar.tsx
new file mode 100644
index 0000000..fc53640
--- /dev/null
+++ b/components/txs-data/txs-table-toolbar.tsx
@@ -0,0 +1,51 @@
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Cross2Icon } from "@radix-ui/react-icons";
+import { Table } from "@tanstack/react-table";
+import { DataTableFacetedFilter } from "../data-table/data-table-faceted-filter";
+import { DataTableViewOptions } from "../data-table/data-table-view-options";
+
+interface DataTableToolbarProps {
+ table: Table;
+}
+
+export function DataTableToolbar({
+ table,
+}: DataTableToolbarProps) {
+ const isFiltered = table.getState().columnFilters.length > 0;
+
+ return (
+
+
+
+ table.getColumn("operationName")?.setFilterValue(event.target.value)
+ }
+ className="h-8 w-[150px] lg:w-[250px]"
+ />
+ {table.getColumn("priority") && (
+
+ )}
+ {isFiltered && (
+
+ )}
+
+
+
+ );
+}