From 7f4a70309cd1b00a561f700492fae0706038579d Mon Sep 17 00:00:00 2001 From: Boki Date: Wed, 18 Jun 2025 07:54:42 -0400 Subject: [PATCH] improved datatable --- .../src/components/ui/DataTable/DataTable.tsx | 181 +++++++++--------- .../exchanges/components/ExchangesTable.tsx | 131 +++++-------- 2 files changed, 137 insertions(+), 175 deletions(-) diff --git a/apps/web-app/src/components/ui/DataTable/DataTable.tsx b/apps/web-app/src/components/ui/DataTable/DataTable.tsx index 288f3a2..9aef247 100644 --- a/apps/web-app/src/components/ui/DataTable/DataTable.tsx +++ b/apps/web-app/src/components/ui/DataTable/DataTable.tsx @@ -4,29 +4,30 @@ import { ColumnDef, flexRender, getCoreRowModel, + getExpandedRowModel, getSortedRowModel, + Row, SortingState, useReactTable, } from '@tanstack/react-table'; -import { useState } from 'react'; -import { TableVirtuoso } from 'react-virtuoso'; +import { Fragment, useState } from 'react'; interface DataTableProps { data: T[]; columns: ColumnDef[]; - height?: number; loading?: boolean; - onRowClick?: (row: T) => void; className?: string; + getRowCanExpand?: (row: Row) => boolean; + renderSubComponent?: (props: { row: Row }) => React.ReactElement; } export function DataTable({ data, columns, - height, loading = false, - onRowClick, className = '', + getRowCanExpand, + renderSubComponent, }: DataTableProps) { const [sorting, setSorting] = useState([]); @@ -39,11 +40,12 @@ export function DataTable({ onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), - enableSorting: true, + getExpandedRowModel: getExpandedRowModel(), + getRowCanExpand, + enableColumnResizing: true, + columnResizeMode: 'onChange', }); - const { rows } = table.getRowModel(); - if (loading) { return (
@@ -53,87 +55,88 @@ export function DataTable({ } return ( - ( - - ), - TableRow: props => { - const index = props['data-index'] as number; - const row = rows[index]; - - return ( - onRowClick?.(row.original)} - > - {row.getVisibleCells().map(cell => ( -
+ + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + - ); - }, - }} - fixedHeaderContent={() => - table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(header => ( - - ))} - - )) - } - /> + {/* Column resizer */} + {header.column.getCanResize() && ( +
+ )} + + )} + + ))} + + ))} +
+ + {table.getRowModel().rows.map(row => ( + + + {row.getVisibleCells().map(cell => ( + + ))} + + {row.getIsExpanded() && renderSubComponent && ( + + + + )} + + ))} + +
-
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
- - ))} -
- {header.isPlaceholder ? null : ( -
- - {flexRender(header.column.columnDef.header, header.getContext())} - - {header.column.getCanSort() && ( -
- {header.column.getIsSorted() === 'asc' ? ( - - ) : header.column.getIsSorted() === 'desc' ? ( - - ) : ( -
+ {header.isPlaceholder ? null : ( + <> +
+ + {flexRender(header.column.columnDef.header, header.getContext())} + + {header.column.getCanSort() && ( +
+ {header.column.getIsSorted() === 'asc' ? ( + + ) : header.column.getIsSorted() === 'desc' ? ( + + ) : ( +
+ )} +
)}
- )} -
- )} -
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ {renderSubComponent({ row })} +
+ ); -} +} \ No newline at end of file diff --git a/apps/web-app/src/features/exchanges/components/ExchangesTable.tsx b/apps/web-app/src/features/exchanges/components/ExchangesTable.tsx index f19cdec..3ea3245 100644 --- a/apps/web-app/src/features/exchanges/components/ExchangesTable.tsx +++ b/apps/web-app/src/features/exchanges/components/ExchangesTable.tsx @@ -13,20 +13,17 @@ export function ExchangesTable() { error, updateExchange, fetchExchangeDetails, - fetchProviderMappings, updateProviderMapping, createProviderMapping, refetch } = useExchanges(); - console.log('ExchangesTable render:', { exchanges, loading, error }); const [editingCell, setEditingCell] = useState<{ id: string; field: string } | null>(null); const [editValue, setEditValue] = useState(''); const [addProviderDialog, setAddProviderDialog] = useState<{ exchangeId: string; exchangeName: string; } | null>(null); - const [expandedRows, setExpandedRows] = useState>(new Set()); const [expandedRowData, setExpandedRowData] = useState>({}); const handleCellEdit = useCallback( @@ -61,59 +58,50 @@ export function ExchangesTable() { [updateProviderMapping, refetch] ); - const handleToggleExpandRow = useCallback(async (rowId: string) => { - setExpandedRows(prev => { - const next = new Set(prev); - if (next.has(rowId)) { - next.delete(rowId); - } else { - next.add(rowId); - // Load provider mappings for this exchange - if (!expandedRowData[rowId]) { - fetchExchangeDetails(rowId).then(details => { - if (details) { - setExpandedRowData(prev => ({ - ...prev, - [rowId]: details.provider_mappings - })); - } - }); + const handleRowExpand = useCallback( + async (row: any) => { + const exchangeId = row.original.id; + if (!expandedRowData[exchangeId]) { + const details = await fetchExchangeDetails(exchangeId); + if (details) { + setExpandedRowData(prev => ({ + ...prev, + [exchangeId]: details.provider_mappings + })); } } - return next; - }); - }, [fetchExchangeDetails, expandedRowData]); + }, + [fetchExchangeDetails] + ); const columns = useMemo[]>(() => { return [ { - id: 'expand', + id: 'expander', header: '', - size: 30, - enableResizing: false, cell: ({ row }) => { - const isExpanded = expandedRows.has(row.original.id); - return ( + return row.getCanExpand() ? ( - ); + ) : null; }, + size: 40, + enableResizing: false, }, { id: 'id', header: 'ID', accessorKey: 'id', - size: 50, - enableResizing: false, - cell: ({ getValue, cell }) => ( - + size: 80, + cell: ({ getValue }) => ( + {getValue() as string} ), @@ -122,13 +110,9 @@ export function ExchangesTable() { id: 'code', header: 'Code', accessorKey: 'code', - size: 80, - enableResizing: false, - cell: ({ getValue, cell }) => ( - + size: 100, + cell: ({ getValue }) => ( + {getValue() as string} ), @@ -137,9 +121,7 @@ export function ExchangesTable() { id: 'name', header: 'Name', accessorKey: 'name', - size: 200, - maxSize: 300, - enableResizing: true, + size: 250, cell: ({ getValue, row, cell }) => { const isEditing = editingCell?.id === row.original.id && editingCell?.field === 'name'; @@ -184,7 +166,6 @@ export function ExchangesTable() { header: 'Country', accessorKey: 'country', size: 80, - maxSize: 80, cell: ({ getValue }) => ( {getValue() as string} ), @@ -193,7 +174,7 @@ export function ExchangesTable() { id: 'currency', header: 'Currency', accessorKey: 'currency', - size: 70, + size: 80, cell: ({ getValue, cell }) => ( { const isActive = getValue() as boolean; return ( @@ -231,7 +211,7 @@ export function ExchangesTable() { id: 'provider_mappings', header: 'Provider Mappings', accessorKey: 'provider_mapping_count', - size: 150, + size: 180, cell: ({ getValue, row }) => { const totalMappings = parseInt(getValue() as string) || 0; const activeMappings = parseInt(row.original.active_mapping_count) || 0; @@ -265,7 +245,7 @@ export function ExchangesTable() { { id: 'actions', header: 'Actions', - size: 100, + size: 120, cell: ({ row }) => (