From bccab06abbbe567c45c27831b2357d8e1fec7fca Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Sat, 29 Jul 2023 16:54:51 +0200 Subject: [PATCH] refactor(ui/datatables): use object type for table data --- app/react/components/datatables/Datatable.tsx | 10 ++++----- .../datatables/DatatableContent.tsx | 6 +++--- .../datatables/ExpandableDatatable.tsx | 5 +++-- .../datatables/ExpandableDatatableRow.tsx | 5 +++-- app/react/components/datatables/Filter.tsx | 6 +++--- .../components/datatables/NestedDatatable.tsx | 6 +++--- .../components/datatables/TableContent.tsx | 8 +++---- .../components/datatables/TableHeaderRow.tsx | 10 +++++---- app/react/components/datatables/TableRow.tsx | 12 +++++++---- .../{NameCell.tsx => buildNameColumn.tsx} | 15 +++++++------ .../components/datatables/defaultGetRowId.ts | 21 +++++++++++++------ .../components/datatables/expand-column.tsx | 6 +++--- .../components/datatables/filter-types.ts | 10 ++++++--- app/react/components/datatables/types.ts | 2 ++ .../ListView/ConfigsDatatable/columns.tsx | 4 ++-- .../ListView/EdgeStacksDatatable/columns.tsx | 9 ++------ .../ListView/columns/index.ts | 4 ++-- .../FDOProfilesDatatable/columns/index.tsx | 4 ++-- .../TeamsDatatable/TeamsDatatable.tsx | 4 ++-- 19 files changed, 84 insertions(+), 63 deletions(-) rename app/react/components/datatables/{NameCell.tsx => buildNameColumn.tsx} (65%) diff --git a/app/react/components/datatables/Datatable.tsx b/app/react/components/datatables/Datatable.tsx index 8f5c6d178..e6ee26fef 100644 --- a/app/react/components/datatables/Datatable.tsx +++ b/app/react/components/datatables/Datatable.tsx @@ -28,7 +28,7 @@ import { DatatableFooter } from './DatatableFooter'; import { defaultGetRowId } from './defaultGetRowId'; import { Table } from './Table'; import { useGoToHighlightedRow } from './useGoToHighlightedRow'; -import { BasicTableSettings } from './types'; +import { BasicTableSettings, DefaultType } from './types'; import { DatatableContent } from './DatatableContent'; import { createSelectColumn } from './select-column'; import { TableRow } from './TableRow'; @@ -49,7 +49,7 @@ export type PaginationProps = }; export interface Props< - D extends Record, + D extends DefaultType, TMeta extends TableMeta = TableMeta > extends AutomationTestingProps { dataset: D[]; @@ -74,7 +74,7 @@ export interface Props< } export function Datatable< - D extends Record, + D extends DefaultType, TMeta extends TableMeta = TableMeta >({ columns, @@ -221,7 +221,7 @@ export function Datatable< } } -function defaultRenderRow>( +function defaultRenderRow( row: Row, highlightedItemId?: string ) { @@ -235,7 +235,7 @@ function defaultRenderRow>( ); } -function getIsSelectionEnabled>( +function getIsSelectionEnabled( disabledSelect?: boolean, isRowSelectable?: Props['isRowSelectable'] ) { diff --git a/app/react/components/datatables/DatatableContent.tsx b/app/react/components/datatables/DatatableContent.tsx index d435a919a..605f1f4b2 100644 --- a/app/react/components/datatables/DatatableContent.tsx +++ b/app/react/components/datatables/DatatableContent.tsx @@ -3,9 +3,9 @@ import { Row, Table as TableInstance } from '@tanstack/react-table'; import { AutomationTestingProps } from '@/types'; import { Table } from './Table'; +import { DefaultType } from './types'; -interface Props> - extends AutomationTestingProps { +interface Props extends AutomationTestingProps { tableInstance: TableInstance; renderRow(row: Row): React.ReactNode; onSortChange?(colId: string, desc: boolean): void; @@ -13,7 +13,7 @@ interface Props> emptyContentLabel?: string; } -export function DatatableContent>({ +export function DatatableContent({ tableInstance, renderRow, onSortChange, diff --git a/app/react/components/datatables/ExpandableDatatable.tsx b/app/react/components/datatables/ExpandableDatatable.tsx index 654266684..56ab2e394 100644 --- a/app/react/components/datatables/ExpandableDatatable.tsx +++ b/app/react/components/datatables/ExpandableDatatable.tsx @@ -7,14 +7,15 @@ import { Props as DatatableProps, PaginationProps, } from './Datatable'; +import { DefaultType } from './types'; -interface Props> +interface Props extends Omit, 'renderRow' | 'expandable'> { renderSubRow(row: Row): ReactNode; expandOnRowClick?: boolean; } -export function ExpandableDatatable>({ +export function ExpandableDatatable({ renderSubRow, getRowCanExpand = () => true, expandOnRowClick, diff --git a/app/react/components/datatables/ExpandableDatatableRow.tsx b/app/react/components/datatables/ExpandableDatatableRow.tsx index 6edc3e821..bcf9da237 100644 --- a/app/react/components/datatables/ExpandableDatatableRow.tsx +++ b/app/react/components/datatables/ExpandableDatatableRow.tsx @@ -2,15 +2,16 @@ import { ReactNode } from 'react'; import { Row } from '@tanstack/react-table'; import { TableRow } from './TableRow'; +import { DefaultType } from './types'; -interface Props> { +interface Props { row: Row; disableSelect?: boolean; renderSubRow(row: Row): ReactNode; expandOnClick?: boolean; } -export function ExpandableDatatableTableRow>({ +export function ExpandableDatatableTableRow({ row, disableSelect, renderSubRow, diff --git a/app/react/components/datatables/Filter.tsx b/app/react/components/datatables/Filter.tsx index c47eabf30..371eb86bf 100644 --- a/app/react/components/datatables/Filter.tsx +++ b/app/react/components/datatables/Filter.tsx @@ -8,6 +8,8 @@ import { getValueAsArrayOfStrings } from '@/portainer/helpers/array'; import { Icon } from '@@/Icon'; +import { DefaultType } from './types'; + interface MultipleSelectionFilterProps { options: string[]; value: string[]; @@ -70,9 +72,7 @@ export function MultipleSelectionFilter({ } } -export function filterHOC>( - menuTitle: string -) { +export function filterHOC(menuTitle: string) { return function Filter({ column: { getFilterValue, setFilterValue, getFacetedRowModel, id }, }: { diff --git a/app/react/components/datatables/NestedDatatable.tsx b/app/react/components/datatables/NestedDatatable.tsx index dff600a68..1b0eaaa3b 100644 --- a/app/react/components/datatables/NestedDatatable.tsx +++ b/app/react/components/datatables/NestedDatatable.tsx @@ -12,9 +12,9 @@ import { defaultGetRowId } from './defaultGetRowId'; import { Table } from './Table'; import { NestedTable } from './NestedTable'; import { DatatableContent } from './DatatableContent'; -import { BasicTableSettings } from './types'; +import { BasicTableSettings, DefaultType } from './types'; -interface Props> { +interface Props { dataset: D[]; columns: TableOptions['columns']; @@ -25,7 +25,7 @@ interface Props> { initialSortBy?: BasicTableSettings['sortBy']; } -export function NestedDatatable>({ +export function NestedDatatable({ columns, dataset, getRowId = defaultGetRowId, diff --git a/app/react/components/datatables/TableContent.tsx b/app/react/components/datatables/TableContent.tsx index 80f692dc5..a76dc8ea6 100644 --- a/app/react/components/datatables/TableContent.tsx +++ b/app/react/components/datatables/TableContent.tsx @@ -1,16 +1,16 @@ import { Fragment, PropsWithChildren } from 'react'; import { Row } from '@tanstack/react-table'; -interface Props = Record> { +import { DefaultType } from './types'; + +interface Props { isLoading?: boolean; rows: Row[]; emptyContent?: string; renderRow(row: Row): React.ReactNode; } -export function TableContent< - T extends Record = Record ->({ +export function TableContent({ isLoading = false, rows, emptyContent = 'No items available', diff --git a/app/react/components/datatables/TableHeaderRow.tsx b/app/react/components/datatables/TableHeaderRow.tsx index b3fdb3b42..56fe28fdc 100644 --- a/app/react/components/datatables/TableHeaderRow.tsx +++ b/app/react/components/datatables/TableHeaderRow.tsx @@ -2,15 +2,17 @@ import { Header, flexRender } from '@tanstack/react-table'; import { filterHOC } from './Filter'; import { TableHeaderCell } from './TableHeaderCell'; +import { DefaultType } from './types'; -interface Props = Record> { +interface Props { headers: Header[]; onSortChange?(colId: string, desc: boolean): void; } -export function TableHeaderRow< - D extends Record = Record ->({ headers, onSortChange }: Props) { +export function TableHeaderRow({ + headers, + onSortChange, +}: Props) { return ( {headers.map((header) => { diff --git a/app/react/components/datatables/TableRow.tsx b/app/react/components/datatables/TableRow.tsx index 2b3bda61e..de7426448 100644 --- a/app/react/components/datatables/TableRow.tsx +++ b/app/react/components/datatables/TableRow.tsx @@ -1,15 +1,19 @@ import { Cell, flexRender } from '@tanstack/react-table'; import clsx from 'clsx'; -interface Props = Record> { +import { DefaultType } from './types'; + +interface Props { cells: Cell[]; className?: string; onClick?: () => void; } -export function TableRow< - D extends Record = Record ->({ cells, className, onClick }: Props) { +export function TableRow({ + cells, + className, + onClick, +}: Props) { return ( >( +import { DefaultType } from './types'; +import { defaultGetRowId } from './defaultGetRowId'; + +export function buildNameColumn( nameKey: keyof T, - idKey: string, path: string, - idParam = 'id' + idParam = 'id', + idGetter: (row: T) => string = defaultGetRowId ): ColumnDef { - const cell = createCell(); + const cell = createCell(); return { header: 'Name', @@ -19,7 +22,7 @@ export function buildNameColumn>( enableHiding: false, }; - function createCell>() { + function createCell() { return function NameCell({ renderValue, row }: CellContext) { const name = renderValue() || ''; @@ -30,7 +33,7 @@ export function buildNameColumn>( return ( {name} diff --git a/app/react/components/datatables/defaultGetRowId.ts b/app/react/components/datatables/defaultGetRowId.ts index dbde4b085..722edb2c8 100644 --- a/app/react/components/datatables/defaultGetRowId.ts +++ b/app/react/components/datatables/defaultGetRowId.ts @@ -1,15 +1,24 @@ -export function defaultGetRowId>( - row: D -): string { - if (row.id && (typeof row.id === 'string' || typeof row.id === 'number')) { +import { DefaultType } from './types'; + +export function defaultGetRowId(row: D): string { + if ( + 'id' in row && + (typeof row.id === 'string' || typeof row.id === 'number') + ) { return row.id.toString(); } - if (row.Id && (typeof row.Id === 'string' || typeof row.Id === 'number')) { + if ( + 'Id' in row && + (typeof row.Id === 'string' || typeof row.Id === 'number') + ) { return row.Id.toString(); } - if (row.ID && (typeof row.ID === 'string' || typeof row.ID === 'number')) { + if ( + 'ID' in row && + (typeof row.ID === 'string' || typeof row.ID === 'number') + ) { return row.ID.toString(); } diff --git a/app/react/components/datatables/expand-column.tsx b/app/react/components/datatables/expand-column.tsx index 33a86d681..ce011991c 100644 --- a/app/react/components/datatables/expand-column.tsx +++ b/app/react/components/datatables/expand-column.tsx @@ -3,9 +3,9 @@ import { ColumnDef } from '@tanstack/react-table'; import { Button } from '@@/buttons'; -export function buildExpandColumn< - T extends Record ->(): ColumnDef { +import { DefaultType } from './types'; + +export function buildExpandColumn(): ColumnDef { return { id: 'expand', header: ({ table }) => { diff --git a/app/react/components/datatables/filter-types.ts b/app/react/components/datatables/filter-types.ts index 6ab102d4d..4be2bf297 100644 --- a/app/react/components/datatables/filter-types.ts +++ b/app/react/components/datatables/filter-types.ts @@ -1,8 +1,12 @@ import { Row } from '@tanstack/react-table'; -export function multiple< - D extends Record = Record ->({ getValue }: Row, columnId: string, filterValue: string[]): boolean { +import { DefaultType } from './types'; + +export function multiple( + { getValue }: Row, + columnId: string, + filterValue: string[] +): boolean { if (filterValue.length === 0) { return true; } diff --git a/app/react/components/datatables/types.ts b/app/react/components/datatables/types.ts index 321802b7a..94e8ab5bf 100644 --- a/app/react/components/datatables/types.ts +++ b/app/react/components/datatables/types.ts @@ -3,6 +3,8 @@ import { persist } from 'zustand/middleware'; import { keyBuilder } from '@/react/hooks/useLocalStorage'; +export type DefaultType = object; + export interface PaginationTableSettings { pageSize: number; setPageSize: (pageSize: number) => void; diff --git a/app/react/docker/configs/ListView/ConfigsDatatable/columns.tsx b/app/react/docker/configs/ListView/ConfigsDatatable/columns.tsx index ac08b2478..5a3445332 100644 --- a/app/react/docker/configs/ListView/ConfigsDatatable/columns.tsx +++ b/app/react/docker/configs/ListView/ConfigsDatatable/columns.tsx @@ -3,14 +3,14 @@ import { createColumnHelper } from '@tanstack/react-table'; import { isoDate } from '@/portainer/filters/filters'; import { createOwnershipColumn } from '@/react/docker/components/datatable-helpers/createOwnershipColumn'; -import { buildNameColumn } from '@@/datatables/NameCell'; +import { buildNameColumn } from '@@/datatables/buildNameColumn'; import { DockerConfig } from '../../types'; const columnHelper = createColumnHelper(); export const columns = [ - buildNameColumn('Name', 'Id', 'docker.configs.config'), + buildNameColumn('Name', 'docker.configs.config'), columnHelper.accessor('CreatedAt', { header: 'Creation Date', cell: ({ getValue }) => { diff --git a/app/react/edge/edge-stacks/ListView/EdgeStacksDatatable/columns.tsx b/app/react/edge/edge-stacks/ListView/EdgeStacksDatatable/columns.tsx index 354b9669a..84bdf418d 100644 --- a/app/react/edge/edge-stacks/ListView/EdgeStacksDatatable/columns.tsx +++ b/app/react/edge/edge-stacks/ListView/EdgeStacksDatatable/columns.tsx @@ -4,7 +4,7 @@ import _ from 'lodash'; import { isoDateFromTimestamp } from '@/portainer/filters/filters'; import { isBE } from '@/react/portainer/feature-flags/feature-flags.service'; -import { buildNameColumn } from '@@/datatables/NameCell'; +import { buildNameColumn } from '@@/datatables/buildNameColumn'; import { Link } from '@@/Link'; import { StatusType } from '../../types'; @@ -16,12 +16,7 @@ import { DeploymentCounter } from './DeploymentCounter'; const columnHelper = createColumnHelper(); export const columns = _.compact([ - buildNameColumn( - 'Name', - 'Id', - 'edge.stacks.edit', - 'stackId' - ), + buildNameColumn('Name', 'edge.stacks.edit', 'stackId'), columnHelper.accessor( (item) => item.aggregatedStatus[StatusType.Acknowledged] || 0, { diff --git a/app/react/portainer/environments/update-schedules/ListView/columns/index.ts b/app/react/portainer/environments/update-schedules/ListView/columns/index.ts index ceffc063a..5b99dd04c 100644 --- a/app/react/portainer/environments/update-schedules/ListView/columns/index.ts +++ b/app/react/portainer/environments/update-schedules/ListView/columns/index.ts @@ -1,4 +1,4 @@ -import { buildNameColumn } from '@@/datatables/NameCell'; +import { buildNameColumn } from '@@/datatables/buildNameColumn'; import { EdgeUpdateListItemResponse } from '../../queries/list'; @@ -9,7 +9,7 @@ import { scheduledTime } from './scheduled-time'; import { scheduleType } from './type'; export const columns = [ - buildNameColumn('name', 'id', '.item'), + buildNameColumn('name', '.item'), scheduledTime, groups, scheduleType, diff --git a/app/react/portainer/settings/EdgeComputeView/FDOProfilesDatatable/columns/index.tsx b/app/react/portainer/settings/EdgeComputeView/FDOProfilesDatatable/columns/index.tsx index 6983ca15c..01ae237ee 100644 --- a/app/react/portainer/settings/EdgeComputeView/FDOProfilesDatatable/columns/index.tsx +++ b/app/react/portainer/settings/EdgeComputeView/FDOProfilesDatatable/columns/index.tsx @@ -1,10 +1,10 @@ import { Profile } from '@/portainer/hostmanagement/fdo/model'; -import { buildNameColumn } from '@@/datatables/NameCell'; +import { buildNameColumn } from '@@/datatables/buildNameColumn'; import { created } from './created'; export const columns = [ - buildNameColumn('name', 'id', 'portainer.endpoints.profile.edit'), + buildNameColumn('name', 'portainer.endpoints.profile.edit'), created, ]; diff --git a/app/react/portainer/users/teams/ListView/TeamsDatatable/TeamsDatatable.tsx b/app/react/portainer/users/teams/ListView/TeamsDatatable/TeamsDatatable.tsx index 9312eeb6f..e266c839d 100644 --- a/app/react/portainer/users/teams/ListView/TeamsDatatable/TeamsDatatable.tsx +++ b/app/react/portainer/users/teams/ListView/TeamsDatatable/TeamsDatatable.tsx @@ -10,14 +10,14 @@ import { deleteTeam } from '@/react/portainer/users/teams/teams.service'; import { confirmDelete } from '@@/modals/confirm'; import { Datatable } from '@@/datatables'; import { Button } from '@@/buttons'; -import { buildNameColumn } from '@@/datatables/NameCell'; +import { buildNameColumn } from '@@/datatables/buildNameColumn'; import { createPersistedStore } from '@@/datatables/types'; import { useTableState } from '@@/datatables/useTableState'; const storageKey = 'teams'; const columns: ColumnDef[] = [ - buildNameColumn('Name', 'Id', 'portainer.teams.team'), + buildNameColumn('Name', 'portainer.teams.team'), ]; interface Props {