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 ( -
-
-
- - - - - - - - - - - - - Dashboard - - - - - - Products - - - - - All Products - - - -
- - -
- - - - - - - - - My Account - - Settings - Support - - Logout - - -
-
- -
- - 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 && ( + + )} +
+ +
+ ); +}