From f8fa68bf3da57bc647270a9d205c5862e69f0edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Mon, 17 Aug 2026 10:33:09 +0200 Subject: [PATCH] refactor: Table component to TanStack v9 Closes: https://github.com/cheminfo/nmrium/issues/1889 --- package-lock.json | 104 ++--- package.json | 5 +- .../ReactTable/Elements/ReactTableHeader.tsx | 81 ---- .../ReactTable/utility/ReactTableContext.ts | 14 - .../ReactTable/utility/addCustomColumn.tsx | 75 ---- .../elements/ReactTable/utility/useRowSpan.ts | 61 --- .../tanstack_table/create_action_column.tsx | 43 ++ .../tanstack_table/create_column_helper.ts | 11 + .../elements/base_react_table.tsx} | 0 .../elements/empty_data_row.tsx} | 0 .../tanstack_table/elements/table_context.ts | 15 + .../tanstack_table/elements/table_header.tsx | 111 ++++++ .../elements/table_row.tsx} | 93 ++--- .../elements/tanstack_table/features.ts | 32 ++ .../elements/tanstack_table/index.ts | 5 + .../table.tsx} | 230 ++++------- .../elements/tanstack_table/types.ts | 86 ++++ .../modal/ImportPublicationStringModal.tsx | 33 +- src/component/modal/LogsHistoryModal.tsx | 56 +-- .../signal-tabs/SignalJCouplingsTable.tsx | 52 +-- .../signal-tabs/SignalPeaksTable.tsx | 52 +-- .../metaImportation/MetaImportationModal.tsx | 21 +- .../AutomaticAssignmentTable.tsx | 52 +-- .../panels/IntegralsPanel/IntegralTable.tsx | 164 ++++---- .../ExclusionsZonesTable.tsx | 128 +++--- .../panels/PeaksPanel/PeaksTable.tsx | 250 ++++++------ src/component/panels/PeaksPanel/peaksToTSV.ts | 29 +- .../panels/RangesPanel/RangesTable.tsx | 5 +- .../panels/RangesPanel/RangesTableRow.tsx | 4 +- .../panels/SpectraPanel/SpectraTable.tsx | 94 +++-- .../base/SpectraColumnsManager.tsx | 66 ++-- .../SummaryPanel/SetShiftTolerancesModal.tsx | 45 ++- .../panels/ZonesPanel/ZonesTableRow.tsx | 4 +- .../panels/databasePanel/DatabaseTable.tsx | 369 +++++++++--------- .../Filters/ExclusionZonesOptionsPanel.tsx | 40 +- .../InformationEditionModal.tsx | 77 ++-- .../MultipleSpectraAnalysisTable.tsx | 47 ++- .../preferences/AnalysisTablePreferences.tsx | 57 +-- .../preferences/LegendsPreferences.tsx | 65 +-- .../spectrumSimulation/SpinSystemTable.tsx | 37 +- test-e2e/panels/filters.test.ts | 8 +- test-e2e/panels/ranges.test.ts | 10 +- test-e2e/panels/spectra.test.ts | 2 +- 43 files changed, 1371 insertions(+), 1362 deletions(-) delete mode 100644 src/component/elements/ReactTable/Elements/ReactTableHeader.tsx delete mode 100644 src/component/elements/ReactTable/utility/ReactTableContext.ts delete mode 100644 src/component/elements/ReactTable/utility/addCustomColumn.tsx delete mode 100644 src/component/elements/ReactTable/utility/useRowSpan.ts create mode 100644 src/component/elements/tanstack_table/create_action_column.tsx create mode 100644 src/component/elements/tanstack_table/create_column_helper.ts rename src/component/elements/{ReactTable/BaseReactTable.tsx => tanstack_table/elements/base_react_table.tsx} (100%) rename src/component/elements/{ReactTable/Elements/EmptyDataRow.tsx => tanstack_table/elements/empty_data_row.tsx} (100%) create mode 100644 src/component/elements/tanstack_table/elements/table_context.ts create mode 100644 src/component/elements/tanstack_table/elements/table_header.tsx rename src/component/elements/{ReactTable/Elements/ReactTableRow.tsx => tanstack_table/elements/table_row.tsx} (64%) create mode 100644 src/component/elements/tanstack_table/features.ts create mode 100644 src/component/elements/tanstack_table/index.ts rename src/component/elements/{ReactTable/ReactTable.tsx => tanstack_table/table.tsx} (69%) create mode 100644 src/component/elements/tanstack_table/types.ts diff --git a/package-lock.json b/package-lock.json index 3892151f7b..4a2cce29e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@hookform/resolvers": "^5.9.1", "@tanstack/react-form": "^1.33.5", "@tanstack/react-store": "^0.11.1", + "@tanstack/react-table": "^9.1.2", "@zakodium/nmr-types": "^0.5.24", "@zakodium/nmrium-core": "^0.7.59", "@zakodium/nmrium-core-plugins": "^0.7.79", @@ -61,7 +62,6 @@ "react-plot": "^3.1.2", "react-rnd": "^10.5.3", "react-science": "^23.0.1", - "react-table": "^7.8.0", "smart-array-filter": "^5.0.0", "ts-pattern": "^5.9.0", "usehooks-ts": "^3.1.1", @@ -85,7 +85,6 @@ "@types/papaparse": "^5.5.2", "@types/react": "^18.3.13", "@types/react-dom": "^18.3.1", - "@types/react-table": "^7.7.20", "@vitejs/plugin-react": "^6.0.5", "@vitest/coverage-v8": "^4.1.11", "@zakodium/tsconfig": "^1.0.5", @@ -3268,6 +3267,26 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@tanstack/react-table": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-9.1.2.tgz", + "integrity": "sha512-YQPZFJ1nIi/bjjwsPZVouABgahDcl7Gdm33CdTStUJBn0DjEVJ2uhSTVmIoWt9MVKdQziXGAsXipSzy949Hygg==", + "license": "MIT", + "dependencies": { + "@tanstack/react-store": "^0.11.0", + "@tanstack/table-core": "9.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=18" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.14.9", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.9.tgz", @@ -3295,6 +3314,22 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/table-core": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-9.1.2.tgz", + "integrity": "sha512-ONpWQeass1sfg80CWF1NSwQ8r3GiqxA2lT/EdqIcrDEPZ0Z+0mM94eQoFYLPN0Kztzj8TQVb2+PrSZSItqA61g==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "^0.11.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/virtual-core": { "version": "3.17.7", "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.7.tgz", @@ -3492,16 +3527,6 @@ "@types/react": "^18.0.0" } }, - "node_modules/@types/react-table": { - "version": "7.7.20", - "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-7.7.20.tgz", - "integrity": "sha512-ahMp4pmjVlnExxNwxyaDrFgmKxSbPwU23sGQw2gJK4EhCvnvmib2s/O/+y1dfV57dXOwpr2plfyBol+vEHbi2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.66.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", @@ -5883,9 +5908,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.400", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.400.tgz", - "integrity": "sha512-96EWDNjM59SYflgeV5Ylsf4EMiq1a25YjCnJH7cxn/AF2H3pILRweaUnoLax0yKHWdpOzY6JKEu45e8irqZIHA==", + "version": "1.5.401", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.401.tgz", + "integrity": "sha512-H6ViHN68nGYlChEvlIU67fn8O2/tpbWQPwck98yaJmh+08LSvHiydzDQ6oXNccLU3kNRVIRS9A4mA7CG+i6fLQ==", "dev": true, "license": "ISC" }, @@ -11081,55 +11106,6 @@ "react-dom": "^18.3.1 || ^19.2.7" } }, - "node_modules/react-science/node_modules/@tanstack/react-table": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-9.1.2.tgz", - "integrity": "sha512-YQPZFJ1nIi/bjjwsPZVouABgahDcl7Gdm33CdTStUJBn0DjEVJ2uhSTVmIoWt9MVKdQziXGAsXipSzy949Hygg==", - "license": "MIT", - "dependencies": { - "@tanstack/react-store": "^0.11.0", - "@tanstack/table-core": "9.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": ">=18" - } - }, - "node_modules/react-science/node_modules/@tanstack/table-core": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-9.1.2.tgz", - "integrity": "sha512-ONpWQeass1sfg80CWF1NSwQ8r3GiqxA2lT/EdqIcrDEPZ0Z+0mM94eQoFYLPN0Kztzj8TQVb2+PrSZSItqA61g==", - "license": "MIT", - "dependencies": { - "@tanstack/store": "^0.11.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/react-table": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", - "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.3 || ^17.0.0-0 || ^18.0.0" - } - }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/package.json b/package.json index 79e5b70112..c3565f2219 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "clean-lib": "rimraf lib", "cspell": "cspell lint \"**/*.tsx\"", "dev": "vite --host localhost --port 3000 --open", - "eslint": "eslint . --cache --max-warnings=103", + "eslint": "eslint . --cache --max-warnings=106", "eslint-fix": "node --run eslint -- --fix", "install:clean": "rm -rf node_modules/ && rm -rf package-lock.json && npm install && npm start", "knip": "knip", @@ -65,6 +65,7 @@ "@hookform/resolvers": "^5.9.1", "@tanstack/react-form": "^1.33.5", "@tanstack/react-store": "^0.11.1", + "@tanstack/react-table": "^9.1.2", "@zakodium/nmr-types": "^0.5.24", "@zakodium/nmrium-core": "^0.7.59", "@zakodium/nmrium-core-plugins": "^0.7.79", @@ -112,7 +113,6 @@ "react-plot": "^3.1.2", "react-rnd": "^10.5.3", "react-science": "^23.0.1", - "react-table": "^7.8.0", "smart-array-filter": "^5.0.0", "ts-pattern": "^5.9.0", "usehooks-ts": "^3.1.1", @@ -136,7 +136,6 @@ "@types/papaparse": "^5.5.2", "@types/react": "^18.3.13", "@types/react-dom": "^18.3.1", - "@types/react-table": "^7.7.20", "@vitejs/plugin-react": "^6.0.5", "@vitest/coverage-v8": "^4.1.11", "@zakodium/tsconfig": "^1.0.5", diff --git a/src/component/elements/ReactTable/Elements/ReactTableHeader.tsx b/src/component/elements/ReactTable/Elements/ReactTableHeader.tsx deleted file mode 100644 index cdb3d77390..0000000000 --- a/src/component/elements/ReactTable/Elements/ReactTableHeader.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import type { CSSProperties, MouseEvent } from 'react'; -import { FaSortAmountDown, FaSortAmountUp } from 'react-icons/fa'; - -interface TableCellEvent { - onClick: (e: MouseEvent) => void; -} -interface ReactTableHeaderProps extends TableCellEvent { - headerGroups: any; -} - -const sortIconStyle: CSSProperties = { - position: 'absolute', - top: '50%', - transform: 'translateY(-50%)', - left: '2px', -}; - -function ReactTableHeader({ headerGroups, onClick }: ReactTableHeaderProps) { - return ( - - {headerGroups.map((headerGroup: any) => { - const { key: headerGroupKey, ...restHeaderGroupProps } = - headerGroup.getHeaderGroupProps(); - return ( - - {headerGroup.headers.map((column: any) => ( - - ))} - - ); - })} - - ); -} - -interface HeaderCellProps extends TableCellEvent { - column: any; -} - -const HeaderCell = (props: HeaderCellProps) => { - const { column } = props; - const { - key: headerKey, - style: headerStyle, - onClick, - ...restHeaderProps - } = column.getHeaderProps(column.getSortByToggleProps()); - function clickHandler(e: MouseEvent) { - if (onClick) { - onClick(e); - props.onClick(e); - } - } - return ( - - - {column.isSorted ? ( - column.isSortedDesc ? ( - - ) : ( - - ) - ) : ( - '' - )} - - {column?.render('Header')} - - ); -}; - -export default ReactTableHeader; diff --git a/src/component/elements/ReactTable/utility/ReactTableContext.ts b/src/component/elements/ReactTable/utility/ReactTableContext.ts deleted file mode 100644 index 4db874dd3b..0000000000 --- a/src/component/elements/ReactTable/utility/ReactTableContext.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createContext, useContext } from 'react'; - -import type { TableVirtualBoundary } from '../ReactTable.js'; - -const reactContext = createContext(null); -export const ReactTableProvider = reactContext.Provider; - -export function useReactTableContext() { - const context = useContext(reactContext); - if (!context) { - throw new Error('react table context was not found '); - } - return context; -} diff --git a/src/component/elements/ReactTable/utility/addCustomColumn.tsx b/src/component/elements/ReactTable/utility/addCustomColumn.tsx deleted file mode 100644 index 360241ed19..0000000000 --- a/src/component/elements/ReactTable/utility/addCustomColumn.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import type { CSSProperties, MouseEvent, ReactNode } from 'react'; -import type { CellProps, Row } from 'react-table'; - -import type { Column } from '../ReactTable.js'; - -export type CustomColumn = Column & { index: number }; -export type ControlCustomColumn = CustomColumn & { - showWhen: string; -}; - -export default function addCustomColumn( - array: any, - options: CustomColumn, -) { - const { - index, - Header = () => null, - accessor = null, - Cell = null, - sortType = 'basic', - enableRowSpan = false, - style = {}, - id, - } = options || {}; - - array.push({ - index, - ...(id && { id }), - ...(accessor && { accessor }), - ...(Cell && { Cell }), - Header, - sortType, - enableRowSpan, - style, - }); -} - -interface CreateActionColumnOptions { - onClick: (row: Row, even?: MouseEvent) => void; - icon: ReactNode; - style?: CSSProperties; - index: number; -} - -export function createActionColumn( - options: CreateActionColumnOptions, -): CustomColumn { - const { onClick, icon, index, style = {} } = options; - - return { - index, - Header: '', - style: { - width: '1%', - maxWidth: '20px', - minWidth: '20px', - padding: '0px', - textAlign: 'center', - ...style, - }, - id: crypto.randomUUID(), - Cell: (cell: CellProps) => ( - - ), - }; -} diff --git a/src/component/elements/ReactTable/utility/useRowSpan.ts b/src/component/elements/ReactTable/utility/useRowSpan.ts deleted file mode 100644 index 6350315908..0000000000 --- a/src/component/elements/ReactTable/utility/useRowSpan.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { Row } from 'react-table'; - -export type RowSpanHeaders = Array<{ - cellIndex: number; - cellValue: string; - id: string; -}>; - -export default function useRowSpan(hooks: any) { - hooks.useInstance.push(reducer); -} - -function reducer(instance: any) { - const { allColumns } = instance; - - const rowSpanHeaders = allColumns - .filter( - ({ enableRowSpan }: any) => - enableRowSpan !== undefined && enableRowSpan !== false, - ) - .map(({ id }: any) => ({ id, cellValue: null, cellIndex: 0 })); - - Object.assign(instance, { - rowSpanHeaders, - }); -} - -export function prepareRowSpan( - rows: Array>, - index: number, - rowSpanHeaders: RowSpanHeaders, - groupKey?: keyof T, -) { - const row = rows[index]; - for (let j = 0; j < row.allCells.length; j++) { - const cell = row.allCells[j]; - const rowSpanHeader = rowSpanHeaders.find((x) => x.id === cell.column.id); - if (rowSpanHeader !== undefined) { - const cellValue = groupKey - ? `${cell.value}-${String(row.original[groupKey])}` - : cell.value; - - if ( - rowSpanHeader.cellValue === null || - rowSpanHeader.cellValue !== cellValue - ) { - // @ts-expect-error Modifying a property that doesn't exist on the type - cell.isRowSpanned = false; - rowSpanHeader.cellValue = cellValue; - rowSpanHeader.cellIndex = index; - // @ts-expect-error Modifying a property that doesn't exist on the type - cell.rowSpan = 1; - } else { - // @ts-expect-error Modifying a property that doesn't exist on the type - rows[rowSpanHeader.cellIndex].allCells[j].rowSpan++; - // @ts-expect-error Modifying a property that doesn't exist on the type - cell.isRowSpanned = true; - } - } - } -} diff --git a/src/component/elements/tanstack_table/create_action_column.tsx b/src/component/elements/tanstack_table/create_action_column.tsx new file mode 100644 index 0000000000..6ef0079f66 --- /dev/null +++ b/src/component/elements/tanstack_table/create_action_column.tsx @@ -0,0 +1,43 @@ +import type { CSSProperties, MouseEvent, ReactNode } from 'react'; + +import type { TanStackRowData, TanStackTableColumn } from './types.ts'; + +interface CreateActionColumnOptions { + onClick: (row: TData, event?: MouseEvent) => void; + icon: ReactNode; + style?: CSSProperties; + id?: string; +} + +export function createActionColumn( + options: CreateActionColumnOptions, +): TanStackTableColumn { + const { onClick, icon, style = {}, id } = options; + + return { + id, + header: '', + meta: { + style: { + width: '1%', + maxWidth: '20px', + minWidth: '20px', + padding: '0px', + textAlign: 'center', + ...style, + }, + }, + cell: ({ row }) => ( + + ), + }; +} diff --git a/src/component/elements/tanstack_table/create_column_helper.ts b/src/component/elements/tanstack_table/create_column_helper.ts new file mode 100644 index 0000000000..0f0090638a --- /dev/null +++ b/src/component/elements/tanstack_table/create_column_helper.ts @@ -0,0 +1,11 @@ +import type { ColumnHelper } from '@tanstack/react-table'; +import { createColumnHelper } from '@tanstack/react-table'; + +import type { TanStackTableFeatures } from './features.ts'; +import type { TanStackRowData } from './types.ts'; + +export function createTanStackColumnHelper< + TData extends TanStackRowData, +>(): ColumnHelper { + return createColumnHelper(); +} diff --git a/src/component/elements/ReactTable/BaseReactTable.tsx b/src/component/elements/tanstack_table/elements/base_react_table.tsx similarity index 100% rename from src/component/elements/ReactTable/BaseReactTable.tsx rename to src/component/elements/tanstack_table/elements/base_react_table.tsx diff --git a/src/component/elements/ReactTable/Elements/EmptyDataRow.tsx b/src/component/elements/tanstack_table/elements/empty_data_row.tsx similarity index 100% rename from src/component/elements/ReactTable/Elements/EmptyDataRow.tsx rename to src/component/elements/tanstack_table/elements/empty_data_row.tsx diff --git a/src/component/elements/tanstack_table/elements/table_context.ts b/src/component/elements/tanstack_table/elements/table_context.ts new file mode 100644 index 0000000000..d938eb3aa9 --- /dev/null +++ b/src/component/elements/tanstack_table/elements/table_context.ts @@ -0,0 +1,15 @@ +import { createContext, useContext } from 'react'; + +import type { TanStackTableVirtualBoundary } from '../types.ts'; + +const reactContext = createContext(null); + +export const TanStackTableProvider = reactContext.Provider; + +export function useTanStackTableContext() { + const context = useContext(reactContext); + if (!context) { + throw new Error('table context was not found'); + } + return context; +} diff --git a/src/component/elements/tanstack_table/elements/table_header.tsx b/src/component/elements/tanstack_table/elements/table_header.tsx new file mode 100644 index 0000000000..9ac0414a9e --- /dev/null +++ b/src/component/elements/tanstack_table/elements/table_header.tsx @@ -0,0 +1,111 @@ +import type { CSSProperties, MouseEvent } from 'react'; +import { FaSortAmountDown, FaSortAmountUp } from 'react-icons/fa'; + +import type { + TanStackReactTable, + TanStackRowData, + TanStackTableHeader, + TanStackTableHeaderGroup, +} from '../types.ts'; + +interface TableCellEvent { + onClick: (e: MouseEvent) => void; +} + +interface TableHeaderProps< + TData extends TanStackRowData, +> extends TableCellEvent { + headerGroups: Array>; + table: TanStackReactTable; +} + +const sortIconStyle: CSSProperties = { + position: 'absolute', + top: '50%', + transform: 'translateY(-50%)', + left: '2px', +}; + +export default function TableHeader( + props: TableHeaderProps, +) { + const { headerGroups, table, onClick } = props; + return ( + + {headerGroups.map((headerGroup) => { + return ( + + {headerGroup.headers.map((header) => ( + + ))} + + ); + })} + + ); +} + +interface HeaderCellProps< + TData extends TanStackRowData, +> extends TableCellEvent { + header: TanStackTableHeader; + table: TanStackReactTable; +} + +function HeaderCell( + props: HeaderCellProps, +) { + const { header, table, onClick } = props; + + const meta = header.column.columnDef.meta; + const canSort = header.column.getCanSort(); + const toggleSortingHandler = canSort + ? header.column.getToggleSortingHandler() + : undefined; + const isSorted = header.column.getIsSorted(); + + function clickHandler(event: MouseEvent) { + if (toggleSortingHandler) { + toggleSortingHandler(event); + onClick(event); + } + } + + return ( + + + {isSorted ? ( + isSorted === 'desc' ? ( + + ) : ( + + ) + ) : null} + + + + ); +} diff --git a/src/component/elements/ReactTable/Elements/ReactTableRow.tsx b/src/component/elements/tanstack_table/elements/table_row.tsx similarity index 64% rename from src/component/elements/ReactTable/Elements/ReactTableRow.tsx rename to src/component/elements/tanstack_table/elements/table_row.tsx index 3988b901a0..d3ca23b685 100644 --- a/src/component/elements/ReactTable/Elements/ReactTableRow.tsx +++ b/src/component/elements/tanstack_table/elements/table_row.tsx @@ -1,5 +1,4 @@ /** @jsxImportSource @emotion/react */ - import type { CSSObject, SerializedStyles } from '@emotion/react'; import { css } from '@emotion/react'; import type { MouseEvent } from 'react'; @@ -7,16 +6,21 @@ import { useCallback, useEffect, useMemo } from 'react'; import type { HighlightEventSource } from '../../../highlight/index.js'; import { useHighlight } from '../../../highlight/index.js'; +import type { BaseContextMenuProps } from '../../ContextMenuBluePrint.js'; import { ContextMenu } from '../../ContextMenuBluePrint.js'; import type { - BaseRowStyle, - HighlightSourceProps, - TableContextMenuProps, -} from '../ReactTable.js'; + TanStackReactTable, + TanStackRowData, + TanStackTableClickEvent, + TanStackTableContextMenuProps, + TanStackTableHighlightSourceProps, + TanStackTableRow, + TanStackTableRowStyle, +} from '../types.ts'; function getRowStyle( isActive: boolean, - rowStyle: BaseRowStyle = {}, + rowStyle: TanStackTableRowStyle = {}, disableDefaultRowStyle?: boolean, ): SerializedStyles { const { hover = {}, active = {}, base = {}, activated = {} } = rowStyle; @@ -40,19 +44,17 @@ function getRowStyle( ]); } -export interface ClickEvent { - onClick?: (event: MouseEvent, data: unknown) => void; -} - -interface ReactTableRowProps extends ClickEvent, TableContextMenuProps { - row: any; +interface TableRowProps + extends TanStackTableClickEvent, TanStackTableContextMenuProps { + row: TanStackTableRow; + table: TanStackReactTable; isRowActive: boolean; - rowStyle: BaseRowStyle | undefined; + rowStyle: TanStackTableRowStyle | undefined; disableDefaultRowStyle?: boolean; } -type ReactTableRowPropsWithHighlight = ReactTableRowProps & - HighlightSourceProps; +type TableRowPropsWithHighlight = + TableRowProps & TanStackTableHighlightSourceProps; function getIDs(row: any): string[] { const id = row.original.id; @@ -66,9 +68,12 @@ function getIDs(row: any): string[] { return ['']; } -function ReactTableRow(props: ReactTableRowPropsWithHighlight) { +export default function TableRow( + props: TableRowPropsWithHighlight, +) { const { row, + table, highlightedSource = 'UNKNOWN', getHighlightExtra, onContextMenuSelect, @@ -103,54 +108,42 @@ function ReactTableRow(props: ReactTableRowPropsWithHighlight) { [onClick, row], ); - const { key: rowKey, ...otherKeyProps } = row.getRowProps(); return ( onContextMenuSelect?.(selected, row.original)} + onSelect={(selected: Parameters[0]) => + onContextMenuSelect?.(selected, row.original) + } as="tr" style={{ position: 'static' }} - key={rowKey} css={getRowStyle( highlight.isActive || isRowActive, rowStyle, disableDefaultRowStyle, )} - {...otherKeyProps} {...highlight.onHover} > - {row.cells.map((cell: any) => { - const { - column: { style }, - isRowSpanned, - rowSpan, - } = cell; - if (isRowSpanned) { - return null; - } else { - const { key: columnKey, ...otherColumnProps } = cell.getCellProps(); - - return ( - { - e.preventDefault(); - - return false; - }} - style={style} - onClick={clickHandler} - > - {cell.render('Cell')} - - ); - } + {row.getAllCells().map((cell) => { + const meta = cell.column.columnDef.meta; + return ( + { + e.preventDefault(); + + return false; + }} + style={{ + ...meta?.style, + ...meta?.tdStyle, + }} + onClick={clickHandler} + > + + + ); })} ); } - -export default ReactTableRow; diff --git a/src/component/elements/tanstack_table/features.ts b/src/component/elements/tanstack_table/features.ts new file mode 100644 index 0000000000..afee9198fd --- /dev/null +++ b/src/component/elements/tanstack_table/features.ts @@ -0,0 +1,32 @@ +import { + createSortedRowModel, + metaHelper, + rowSortingFeature, + sortFn_alphanumeric as sortFnAlphanumeric, + sortFn_basic as sortFnBasic, + sortFn_datetime as sortFnDatetime, + sortFn_text as sortFnText, + tableFeatures, +} from '@tanstack/react-table'; +import type { CSSProperties } from 'react'; + +interface TanStackTableColumnMeta { + enableRowSpan?: boolean; + style?: CSSProperties; + thStyle?: CSSProperties; + tdStyle?: CSSProperties; +} + +export const tanStackTableFeatures = tableFeatures({ + rowSortingFeature, + sortedRowModel: createSortedRowModel(), + sortFns: { + alphanumeric: sortFnAlphanumeric, + basic: sortFnBasic, + datetime: sortFnDatetime, + text: sortFnText, + }, + columnMeta: metaHelper(), +}); + +export type TanStackTableFeatures = typeof tanStackTableFeatures; diff --git a/src/component/elements/tanstack_table/index.ts b/src/component/elements/tanstack_table/index.ts new file mode 100644 index 0000000000..ae70aba71a --- /dev/null +++ b/src/component/elements/tanstack_table/index.ts @@ -0,0 +1,5 @@ +export * from './create_action_column.tsx'; +export * from './create_column_helper.ts'; +export * from './features.ts'; +export { default as TanStackTable } from './table.tsx'; +export * from './types.ts'; diff --git a/src/component/elements/ReactTable/ReactTable.tsx b/src/component/elements/tanstack_table/table.tsx similarity index 69% rename from src/component/elements/ReactTable/ReactTable.tsx rename to src/component/elements/tanstack_table/table.tsx index 8e109db669..00945ccbe4 100644 --- a/src/component/elements/ReactTable/ReactTable.tsx +++ b/src/component/elements/tanstack_table/table.tsx @@ -1,7 +1,7 @@ -/* eslint-disable unicorn/prefer-logical-operator-over-ternary */ /** @jsxImportSource @emotion/react */ import type { CSSObject, SerializedStyles } from '@emotion/react'; import { css } from '@emotion/react'; +import { useTable } from '@tanstack/react-table'; import type { CSSProperties, MouseEvent, @@ -10,7 +10,6 @@ import type { UIEvent, } from 'react'; import { - forwardRef, memo, useEffect, useLayoutEffect, @@ -19,105 +18,61 @@ import { useState, } from 'react'; import { useResizeObserver } from 'react-d3-utils'; -import type { - CellProps, - Column as ReactColumn, - Row, - TableInstance, - UseSortByColumnOptions, - UseSortByInstanceProps, - UseSortByOptions, - UseTableOptions, -} from 'react-table'; -import { useSortBy, useTable } from 'react-table'; -import type { - HighlightEventSourceExtra, - HighlightEventSourceType, -} from '../../highlight/index.js'; -import type { BaseContextMenuProps } from '../ContextMenuBluePrint.js'; - -import { BaseReactTable } from './BaseReactTable.js'; -import { EmptyDataRow } from './Elements/EmptyDataRow.js'; -import ReactTableHeader from './Elements/ReactTableHeader.js'; -import type { ClickEvent } from './Elements/ReactTableRow.js'; -import ReactTableRow from './Elements/ReactTableRow.js'; +import { BaseReactTable } from './elements/base_react_table.tsx'; +import { EmptyDataRow } from './elements/empty_data_row.tsx'; import { - ReactTableProvider, - useReactTableContext, -} from './utility/ReactTableContext.js'; -import type { RowSpanHeaders } from './utility/useRowSpan.js'; -import useRowSpan, { prepareRowSpan } from './utility/useRowSpan.js'; - -export type HighlightSourceProps = { - [K in HighlightEventSourceType]: HighlightEventSourceExtra extends never - ? { highlightedSource?: K; getHighlightExtra?: never } - : { - highlightedSource: K; - getHighlightExtra: (row: any) => HighlightEventSourceExtra; - }; -}[HighlightEventSourceType]; - -interface ExtraColumn { - enableRowSpan?: boolean; - style?: CSSProperties; - Cell?: (cell: CellProps) => ReactElement | string; -} - -export type Column = ReactColumn & - ExtraColumn & - UseSortByColumnOptions; - -type TableInstanceWithHooks = TableInstance & { - rowSpanHeaders: RowSpanHeaders; -} & UseSortByInstanceProps; - -type TableOptions = UseTableOptions & UseSortByOptions; - -interface SortEvent { - onSortEnd?: (data: any, isTableSorted?: boolean) => void; -} - -export interface BaseRowStyle { - active?: CSSProperties; - activated?: CSSProperties; - hover?: CSSProperties; - base?: CSSProperties; -} - -export interface TableContextMenuProps { - onContextMenuSelect?: ( - selected: Parameters[0], - data: any, - ) => void; - contextMenu?: BaseContextMenuProps['options']; -} -interface ReactTableProps - extends TableContextMenuProps, ClickEvent, SortEvent { - data: T[]; - columns: Array>; + TanStackTableProvider, + useTanStackTableContext, +} from './elements/table_context.ts'; +import TableHeader from './elements/table_header.tsx'; +import TableRow from './elements/table_row.tsx'; +import { tanStackTableFeatures } from './features.ts'; +import type { + TanStackRowData, + TanStackTableClickEvent, + TanStackTableColumn, + TanStackTableContextMenuProps, + TanStackTableHighlightSourceProps, + TanStackTableRow, + TanStackTableRowStyle, + TanStackTableSortEvent, + TanStackTableVirtualBoundary, +} from './types.ts'; + +interface TanStackTableProps + extends + TanStackTableContextMenuProps, + TanStackTableClickEvent, + TanStackTableSortEvent { + data: readonly TData[]; + columns: ReadonlyArray>; approxItemHeight?: number; approxColumnWidth?: number; - groupKey?: keyof T; + groupKey?: keyof TData; indexKey?: string; enableVirtualScroll?: boolean; enableColumnsVirtualScroll?: boolean; - activeRow?: (data: Row) => boolean; + activeRow?: (data: TanStackTableRow) => boolean; enableDefaultActiveRow?: boolean; totalCount?: number; emptyDataRowText?: string; - rowStyle?: BaseRowStyle | ((data: T) => BaseRowStyle | undefined); + rowStyle?: + | TanStackTableRowStyle + | ((data: TData) => TanStackTableRowStyle | undefined); style?: CSSObject | SerializedStyles; disableDefaultRowStyle?: boolean; } -type ReactTableInnerProps = ReactTableProps & - HighlightSourceProps & { - onScroll?: (event: UIEvent) => void; - }; +type ReactTableInnerProps = + TanStackTableProps & + TanStackTableHighlightSourceProps & { + containerRef: Ref; + onScroll?: (event: UIEvent) => void; + }; -type ReactTableOuterProps = ReactTableProps & - HighlightSourceProps; +type ReactTableOuterProps = + TanStackTableProps & TanStackTableHighlightSourceProps; const styles = { table: ( @@ -150,13 +105,8 @@ const counterStyle: CSSProperties = { fontSize: '1.4em', }; -const ReactTableInner = forwardRef(TableInner) as ( - props: ReactTableInnerProps & { ref?: Ref }, -) => ReactElement; - -function TableInner( - props: ReactTableInnerProps, - ref: Ref, +function TanStackTableInner( + props: ReactTableInnerProps, ) { const { data, @@ -170,7 +120,6 @@ function TableInner( enableVirtualScroll = false, enableColumnsVirtualScroll = false, approxColumnWidth = 40, - groupKey, onClick, activeRow, totalCount, @@ -180,10 +129,11 @@ function TableInner( disableDefaultRowStyle = false, enableDefaultActiveRow = false, emptyDataRowText = 'No Data', + containerRef, } = props; const isSortedEventTriggered = useRef(false); - const virtualBoundary = useReactTableContext(); + const virtualBoundary = useTanStackTableContext(); const [rowIndex, setRowIndex] = useState(); const timeoutIdRef = useRef(); const [isCounterVisible, setCounterVisibility] = useState(false); @@ -199,25 +149,14 @@ function TableInner( : columns; }, [enableColumnsVirtualScroll, virtualBoundary.columns, columns]); - const { - getTableProps, - getTableBodyProps, - headerGroups, - rows, - prepareRow, - rowSpanHeaders, - state, // Access the sort state here - } = useTable( - { - columns: memoColumns, - data, - autoResetSortBy: false, - } as TableOptions, - useSortBy, - useRowSpan, - ) as TableInstanceWithHooks; - - const { sortBy } = state as any; + const table = useTable({ + features: tanStackTableFeatures, + data, + columns: memoColumns, + }); + + const sortBy = table.state.sorting; + const rows = table.getRowModel().rows; function clickHandler(event: MouseEvent, row: any) { setRowIndex(row.index); @@ -263,14 +202,14 @@ function TableInner( const lastRow = rowsData.at(-1); const index = (lastRow?.original as any)?.[indexKey] || lastRow?.index; - const total = totalCount ? totalCount : data.length; + const total = totalCount || data.length; - const startColumn = columns[virtualBoundary.columns.start]?.Header; + const startColumn = columns[virtualBoundary.columns.start]?.header; return ( <>
( }} /> - - + {!data || (data?.length === 0 && ( ( /> ))} {rowsData.map((row, index) => { - prepareRow(row); - - prepareRowSpan( - rows, - enableVirtualScroll - ? index + virtualBoundary.rows.start - : index, - rowSpanHeaders, - groupKey, - ); - const { key, ...restRowProps } = row.getRowProps(); const highlightSourceProps = { highlightedSource, getHighlightExtra, - } as HighlightSourceProps; + } as TanStackTableHighlightSourceProps; return ( - ( } rowStyle={ typeof rowStyle === 'function' - ? rowStyle(row as T) + ? rowStyle(row.original) : rowStyle } disableDefaultRowStyle={disableDefaultRowStyle} @@ -377,16 +305,9 @@ function TableInner( ); } -interface VirtualBoundary { - start: number; - end: number; -} -export interface TableVirtualBoundary { - rows: VirtualBoundary; - columns: VirtualBoundary; -} - -function ReactTable(props: ReactTableOuterProps) { +function TanStackTable( + props: ReactTableOuterProps, +) { const { data, approxItemHeight = 40, @@ -403,7 +324,7 @@ function ReactTable(props: ReactTableOuterProps) { useResizeObserver(); const [tableVirtualBoundary, setTableVirtualBoundary] = - useState({ + useState({ rows: { start: 1, end: 0, @@ -489,7 +410,6 @@ function ReactTable(props: ReactTableOuterProps) { function findEndIndex(index: number, numberOfVisibleRows: number) { const newIndex = index + numberOfVisibleRows; const currentIndx = newIndex >= data.length ? data.length - 1 : newIndex; - // return currentIndx; // Look for the last index of the group return lookForGroupIndex(currentIndx, 1); } @@ -521,7 +441,7 @@ function ReactTable(props: ReactTableOuterProps) { } return ( - +
(props: ReactTableOuterProps) { style, )} > - + onScroll={scrollHandler} onSortEnd={onSortEnd} - ref={containerRef} + containerRef={containerRef} {...props} />
-
+ ); } -export default memo(ReactTable) as ( - props: ReactTableOuterProps, +export default memo(TanStackTable) as ( + props: ReactTableOuterProps, ) => ReactElement; diff --git a/src/component/elements/tanstack_table/types.ts b/src/component/elements/tanstack_table/types.ts new file mode 100644 index 0000000000..27b7a2ffbd --- /dev/null +++ b/src/component/elements/tanstack_table/types.ts @@ -0,0 +1,86 @@ +import type { + CellData, + ColumnDef, + Header, + HeaderGroup, + ReactTable, + Row, + RowData, +} from '@tanstack/react-table'; +import type { CSSProperties, MouseEvent } from 'react'; + +import type { + HighlightEventSourceExtra, + HighlightEventSourceType, +} from '../../highlight/index.tsx'; +import type { BaseContextMenuProps } from '../ContextMenuBluePrint.tsx'; + +import type { TanStackTableFeatures } from './features.ts'; + +type TanStackCellData = CellData; +export type TanStackRowData = RowData; + +export type TanStackReactTable = ReactTable< + TanStackTableFeatures, + TData +>; + +export type TanStackTableRow = Row< + TanStackTableFeatures, + TData +>; + +export type TanStackTableColumn< + TData extends TanStackRowData, + TValue extends TanStackCellData = TanStackCellData, +> = ColumnDef; + +export type TanStackTableHeader< + TData extends TanStackRowData, + TValue extends TanStackCellData = TanStackCellData, +> = Header; + +export type TanStackTableHeaderGroup = + HeaderGroup; + +export type TanStackTableHighlightSourceProps = { + [K in HighlightEventSourceType]: HighlightEventSourceExtra extends never + ? { highlightedSource?: K; getHighlightExtra?: never } + : { + highlightedSource: K; + getHighlightExtra: (row: TData) => HighlightEventSourceExtra; + }; +}[HighlightEventSourceType]; + +export interface TanStackTableRowStyle { + active?: CSSProperties; + activated?: CSSProperties; + hover?: CSSProperties; + base?: CSSProperties; +} + +export interface TanStackTableContextMenuProps { + contextMenu?: BaseContextMenuProps['options']; + onContextMenuSelect?: ( + selected: Parameters[0], + data: TData, + ) => void; +} + +export interface TanStackTableClickEvent { + onClick?: (event: MouseEvent, row: TanStackTableRow) => void; +} + +export interface TanStackTableSortEvent { + onSortEnd?: (data: TData[], isTableSorted?: boolean) => void; +} + +interface VirtualBoundary { + start: number; + end: number; +} + +export interface TanStackTableVirtualBoundary { + rows: VirtualBoundary; + columns: VirtualBoundary; +} diff --git a/src/component/modal/ImportPublicationStringModal.tsx b/src/component/modal/ImportPublicationStringModal.tsx index e202dfb302..48cfb054ee 100644 --- a/src/component/modal/ImportPublicationStringModal.tsx +++ b/src/component/modal/ImportPublicationStringModal.tsx @@ -9,10 +9,10 @@ import { Controller, useForm } from 'react-hook-form'; import { useDispatch } from '../context/DispatchContext.js'; import { useToaster } from '../context/ToasterContext.js'; import { GroupPane } from '../elements/GroupPane.js'; -import type { Column } from '../elements/ReactTable/ReactTable.js'; -import ReactTable from '../elements/ReactTable/ReactTable.js'; import { StandardDialog } from '../elements/StandardDialog.tsx'; import { StyledDialogBody } from '../elements/StyledDialogBody.js'; +import type { TanStackTableColumn } from '../elements/tanstack_table/index.ts'; +import { TanStackTable } from '../elements/tanstack_table/index.ts'; interface InnerImportPublicationStringModalProps { onClose: () => void; @@ -22,8 +22,8 @@ interface ImportPublicationStringModalProps extends InnerImportPublicationString isOpen: boolean; } -function handleRowStyle(data: any) { - const level = (data?.original as LogEntry).level; +function handleRowStyle(data: LogEntry) { + const { level } = data; let backgroundColor = 'lightgreen'; if (level > 40) { backgroundColor = 'pink'; @@ -39,20 +39,21 @@ const INITIAL_VALUES = { '1H NMR (CDCl3, 400MHz) δ 1 (s, 1H), 2 (d, 1H, J=7), 3 (t, 1H, J=7), 4 (q, 1H, J=7), 5 (quint, 1H, J=7), 6 (hex, 1H, J=7), 7 (hept, 1H, J=7), 8 (dd, 1H, J=7, J=4)', }; -const COLUMNS: Array> = [ +const COLUMNS: Array> = [ { - Header: '#', - accessor: (_, index) => index + 1, - style: { width: '40px' }, + id: 'rowNumber', + header: '#', + accessorFn: (_, index) => index + 1, + meta: { style: { width: '40px' } }, }, { - Header: 'Label', - accessor: 'levelLabel', - style: { width: '60px' }, + header: 'Label', + accessorKey: 'levelLabel', + meta: { style: { width: '60px' } }, }, { - Header: 'Message', - accessor: 'message', + header: 'Message', + accessorKey: 'message', }, ]; @@ -94,7 +95,7 @@ function InnerImportPublicationStringModal( const debounceChanges = useMemo( () => - debounce((value: any) => { + debounce((value: string) => { resurrect(value, { logger: loggerRef.current }); }, 250), [], @@ -128,7 +129,7 @@ function InnerImportPublicationStringModal( })(); } - function handleOnChange(value: any) { + function handleOnChange(value: string) { loggerRef.current.clear(); if (!value) { @@ -183,7 +184,7 @@ function InnerImportPublicationStringModal( />
- 40) { backgroundColor = 'pink'; @@ -77,12 +78,13 @@ export function LogsHistoryModal(props: LogsHistoryModalProps) { lastReadLogId, ); - const COLUMNS = useMemo>>( - () => [ + const COLUMNS = useMemo(() => { + const columnHelper = createTanStackColumnHelper(); + return columnHelper.columns([ { - Header: '#', - accessor: (_, index) => index + 1, - Cell: ({ row }: CellProps) => ( + header: '#', + accessorFn: (_, index) => index + 1, + cell: ({ row }) => ( {row.original.id > lastReadLogId && (
), - style: { width: '40px' }, + meta: { style: { width: '40px' } }, }, { - Header: 'Time', - accessor: (row) => logsDataFormat.format(row.time), - style: { width: '100px' }, + header: 'Time', + accessorKey: 'time', + cell: ({ getValue }) => logsDataFormat.format(getValue()), + meta: { style: { width: '100px' } }, }, { - Header: 'Label', - accessor: 'levelLabel', - style: { width: '60px' }, + header: 'Label', + accessorKey: 'levelLabel', + meta: { style: { width: '60px' } }, }, { - Header: 'Message', - accessor: 'message', + header: 'Message', + accessorKey: 'message', }, { - Header: 'Error', - Cell: ({ row }: CellProps) => { + header: 'Error', + cell: ({ row }) => { const { error } = row.original; return ( @@ -127,11 +130,14 @@ export function LogsHistoryModal(props: LogsHistoryModalProps) { ); }, }, - ], - [lastReadLogId], + ]); + }, [lastReadLogId]); + + const sortedLogs = useMemo( + () => logsHistory.toSorted((a, b) => b.time - a.time), + [logsHistory], ); - const sortedLogs = logsHistory.toSorted((a, b) => b.time - a.time); return ( <> {!autoOpen && ( @@ -173,7 +179,7 @@ export function LogsHistoryModal(props: LogsHistoryModalProps) { style={{ width: '50vw', height: '50vh' }} > - { + (index: number) => { + const data = getValues(`signals.${signalIndex}.js`) || []; const jCouplings = data.filter( (_: any, columnIndex: number) => columnIndex !== index, ); @@ -135,24 +138,26 @@ export function SignalJCouplingsTable(props: SignalJCouplingsTableProps) { lastSelectedCouplingIndexRef.current = null; } }, - [setValue, signalIndex], + [getValues, setValue, signalIndex], ); function deleteAllHandler() { setValue(`signals[${signalIndex}].js`, []); lastSelectedCouplingIndexRef.current = null; } - const COLUMNS = useMemo>>( - () => [ + + const COLUMNS = useMemo(() => { + const columnHelper = createTanStackColumnHelper(); + return columnHelper.columns([ { - Header: '#', - style: { width: '25px', ...styles.column }, - accessor: (_: any, index) => index + 1, + header: '#', + meta: { style: { width: '25px', ...styles.column } }, + accessorFn: (_: any, index) => index + 1, }, { - Header: 'Multiplicity', - style: { padding: 0, ...styles.column }, - Cell: ({ row }: CellProps) => { + header: 'Multiplicity', + meta: { style: { padding: 0, ...styles.column } }, + cell: ({ row }) => { return ( ) => { + header: 'J (Hz)', + meta: { style: { padding: 0, ...styles.column } }, + cell: ({ row }) => { return ( ) => { + cell: ({ row }) => { const record: any = row.original; return (
@@ -215,7 +220,7 @@ export function SignalJCouplingsTable(props: SignalJCouplingsTableProps) { size="small" variant="outlined" intent="danger" - onClick={() => deleteHandler(data, row.index)} + onClick={() => deleteHandler(row.index)} > @@ -224,9 +229,8 @@ export function SignalJCouplingsTable(props: SignalJCouplingsTableProps) { ); }, }, - ], - [control, deleteHandler, setFocus, setValue, signalIndex], - ); + ]); + }, [control, deleteHandler, setFocus, setValue, signalIndex]); function selectRowHandler(index: number) { setFocusSource('coupling'); @@ -263,7 +267,7 @@ export function SignalJCouplingsTable(props: SignalJCouplingsTableProps) { overflow: 'auto', }} > - selectRowHandler(rowData.index)} diff --git a/src/component/modal/editRange/forms/components/signal-tabs/SignalPeaksTable.tsx b/src/component/modal/editRange/forms/components/signal-tabs/SignalPeaksTable.tsx index 2ea6583750..302b5a25d3 100644 --- a/src/component/modal/editRange/forms/components/signal-tabs/SignalPeaksTable.tsx +++ b/src/component/modal/editRange/forms/components/signal-tabs/SignalPeaksTable.tsx @@ -8,12 +8,13 @@ import { useCallback, useMemo, useRef } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; import { FaPlus, FaRegTrashAlt } from 'react-icons/fa'; import { Toolbar } from 'react-science/ui'; -import type { CellProps } from 'react-table'; import { NumberInput2Controller } from '../../../../../elements/NumberInput2Controller.js'; -import type { Column } from '../../../../../elements/ReactTable/ReactTable.js'; -import ReactTable from '../../../../../elements/ReactTable/ReactTable.js'; import { useTabsController } from '../../../../../elements/TabsProvider.js'; +import { + TanStackTable, + createTanStackColumnHelper, +} from '../../../../../elements/tanstack_table/index.ts'; import useSpectrum from '../../../../../hooks/useSpectrum.js'; import { useEvent } from '../../../../../utility/Events.js'; import { useEventFocusInput } from '../SignalsContent.js'; @@ -49,7 +50,7 @@ function getPeakKey( export function SignalPeaksTable(props: SignalPeaksTableProps) { const { focusSource, setFocusSource } = useEventFocusInput(); - const { setValue, control, setFocus } = useFormContext(); + const { setValue, control, setFocus, getValues } = useFormContext(); const { signals } = useWatch(); const { selectedTabId: signalIndex = 0 } = useTabsController(); @@ -123,7 +124,8 @@ export function SignalPeaksTable(props: SignalPeaksTableProps) { ); const deleteHandler = useCallback( - (data: any, index: number) => { + (index: number) => { + const data = getValues(`signals.${signalIndex}.peaks`) || []; const peaks = data.filter( (_: any, columnIndex: number) => columnIndex !== index, ); @@ -133,7 +135,7 @@ export function SignalPeaksTable(props: SignalPeaksTableProps) { lastSelectedPeakIndexRef.current = null; } }, - [setValue, signalIndex], + [getValues, setValue, signalIndex], ); function deleteAllHandler() { @@ -150,17 +152,18 @@ export function SignalPeaksTable(props: SignalPeaksTableProps) { [re, setValue, signalIndex, xArray], ); - const COLUMNS = useMemo>>( - () => [ + const COLUMNS = useMemo(() => { + const columnHelper = createTanStackColumnHelper(); + return columnHelper.columns([ { - Header: '#', - style: { width: '25px', ...styles.column }, - accessor: (_: any, index) => index + 1, + header: '#', + meta: { style: { width: '25px', ...styles.column } }, + accessorFn: (_: any, index) => index + 1, }, { - Header: 'delta', - style: { padding: 0, ...styles.column }, - Cell: ({ row }: CellProps) => { + header: 'delta', + meta: { style: { padding: 0, ...styles.column } }, + cell: ({ row }) => { return ( ) => { + header: 'Intensity', + meta: { style: { padding: 0, ...styles.column } }, + cell: ({ row }) => { return ( ) => { + cell: ({ row }) => { const record: any = row.original; return (
@@ -207,7 +210,7 @@ export function SignalPeaksTable(props: SignalPeaksTableProps) { size="small" variant="outlined" intent="danger" - onClick={() => deleteHandler(data, row.index)} + onClick={() => deleteHandler(row.index)} > @@ -216,9 +219,8 @@ export function SignalPeaksTable(props: SignalPeaksTableProps) { ); }, }, - ], - [changeDeltaHandler, control, deleteHandler, signalIndex], - ); + ]); + }, [changeDeltaHandler, control, deleteHandler, signalIndex]); function selectRowHandler(index: number) { setFocusSource('peak'); @@ -250,7 +252,7 @@ export function SignalPeaksTable(props: SignalPeaksTableProps) { overflow: 'auto', }} > - selectRowHandler(rowData.index)} diff --git a/src/component/modal/metaImportation/MetaImportationModal.tsx b/src/component/modal/metaImportation/MetaImportationModal.tsx index 94663701f4..d9942a13b0 100644 --- a/src/component/modal/metaImportation/MetaImportationModal.tsx +++ b/src/component/modal/metaImportation/MetaImportationModal.tsx @@ -21,10 +21,12 @@ import { useToaster } from '../../context/ToasterContext.js'; import Button from '../../elements/Button.js'; import { Input2Controller } from '../../elements/Input2Controller.js'; import Label from '../../elements/Label.js'; -import type { Column } from '../../elements/ReactTable/ReactTable.js'; -import ReactTable from '../../elements/ReactTable/ReactTable.js'; import { Select2Controller } from '../../elements/Select2Controller.js'; import { StandardDialog } from '../../elements/StandardDialog.tsx'; +import { + TanStackTable, + createTanStackColumnHelper, +} from '../../elements/tanstack_table/index.ts'; import { convertPathArrayToString } from '../../utility/convertPathArrayToString.js'; import { getSpectraObjectPaths } from '../../utility/getSpectraObjectPaths.js'; @@ -164,9 +166,10 @@ function InnerMetaImportationModal({ const columns = useMemo(() => { const fields = parseResult?.meta.fields || []; - const columns: Array> = [ - { Header: '#', accessor: (_: any, index) => index + 1 }, - ]; + const columnHelper = createTanStackColumnHelper(); + const columns = columnHelper.columns([ + { header: '#', accessorFn: (_: any, index) => index + 1 }, + ]); function getRowValue(val: any) { if (typeof val === 'string') return val; @@ -177,9 +180,9 @@ function InnerMetaImportationModal({ for (const fieldName of fields) { if (fieldName) { columns.push({ - Header: fieldName, - accessor: (row: any) => getRowValue(row[fieldName]), - style: styles.column, + header: fieldName, + accessorFn: (row: any) => getRowValue(row[fieldName]), + meta: { style: styles.column }, }); } } @@ -303,7 +306,7 @@ function InnerMetaImportationModal({
- { + (event: MouseEvent, row: TanStackTableRow) => { event.preventDefault(); event.stopPropagation(); @@ -29,29 +32,29 @@ function AutomaticAssignmentTable({ data }: AutomaticAssignmentTableProps) { [dispatch], ); - const COLUMNS = useMemo>>( - () => [ + const COLUMNS = useMemo(() => { + const columnHelper = createTanStackColumnHelper(); + return columnHelper.columns([ { - index: 1, - Header: '#', - accessor: (_, index) => index + 1, - style: { width: '1%', maxWidth: '40px', minWidth: '40px' }, + header: '#', + accessorFn: (_, index) => index + 1, + meta: { style: { width: '1%', maxWidth: '40px', minWidth: '40px' } }, }, { - index: 2, - Header: 'Score', - accessor: 'score', + header: 'Score', + accessorKey: 'score', }, { - index: 3, - Header: '', - style: { - width: '1%', - maxWidth: '24px', - minWidth: '24px', + header: '', + meta: { + style: { + width: '1%', + maxWidth: '24px', + minWidth: '24px', + }, }, id: 'assign-button', - Cell: ({ row }: CellProps) => ( + cell: ({ row }) => (