Skip to content

Commit

Permalink
fix(tangle-dapp): Fix sort/scroll issues on nomination selection list…
Browse files Browse the repository at this point in the history
… modal (#2430)

Co-authored-by: Trung-Tin Pham <[email protected]>
  • Loading branch information
vutuanlinh2k2 and AtelyPham authored Jul 11, 2024
1 parent 4056915 commit 84e78c0
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 156 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
'use client';

import { BN } from '@polkadot/util';
import {
type Column,
type ColumnSort,
createColumnHelper,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
type PaginationState,
type Row,
type RowSelectionState,
type SortingColumn,
type SortingFn,
type SortingState,
type TableOptions,
useReactTable,
} from '@tanstack/react-table';
import { ArrowDropDownFill, ArrowDropUpFill, Search } from '@webb-tools/icons';
import { Search } from '@webb-tools/icons';
import {
Avatar,
CheckBox,
Expand All @@ -41,27 +38,22 @@ import React, {

import { Validator } from '../../types';
import calculateCommission from '../../utils/calculateCommission';
import {
sortAddressOrIdentityForNomineeOrValidator,
sortBnValueForNomineeOrValidator,
} from '../../utils/table';
import { ContainerSkeleton } from '..';
import { HeaderCell } from '../tableCells';
import TokenAmountCell from '../tableCells/TokenAmountCell';
import { ValidatorSelectionTableProps } from './types';

const DEFAULT_PAGINATION: PaginationState = {
pageIndex: 0,
pageSize: 20,
};

const SELECTED_VALIDATORS_COLUMN_SORT = {
id: 'address',
desc: false,
} as const satisfies ColumnSort;

const columnHelper = createColumnHelper<Validator>();

const ValidatorSelectionTable: FC<ValidatorSelectionTableProps> = ({
allValidators,
defaultSelectedValidators,
setSelectedValidators,
pageSize = 20,
}) => {
const [searchValue, setSearchValue] = useState('');
const [rowSelection, setRowSelection] = useState<RowSelectionState>(
Expand All @@ -71,11 +63,13 @@ const ValidatorSelectionTable: FC<ValidatorSelectionTableProps> = ({
}, {} as RowSelectionState),
);
const [sorting, setSorting] = useState<SortingState>([
SELECTED_VALIDATORS_COLUMN_SORT,
{
id: 'totalStakeAmount',
desc: true,
},
]);

const [pagination, setPagination] =
useState<PaginationState>(DEFAULT_PAGINATION);
const isDesc = useMemo(() => sorting[0].desc, [sorting]);

const toggleSortSelectionHandlerRef = useRef<
SortingColumn<Validator>['toggleSorting'] | null
Expand All @@ -91,10 +85,9 @@ const ValidatorSelectionTable: FC<ValidatorSelectionTableProps> = ({
const columns = useMemo(
() => [
columnHelper.accessor('address', {
header: ({ header }) => {
toggleSortSelectionHandlerRef.current = header.column.toggleSorting;
return <HeaderCell title="Validator" className="justify-start" />;
},
header: () => (
<HeaderCell title="Validator" className="justify-start" />
),
cell: (props) => {
const address = props.getValue();
const identity = props.row.original.identityName;
Expand Down Expand Up @@ -123,134 +116,116 @@ const ValidatorSelectionTable: FC<ValidatorSelectionTableProps> = ({
textToCopy={address}
isButton={false}
className="cursor-pointer"
iconClassName="!fill-mono-160 dark:!fill-mono-80"
/>
</div>
</div>
);
},
// Sort the selected validators first
sortingFn: (rowA, rowB) => {
const rowASelected = rowA.getIsSelected();
const rowBSelected = rowB.getIsSelected();

if (rowASelected && !rowBSelected) {
return -1;
}

if (!rowASelected && rowBSelected) {
return 1;
}

return 0;
},
sortingFn: (rowA, rowB, columnId) =>
sortValidatorsBasedOnSortingFn(
rowA,
rowB,
columnId,
sortAddressOrIdentityForNomineeOrValidator,
isDesc,
),
}),
columnHelper.accessor('totalStakeAmount', {
header: ({ header }) => (
<div
className="flex items-center justify-center cursor-pointer"
onClick={header.column.getToggleSortingHandler()}
>
<HeaderCell title="Total Staked" className="flex-none block" />

<SortArrow column={header.column} />
</div>
header: () => (
<HeaderCell title="Total Staked" className="justify-start" />
),
cell: (props) => (
<div className="flex items-center justify-center">
<div className="flex items-center justify-start">
<Chip color="dark-grey" className="normal-case">
<TokenAmountCell amount={props.getValue()} />
</Chip>
</div>
),
sortingFn,
sortingFn: (rowA, rowB, columnId) =>
sortValidatorsBasedOnSortingFn(
rowA,
rowB,
columnId,
sortBnValueForNomineeOrValidator,
isDesc,
),
}),
columnHelper.accessor('nominatorCount', {
header: ({ header }) => (
<div
className="flex items-center justify-center cursor-pointer"
onClick={header.column.getToggleSortingHandler()}
>
<HeaderCell title="Nominations" className="flex-none block" />

<SortArrow column={header.column} />
</div>
header: () => (
<HeaderCell title="Nominations" className="justify-start" />
),
cell: (props) => (
<div className="flex items-center justify-center">
<div className="flex items-center justify-start">
<Chip color="dark-grey">{props.getValue()}</Chip>
</div>
),
sortingFn,
sortingFn: (rowA, rowB, columnId) =>
sortValidatorsBasedOnSortingFn(
rowA,
rowB,
columnId,
(rowA, rowB) =>
rowA.original.nominatorCount - rowB.original.nominatorCount,
isDesc,
),
}),
columnHelper.accessor('commission', {
header: ({ header }) => (
<div
className="flex items-center justify-center cursor-pointer"
onClick={header.column.getToggleSortingHandler()}
>
<HeaderCell title="Commission" className="flex-none block" />

<SortArrow column={header.column} />
</div>
header: () => (
<HeaderCell title="Commission" className="justify-start" />
),
cell: (props) => (
<div className="flex items-center justify-center">
<div className="flex items-center justify-start">
<Chip color="dark-grey">
{calculateCommission(props.getValue()).toFixed(2)}%
</Chip>
</div>
),
sortingFn,
sortingFn: (rowA, rowB, columnId) =>
sortValidatorsBasedOnSortingFn(
rowA,
rowB,
columnId,
sortBnValueForNomineeOrValidator,
isDesc,
),
}),
columnHelper.accessor('identityName', {
header: () => <HeaderCell title="Identity" />,
cell: (props) => props.getValue(),
}),
],
[],
[isDesc],
);

const tableProps = useMemo<TableOptions<Validator>>(
() => ({
data: allValidators,
columns,
initialState: {
pagination: {
pageSize,
},
},
state: {
columnVisibility: {
identityName: false,
},
sorting,
rowSelection,
pagination,
globalFilter: searchValue,
},
enableRowSelection: true,
onPaginationChange: setPagination,
onGlobalFilterChange: (props) => {
setPagination(DEFAULT_PAGINATION);
setSearchValue(props);
},
onRowSelectionChange: (props) => {
toggleSortSelectionHandlerRef.current?.(false);
setRowSelection(props);
// Force re-sort after selecting an item
setSorting((prevSorting) => [...prevSorting]);
},
onSortingChange: (updaterOrValue) => {
if (typeof updaterOrValue === 'function') {
setSorting((prev) => {
const newSorting = updaterOrValue(prev);

// Modify the sorting state to always sort by the selected validators first
if (newSorting.length === 0) {
return [SELECTED_VALIDATORS_COLUMN_SORT];
} else if (newSorting[0].id === 'address') {
return newSorting;
} else {
return [SELECTED_VALIDATORS_COLUMN_SORT, ...newSorting];
}
});
} else {
setSorting(updaterOrValue);
}
},
onSortingChange: setSorting,
filterFns: {
fuzzy: fuzzyFilter,
},
Expand All @@ -261,8 +236,9 @@ const ValidatorSelectionTable: FC<ValidatorSelectionTableProps> = ({
getSortedRowModel: getSortedRowModel(),
getRowId: (row) => row.address,
autoResetPageIndex: false,
enableSortingRemoval: false,
}),
[columns, allValidators, pagination, rowSelection, searchValue, sorting],
[allValidators, columns, rowSelection, searchValue, sorting, pageSize],
);

const table = useReactTable(tableProps);
Expand Down Expand Up @@ -295,73 +271,35 @@ const ValidatorSelectionTable: FC<ValidatorSelectionTableProps> = ({
)}
</div>

<Typography
variant="body1"
fw="normal"
className="text-mono-200 dark:text-mono-0"
>
Selected: {Object.keys(rowSelection).length}/
{table.getPreFilteredRowModel().rows.length}
</Typography>
{allValidators.length > 0 && (
<Typography
variant="body1"
fw="normal"
className="text-mono-200 dark:text-mono-0"
>
Selected: {Object.keys(rowSelection).length}/
{table.getPreFilteredRowModel().rows.length}
</Typography>
)}
</>
);
};

type ColumnIdAssertFn = (
columnId: string,
) => asserts columnId is keyof Validator;

const assertColumnId: ColumnIdAssertFn = (columnId) => {
if (
![
'address',
'effectiveAmountStaked',
'delegations',
'commission',
'nominatorCount',
'totalStakeAmount',
].includes(columnId)
) {
throw new Error(`Invalid columnId: ${columnId}`);
}
};
export default React.memo(ValidatorSelectionTable);

const sortingFn = (
function sortValidatorsBasedOnSortingFn(
rowA: Row<Validator>,
rowB: Row<Validator>,
columnId: string,
) => {
assertColumnId(columnId);

if (columnId === 'totalStakeAmount') {
const totalStakedA = rowA.original.totalStakeAmount;
const totalStakedB = rowB.original.totalStakeAmount;
const result = totalStakedA.sub(totalStakedB);

return result.ltn(0) ? -1 : result.gtn(0) ? 1 : 0;
}

// TODO: Avoid using Number() here, if it is a BN value, it should be compared as such.
const rowAValue = Number(rowA.original[columnId]);
const rowBValue = Number(rowB.original[columnId]);

return rowAValue - rowBValue;
};

const SortArrow: FC<{ column: Column<Validator, BN | number> }> = ({
column,
}) => {
const isSorted = column.getIsSorted();

if (!isSorted) {
return null;
}

return isSorted === 'asc' ? (
<ArrowDropUpFill className="cursor-pointer" size="lg" />
) : (
<ArrowDropDownFill className="cursor-pointer" size="lg" />
);
};

export default React.memo(ValidatorSelectionTable);
sortFn: SortingFn<Validator>,
isDesc: boolean,
) {
const rowASelected = rowA.getIsSelected();
const rowBSelected = rowB.getIsSelected();

// Prioritize selected validators
if (rowASelected && !rowBSelected) return isDesc ? 1 : -1;
if (!rowASelected && rowBSelected) return isDesc ? -1 : 1;

return sortFn(rowA, rowB, columnId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type ValidatorSelectionTableProps = {
allValidators: Validator[];
defaultSelectedValidators: string[];
setSelectedValidators: Dispatch<SetStateAction<Set<string>>>;
pageSize?: number;
};

export type SortBy = 'asc' | 'dsc';
Expand Down
Loading

0 comments on commit 84e78c0

Please sign in to comment.