Commit fbba31f9 authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Merge pull request #1378 from blockscout/fe-1210

Sorting in tables v0.5
parents 2d7ce289 9f9707ca
...@@ -58,9 +58,16 @@ import type { ...@@ -58,9 +58,16 @@ import type {
} from 'types/api/token'; } from 'types/api/token';
import type { TokensResponse, TokensFilters, TokensSorting, TokenInstanceTransferResponse, TokensBridgedFilters } from 'types/api/tokens'; import type { TokensResponse, TokensFilters, TokensSorting, TokenInstanceTransferResponse, TokensBridgedFilters } from 'types/api/tokens';
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer'; import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction, TransactionsResponseWatchlist } from 'types/api/transaction'; import type {
TransactionsResponseValidated,
TransactionsResponsePending,
Transaction,
TransactionsResponseWatchlist,
TransactionsSorting,
} from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters'; import type { TTxsFilters } from 'types/api/txsFilters';
import type { TxStateChanges } from 'types/api/txStateChanges'; import type { TxStateChanges } from 'types/api/txStateChanges';
import type { VerifiedContractsSorting } from 'types/api/verifiedContracts';
import type { VisualizedContract } from 'types/api/visualization'; import type { VisualizedContract } from 'types/api/visualization';
import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals'; import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals';
import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem, ZkEvmL2TxnBatchesResponse, ZkEvmL2TxnBatchTxs } from 'types/api/zkEvmL2TxnBatches'; import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem, ZkEvmL2TxnBatchesResponse, ZkEvmL2TxnBatchTxs } from 'types/api/zkEvmL2TxnBatches';
...@@ -725,5 +732,7 @@ never; ...@@ -725,5 +732,7 @@ never;
export type PaginationSorting<Q extends PaginatedResources> = export type PaginationSorting<Q extends PaginatedResources> =
Q extends 'tokens' ? TokensSorting : Q extends 'tokens' ? TokensSorting :
Q extends 'tokens_bridged' ? TokensSorting : Q extends 'tokens_bridged' ? TokensSorting :
Q extends 'verified_contracts' ? VerifiedContractsSorting :
Q extends 'address_txs' ? TransactionsSorting :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
import type { Transaction } from 'types/api/transaction';
import type { Sort } from 'types/client/txs-sort';
import compareBns from 'lib/bigint/compareBns';
const sortTxs = (sorting?: Sort) => (tx1: Transaction, tx2: Transaction) => {
switch (sorting) {
case 'val-desc':
return compareBns(tx1.value, tx2.value);
case 'val-asc':
return compareBns(tx2.value, tx1.value);
case 'fee-desc':
return compareBns(tx1.fee.value || 0, tx2.fee.value || 0);
case 'fee-asc':
return compareBns(tx2.fee.value || 0, tx1.fee.value || 0);
default:
return 0;
}
};
export default sortTxs;
...@@ -117,3 +117,12 @@ export type TransactionType = 'rootstock_remasc' | ...@@ -117,3 +117,12 @@ export type TransactionType = 'rootstock_remasc' |
'coin_transfer' 'coin_transfer'
export type TxsResponse = TransactionsResponseValidated | TransactionsResponsePending | BlockTransactionsResponse; export type TxsResponse = TransactionsResponseValidated | TransactionsResponsePending | BlockTransactionsResponse;
export interface TransactionsSorting {
sort: 'value' | 'fee';
order: 'asc' | 'desc';
}
export type TransactionsSortingField = TransactionsSorting['sort'];
export type TransactionsSortingValue = `${ TransactionsSortingField }-${ TransactionsSorting['order'] }`;
export interface VerifiedContractsSorting {
sort: 'balance' | 'txs_count';
order: 'asc' | 'desc';
}
export type VerifiedContractsSortingField = VerifiedContractsSorting['sort'];
export type VerifiedContractsSortingValue = `${ VerifiedContractsSortingField }-${ VerifiedContractsSorting['order'] }`;
export type Sort = 'val-desc' | 'val-asc' | 'fee-desc' | 'fee-asc' | '';
...@@ -5,7 +5,7 @@ import React from 'react'; ...@@ -5,7 +5,7 @@ import React from 'react';
import type { SocketMessage } from 'lib/socket/types'; import type { SocketMessage } from 'lib/socket/types';
import type { AddressFromToFilter, AddressTransactionsResponse } from 'types/api/address'; import type { AddressFromToFilter, AddressTransactionsResponse } from 'types/api/address';
import { AddressFromToFilterValues } from 'types/api/address'; import { AddressFromToFilterValues } from 'types/api/address';
import type { Transaction } from 'types/api/transaction'; import type { Transaction, TransactionsSortingField, TransactionsSortingValue, TransactionsSorting } from 'types/api/transaction';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery'; import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
...@@ -18,7 +18,10 @@ import { generateListStub } from 'stubs/utils'; ...@@ -18,7 +18,10 @@ import { generateListStub } from 'stubs/utils';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import TxsContent from 'ui/txs/TxsContent'; import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import TxsWithAPISorting from 'ui/txs/TxsWithAPISorting';
import { SORT_OPTIONS } from 'ui/txs/useTxsSort';
import AddressCsvExportLink from './AddressCsvExportLink'; import AddressCsvExportLink from './AddressCsvExportLink';
import AddressTxsFilter from './AddressTxsFilter'; import AddressTxsFilter from './AddressTxsFilter';
...@@ -53,6 +56,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { ...@@ -53,6 +56,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
const [ socketAlert, setSocketAlert ] = React.useState(''); const [ socketAlert, setSocketAlert ] = React.useState('');
const [ newItemsCount, setNewItemsCount ] = React.useState(0); const [ newItemsCount, setNewItemsCount ] = React.useState(0);
const [ sort, setSort ] = React.useState<TransactionsSortingValue | undefined>(getSortValueFromQuery<TransactionsSortingValue>(router.query, SORT_OPTIONS));
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const currentAddress = getQueryParamString(router.query.hash); const currentAddress = getQueryParamString(router.query.hash);
...@@ -63,6 +67,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { ...@@ -63,6 +67,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
resourceName: 'address_txs', resourceName: 'address_txs',
pathParams: { hash: currentAddress }, pathParams: { hash: currentAddress },
filters: { filter: filterValue }, filters: { filter: filterValue },
sorting: getSortParamsFromValue<TransactionsSortingValue, TransactionsSortingField, TransactionsSorting['order']>(sort),
scrollRef, scrollRef,
options: { options: {
placeholderData: generateListStub<'address_txs'>(TX, 50, { next_page_params: { placeholderData: generateListStub<'address_txs'>(TX, 50, { next_page_params: {
...@@ -177,7 +182,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { ...@@ -177,7 +182,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
<Pagination { ...addressTxsQuery.pagination } ml={ 8 }/> <Pagination { ...addressTxsQuery.pagination } ml={ 8 }/>
</ActionBar> </ActionBar>
) } ) }
<TxsContent <TxsWithAPISorting
filter={ filter } filter={ filter }
filterValue={ filterValue } filterValue={ filterValue }
query={ addressTxsQuery } query={ addressTxsQuery }
...@@ -187,6 +192,8 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { ...@@ -187,6 +192,8 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => {
socketInfoAlert={ socketAlert } socketInfoAlert={ socketAlert }
socketInfoNum={ newItemsCount } socketInfoNum={ newItemsCount }
top={ 80 } top={ 80 }
sorting={ sort }
setSort={ setSort }
/> />
</> </>
); );
......
...@@ -24,7 +24,7 @@ import Pagination from 'ui/shared/pagination/Pagination'; ...@@ -24,7 +24,7 @@ import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import TxsContent from 'ui/txs/TxsContent'; import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting';
const TAB_LIST_PROPS = { const TAB_LIST_PROPS = {
marginBottom: 0, marginBottom: 0,
...@@ -82,7 +82,7 @@ const BlockPageContent = () => { ...@@ -82,7 +82,7 @@ const BlockPageContent = () => {
const tabs: Array<RoutedTab> = React.useMemo(() => ([ const tabs: Array<RoutedTab> = React.useMemo(() => ([
{ id: 'index', title: 'Details', component: <BlockDetails query={ blockQuery }/> }, { id: 'index', title: 'Details', component: <BlockDetails query={ blockQuery }/> },
{ id: 'txs', title: 'Transactions', component: <TxsContent query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false }/> }, { id: 'txs', title: 'Transactions', component: <TxsWithFrontendSorting query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false }/> },
config.features.beaconChain.isEnabled && Boolean(blockQuery.data?.withdrawals_count) ? config.features.beaconChain.isEnabled && Boolean(blockQuery.data?.withdrawals_count) ?
{ id: 'withdrawals', title: 'Withdrawals', component: <BlockWithdrawals blockWithdrawalsQuery={ blockWithdrawalsQuery }/> } : { id: 'withdrawals', title: 'Withdrawals', component: <BlockWithdrawals blockWithdrawalsQuery={ blockWithdrawalsQuery }/> } :
null, null,
......
...@@ -7,7 +7,7 @@ import { generateListStub } from 'stubs/utils'; ...@@ -7,7 +7,7 @@ import { generateListStub } from 'stubs/utils';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import TxsContent from 'ui/txs/TxsContent'; import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting';
const KettleTxs = () => { const KettleTxs = () => {
const router = useRouter(); const router = useRouter();
...@@ -31,7 +31,7 @@ const KettleTxs = () => { ...@@ -31,7 +31,7 @@ const KettleTxs = () => {
<> <>
<PageTitle title="Computor transactions" withTextAd/> <PageTitle title="Computor transactions" withTextAd/>
<AddressEntity address={{ hash }} mb={ 6 }/> <AddressEntity address={{ hash }} mb={ 6 }/>
<TxsContent <TxsWithFrontendSorting
query={ query } query={ query }
showSocketInfo={ false } showSocketInfo={ false }
/> />
......
...@@ -3,7 +3,7 @@ import { useRouter } from 'next/router'; ...@@ -3,7 +3,7 @@ import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { TokenType } from 'types/api/token'; import type { TokenType } from 'types/api/token';
import type { TokensSortingValue } from 'types/api/tokens'; import type { TokensSortingValue, TokensSortingField, TokensSorting } from 'types/api/tokens';
import type { RoutedTab } from 'ui/shared/Tabs/types'; import type { RoutedTab } from 'ui/shared/Tabs/types';
import config from 'configs/app'; import config from 'configs/app';
...@@ -16,11 +16,13 @@ import PopoverFilter from 'ui/shared/filters/PopoverFilter'; ...@@ -16,11 +16,13 @@ import PopoverFilter from 'ui/shared/filters/PopoverFilter';
import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter'; import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TokensList from 'ui/tokens/Tokens'; import TokensList from 'ui/tokens/Tokens';
import TokensActionBar from 'ui/tokens/TokensActionBar'; import TokensActionBar from 'ui/tokens/TokensActionBar';
import TokensBridgedChainsFilter from 'ui/tokens/TokensBridgedChainsFilter'; import TokensBridgedChainsFilter from 'ui/tokens/TokensBridgedChainsFilter';
import { getSortParamsFromValue, getSortValueFromQuery, getTokenFilterValue, getBridgedChainsFilterValue } from 'ui/tokens/utils'; import { SORT_OPTIONS, getTokenFilterValue, getBridgedChainsFilterValue } from 'ui/tokens/utils';
const TAB_LIST_PROPS = { const TAB_LIST_PROPS = {
marginBottom: 0, marginBottom: 0,
...@@ -44,7 +46,7 @@ const Tokens = () => { ...@@ -44,7 +46,7 @@ const Tokens = () => {
const q = getQueryParamString(router.query.q); const q = getQueryParamString(router.query.q);
const [ searchTerm, setSearchTerm ] = React.useState<string>(q ?? ''); const [ searchTerm, setSearchTerm ] = React.useState<string>(q ?? '');
const [ sort, setSort ] = React.useState<TokensSortingValue | undefined>(getSortValueFromQuery(router.query)); const [ sort, setSort ] = React.useState<TokensSortingValue | undefined>(getSortValueFromQuery<TokensSortingValue>(router.query, SORT_OPTIONS));
const [ tokenTypes, setTokenTypes ] = React.useState<Array<TokenType> | undefined>(getTokenFilterValue(router.query.type)); const [ tokenTypes, setTokenTypes ] = React.useState<Array<TokenType> | undefined>(getTokenFilterValue(router.query.type));
const [ bridgeChains, setBridgeChains ] = React.useState<Array<string> | undefined>(getBridgedChainsFilterValue(router.query.chain_ids)); const [ bridgeChains, setBridgeChains ] = React.useState<Array<string> | undefined>(getBridgedChainsFilterValue(router.query.chain_ids));
...@@ -53,7 +55,7 @@ const Tokens = () => { ...@@ -53,7 +55,7 @@ const Tokens = () => {
const tokensQuery = useQueryWithPages({ const tokensQuery = useQueryWithPages({
resourceName: tab === 'bridged' ? 'tokens_bridged' : 'tokens', resourceName: tab === 'bridged' ? 'tokens_bridged' : 'tokens',
filters: tab === 'bridged' ? { q: debouncedSearchTerm, chain_ids: bridgeChains } : { q: debouncedSearchTerm, type: tokenTypes }, filters: tab === 'bridged' ? { q: debouncedSearchTerm, chain_ids: bridgeChains } : { q: debouncedSearchTerm, type: tokenTypes },
sorting: getSortParamsFromValue(sort), sorting: getSortParamsFromValue<TokensSortingValue, TokensSortingField, TokensSorting['order']>(sort),
options: { options: {
placeholderData: generateListStub<'tokens'>( placeholderData: generateListStub<'tokens'>(
TOKEN_INFO_ERC_20, TOKEN_INFO_ERC_20,
......
...@@ -13,8 +13,8 @@ import PageTitle from 'ui/shared/Page/PageTitle'; ...@@ -13,8 +13,8 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TxsContent from 'ui/txs/TxsContent';
import TxsWatchlist from 'ui/txs/TxsWatchlist'; import TxsWatchlist from 'ui/txs/TxsWatchlist';
import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting';
const TAB_LIST_PROPS = { const TAB_LIST_PROPS = {
marginBottom: 0, marginBottom: 0,
...@@ -60,12 +60,13 @@ const Transactions = () => { ...@@ -60,12 +60,13 @@ const Transactions = () => {
{ {
id: 'validated', id: 'validated',
title: verifiedTitle, title: verifiedTitle,
component: <TxsContent query={ txsQuery } showSocketInfo={ txsQuery.pagination.page === 1 } socketInfoNum={ num } socketInfoAlert={ socketAlert }/> }, component:
<TxsWithFrontendSorting query={ txsQuery } showSocketInfo={ txsQuery.pagination.page === 1 } socketInfoNum={ num } socketInfoAlert={ socketAlert }/> },
{ {
id: 'pending', id: 'pending',
title: 'Pending', title: 'Pending',
component: ( component: (
<TxsContent <TxsWithFrontendSorting
query={ txsQuery } query={ txsQuery }
showBlockInfo={ false } showBlockInfo={ false }
showSocketInfo={ txsQuery.pagination.page === 1 } showSocketInfo={ txsQuery.pagination.page === 1 }
......
...@@ -3,6 +3,7 @@ import { useRouter } from 'next/router'; ...@@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { VerifiedContractsFilters } from 'types/api/contracts'; import type { VerifiedContractsFilters } from 'types/api/contracts';
import type { VerifiedContractsSorting, VerifiedContractsSortingField, VerifiedContractsSortingValue } from 'types/api/verifiedContracts';
import useDebounce from 'lib/hooks/useDebounce'; import useDebounce from 'lib/hooks/useDebounce';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
...@@ -16,9 +17,10 @@ import FilterInput from 'ui/shared/filters/FilterInput'; ...@@ -16,9 +17,10 @@ import FilterInput from 'ui/shared/filters/FilterInput';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import Sort from 'ui/shared/sort/Sort'; import Sort from 'ui/shared/sort/Sort';
import type { SortField, Sort as TSort } from 'ui/verifiedContracts/utils'; import { SORT_OPTIONS } from 'ui/verifiedContracts/utils';
import { SORT_OPTIONS, sortFn, getNextSortValue } from 'ui/verifiedContracts/utils';
import VerifiedContractsCounters from 'ui/verifiedContracts/VerifiedContractsCounters'; import VerifiedContractsCounters from 'ui/verifiedContracts/VerifiedContractsCounters';
import VerifiedContractsFilter from 'ui/verifiedContracts/VerifiedContractsFilter'; import VerifiedContractsFilter from 'ui/verifiedContracts/VerifiedContractsFilter';
import VerifiedContractsList from 'ui/verifiedContracts/VerifiedContractsList'; import VerifiedContractsList from 'ui/verifiedContracts/VerifiedContractsList';
...@@ -28,15 +30,17 @@ const VerifiedContracts = () => { ...@@ -28,15 +30,17 @@ const VerifiedContracts = () => {
const router = useRouter(); const router = useRouter();
const [ searchTerm, setSearchTerm ] = React.useState(getQueryParamString(router.query.q) || undefined); const [ searchTerm, setSearchTerm ] = React.useState(getQueryParamString(router.query.q) || undefined);
const [ type, setType ] = React.useState(getQueryParamString(router.query.filter) as VerifiedContractsFilters['filter'] || undefined); const [ type, setType ] = React.useState(getQueryParamString(router.query.filter) as VerifiedContractsFilters['filter'] || undefined);
const [ sort, setSort ] = React.useState<TSort>(); const [ sort, setSort ] =
React.useState<VerifiedContractsSortingValue | undefined>(getSortValueFromQuery<VerifiedContractsSortingValue>(router.query, SORT_OPTIONS));
const debouncedSearchTerm = useDebounce(searchTerm || '', 300); const debouncedSearchTerm = useDebounce(searchTerm || '', 300);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { isError, isPlaceholderData, data, pagination, onFilterChange } = useQueryWithPages({ const { isError, isPlaceholderData, data, pagination, onFilterChange, onSortingChange } = useQueryWithPages({
resourceName: 'verified_contracts', resourceName: 'verified_contracts',
filters: { q: debouncedSearchTerm, filter: type }, filters: { q: debouncedSearchTerm, filter: type },
sorting: getSortParamsFromValue<VerifiedContractsSortingValue, VerifiedContractsSortingField, VerifiedContractsSorting['order']>(sort),
options: { options: {
placeholderData: generateListStub<'verified_contracts'>( placeholderData: generateListStub<'verified_contracts'>(
VERIFIED_CONTRACT_INFO, VERIFIED_CONTRACT_INFO,
...@@ -67,11 +71,10 @@ const VerifiedContracts = () => { ...@@ -67,11 +71,10 @@ const VerifiedContracts = () => {
setType(filter); setType(filter);
}, [ debouncedSearchTerm, onFilterChange ]); }, [ debouncedSearchTerm, onFilterChange ]);
const handleSortToggle = React.useCallback((field: SortField) => { const handleSortChange = React.useCallback((value?: VerifiedContractsSortingValue) => {
return () => { setSort(value);
setSort(getNextSortValue(field)); onSortingChange(getSortParamsFromValue(value));
}; }, [ onSortingChange ]);
}, []);
const typeFilter = <VerifiedContractsFilter onChange={ handleTypeChange } defaultValue={ type } isActive={ Boolean(type) }/>; const typeFilter = <VerifiedContractsFilter onChange={ handleTypeChange } defaultValue={ type } isActive={ Boolean(type) }/>;
...@@ -89,7 +92,7 @@ const VerifiedContracts = () => { ...@@ -89,7 +92,7 @@ const VerifiedContracts = () => {
<Sort <Sort
options={ SORT_OPTIONS } options={ SORT_OPTIONS }
sort={ sort } sort={ sort }
setSort={ setSort } setSort={ handleSortChange }
/> />
); );
...@@ -112,15 +115,13 @@ const VerifiedContracts = () => { ...@@ -112,15 +115,13 @@ const VerifiedContracts = () => {
</> </>
); );
const sortedData = data?.items.slice().sort(sortFn(sort)); const content = data?.items ? (
const content = sortedData ? (
<> <>
<Show below="lg" ssr={ false }> <Show below="lg" ssr={ false }>
<VerifiedContractsList data={ sortedData } isLoading={ isPlaceholderData }/> <VerifiedContractsList data={ data.items } isLoading={ isPlaceholderData }/>
</Show> </Show>
<Hide below="lg" ssr={ false }> <Hide below="lg" ssr={ false }>
<VerifiedContractsTable data={ sortedData } sort={ sort } onSortToggle={ handleSortToggle } isLoading={ isPlaceholderData }/> <VerifiedContractsTable data={ data.items } sort={ sort } setSorting={ handleSortChange } isLoading={ isPlaceholderData }/>
</Hide> </Hide>
</> </>
) : null; ) : null;
......
...@@ -14,7 +14,7 @@ import PageTitle from 'ui/shared/Page/PageTitle'; ...@@ -14,7 +14,7 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton'; import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import TxsContent from 'ui/txs/TxsContent'; import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting';
import ZkEvmL2TxnBatchDetails from 'ui/zkEvmL2TxnBatches/ZkEvmL2TxnBatchDetails'; import ZkEvmL2TxnBatchDetails from 'ui/zkEvmL2TxnBatches/ZkEvmL2TxnBatchDetails';
const ZkEvmL2TxnBatch = () => { const ZkEvmL2TxnBatch = () => {
...@@ -51,7 +51,7 @@ const ZkEvmL2TxnBatch = () => { ...@@ -51,7 +51,7 @@ const ZkEvmL2TxnBatch = () => {
const tabs: Array<RoutedTab> = React.useMemo(() => ([ const tabs: Array<RoutedTab> = React.useMemo(() => ([
{ id: 'index', title: 'Details', component: <ZkEvmL2TxnBatchDetails query={ batchQuery }/> }, { id: 'index', title: 'Details', component: <ZkEvmL2TxnBatchDetails query={ batchQuery }/> },
{ id: 'txs', title: 'Transactions', component: <TxsContent query={ batchTxsQuery } showSocketInfo={ false }/> }, { id: 'txs', title: 'Transactions', component: <TxsWithFrontendSorting query={ batchTxsQuery } showSocketInfo={ false }/> },
].filter(Boolean)), [ batchQuery, batchTxsQuery ]); ].filter(Boolean)), [ batchQuery, batchTxsQuery ]);
const backLink = React.useMemo(() => { const backLink = React.useMemo(() => {
......
export default function getSortParamsFromValue<SortValue extends string, SortField extends string, SortOrder extends string>(val?: SortValue) {
if (!val) {
return undefined;
}
const sortingChunks = val.split('-') as [ SortField, SortOrder ];
return { sort: sortingChunks[0], order: sortingChunks[1] };
}
import type { Query } from 'nextjs-routes';
import type { Option } from 'ui/shared/sort/Sort';
export default function getSortValueFromQuery<SortValue extends string>(query: Query, sortOptions: Array<Option<SortValue>>) {
if (!query.sort || !query.order) {
return undefined;
}
const str = query.sort + '-' + query.order;
if (sortOptions.map(option => option.id).includes(str as SortValue)) {
return str as SortValue;
}
}
import type { TokenType } from 'types/api/token'; import type { TokenType } from 'types/api/token';
import type { TokensSortingField, TokensSortingValue, TokensSorting } from 'types/api/tokens'; import type { TokensSortingValue } from 'types/api/tokens';
import type { Query } from 'nextjs-routes';
import config from 'configs/app'; import config from 'configs/app';
import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery'; import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
...@@ -29,22 +27,3 @@ const bridgedTokensChainIds = (() => { ...@@ -29,22 +27,3 @@ const bridgedTokensChainIds = (() => {
return feature.chains.map(chain => chain.id); return feature.chains.map(chain => chain.id);
})(); })();
export const getBridgedChainsFilterValue = (getFilterValuesFromQuery<string>).bind(null, bridgedTokensChainIds); export const getBridgedChainsFilterValue = (getFilterValuesFromQuery<string>).bind(null, bridgedTokensChainIds);
export const getSortValueFromQuery = (query: Query): TokensSortingValue | undefined => {
if (!query.sort || !query.order) {
return undefined;
}
const str = query.sort + '-' + query.order;
if (SORT_OPTIONS.map(option => option.id).includes(str)) {
return str as TokensSortingValue;
}
};
export const getSortParamsFromValue = (val?: TokensSortingValue): TokensSorting | undefined => {
if (!val) {
return undefined;
}
const sortingChunks = val.split('-') as [ TokensSortingField, TokensSorting['order'] ];
return { sort: sortingChunks[0], order: sortingChunks[1] };
};
...@@ -2,17 +2,23 @@ import { Box, Show, Hide } from '@chakra-ui/react'; ...@@ -2,17 +2,23 @@ import { Box, Show, Hide } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressFromToFilter } from 'types/api/address'; import type { AddressFromToFilter } from 'types/api/address';
import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import AddressCsvExportLink from 'ui/address/AddressCsvExportLink'; import AddressCsvExportLink from 'ui/address/AddressCsvExportLink';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import getNextSortValue from 'ui/shared/sort/getNextSortValue';
import TxsHeaderMobile from './TxsHeaderMobile'; import TxsHeaderMobile from './TxsHeaderMobile';
import TxsListItem from './TxsListItem'; import TxsListItem from './TxsListItem';
import TxsTable from './TxsTable'; import TxsTable from './TxsTable';
import useTxsSort from './useTxsSort';
const SORT_SEQUENCE: Record<TransactionsSortingField, Array<TransactionsSortingValue | undefined>> = {
value: [ 'value-desc', 'value-asc', undefined ],
fee: [ 'fee-desc', 'fee-asc', undefined ],
};
type Props = { type Props = {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
...@@ -26,12 +32,17 @@ type Props = { ...@@ -26,12 +32,17 @@ type Props = {
filterValue?: AddressFromToFilter; filterValue?: AddressFromToFilter;
enableTimeIncrement?: boolean; enableTimeIncrement?: boolean;
top?: number; top?: number;
items?: Array<Transaction>;
isPlaceholderData: boolean;
isError: boolean;
setSorting: (value: TransactionsSortingValue | undefined) => void;
sort: TransactionsSortingValue | undefined;
} }
const TxsContent = ({ const TxsContent = ({
query,
filter, filter,
filterValue, filterValue,
query,
showBlockInfo = true, showBlockInfo = true,
showSocketInfo = true, showSocketInfo = true,
socketInfoAlert, socketInfoAlert,
...@@ -39,11 +50,20 @@ const TxsContent = ({ ...@@ -39,11 +50,20 @@ const TxsContent = ({
currentAddress, currentAddress,
enableTimeIncrement, enableTimeIncrement,
top, top,
items,
isPlaceholderData,
isError,
setSorting,
sort,
}: Props) => { }: Props) => {
const { data, isPlaceholderData, isError, setSortByField, setSortByValue, sorting } = useTxsSort(query);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const content = data?.items ? ( const onSortToggle = React.useCallback((field: TransactionsSortingField) => () => {
const value = getNextSortValue<TransactionsSortingField, TransactionsSortingValue>(SORT_SEQUENCE, field)(sort);
setSorting(value);
}, [ sort, setSorting ]);
const content = items ? (
<> <>
<Show below="lg" ssr={ false }> <Show below="lg" ssr={ false }>
<Box> <Box>
...@@ -55,7 +75,7 @@ const TxsContent = ({ ...@@ -55,7 +75,7 @@ const TxsContent = ({
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
/> />
) } ) }
{ data.items.map((tx, index) => ( { items.map((tx, index) => (
<TxsListItem <TxsListItem
key={ tx.hash + (isPlaceholderData ? index : '') } key={ tx.hash + (isPlaceholderData ? index : '') }
tx={ tx } tx={ tx }
...@@ -69,9 +89,9 @@ const TxsContent = ({ ...@@ -69,9 +89,9 @@ const TxsContent = ({
</Show> </Show>
<Hide below="lg" ssr={ false }> <Hide below="lg" ssr={ false }>
<TxsTable <TxsTable
txs={ data.items } txs={ items }
sort={ setSortByField } sort={ onSortToggle }
sorting={ sorting } sorting={ sort }
showBlockInfo={ showBlockInfo } showBlockInfo={ showBlockInfo }
showSocketInfo={ showSocketInfo } showSocketInfo={ showSocketInfo }
socketInfoAlert={ socketInfoAlert } socketInfoAlert={ socketInfoAlert }
...@@ -88,8 +108,8 @@ const TxsContent = ({ ...@@ -88,8 +108,8 @@ const TxsContent = ({
const actionBar = isMobile ? ( const actionBar = isMobile ? (
<TxsHeaderMobile <TxsHeaderMobile
mt={ -6 } mt={ -6 }
sorting={ sorting } sorting={ sort }
setSorting={ setSortByValue } setSorting={ setSorting }
paginationProps={ query.pagination } paginationProps={ query.pagination }
showPagination={ query.pagination.isVisible } showPagination={ query.pagination.isVisible }
filterComponent={ filter } filterComponent={ filter }
...@@ -107,7 +127,7 @@ const TxsContent = ({ ...@@ -107,7 +127,7 @@ const TxsContent = ({
return ( return (
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
items={ data?.items } items={ items }
emptyText="There are no transactions." emptyText="There are no transactions."
content={ content } content={ content }
actionBar={ actionBar } actionBar={ actionBar }
......
import { HStack, chakra } from '@chakra-ui/react'; import { HStack, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Sort as TSort } from 'types/client/txs-sort'; import type { TransactionsSortingValue } from 'types/api/transaction';
import type { PaginationParams } from 'ui/shared/pagination/types'; import type { PaginationParams } from 'ui/shared/pagination/types';
// import FilterInput from 'ui/shared/filters/FilterInput'; // import FilterInput from 'ui/shared/filters/FilterInput';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import type { Option } from 'ui/shared/sort/Sort';
import Sort from 'ui/shared/sort/Sort'; import Sort from 'ui/shared/sort/Sort';
// import TxsFilters from './TxsFilters'; import { SORT_OPTIONS } from './useTxsSort';
const SORT_OPTIONS: Array<Option<TSort>> = [ // import TxsFilters from './TxsFilters';
{ title: 'Default', id: undefined },
{ title: 'Value ascending', id: 'val-asc' },
{ title: 'Value descending', id: 'val-desc' },
{ title: 'Fee ascending', id: 'fee-asc' },
{ title: 'Fee descending', id: 'fee-desc' },
];
type Props = { type Props = {
sorting: TSort; sorting: TransactionsSortingValue | undefined;
setSorting: (val: TSort | undefined) => void; setSorting: (val: TransactionsSortingValue | undefined) => void;
paginationProps: PaginationParams; paginationProps: PaginationParams;
className?: string; className?: string;
showPagination?: boolean; showPagination?: boolean;
......
...@@ -2,8 +2,7 @@ import { Link, Table, Tbody, Tr, Th, Icon, Show, Hide } from '@chakra-ui/react'; ...@@ -2,8 +2,7 @@ import { Link, Table, Tbody, Tr, Th, Icon, Show, Hide } from '@chakra-ui/react';
import { AnimatePresence } from 'framer-motion'; import { AnimatePresence } from 'framer-motion';
import React from 'react'; import React from 'react';
import type { Transaction } from 'types/api/transaction'; import type { Transaction, TransactionsSortingField, TransactionsSortingValue } from 'types/api/transaction';
import type { Sort } from 'types/client/txs-sort';
import config from 'configs/app'; import config from 'configs/app';
import rightArrowIcon from 'icons/arrows/east.svg'; import rightArrowIcon from 'icons/arrows/east.svg';
...@@ -14,8 +13,8 @@ import TxsTableItem from './TxsTableItem'; ...@@ -14,8 +13,8 @@ import TxsTableItem from './TxsTableItem';
type Props = { type Props = {
txs: Array<Transaction>; txs: Array<Transaction>;
sort: (field: 'val' | 'fee') => () => void; sort: (field: TransactionsSortingField) => () => void;
sorting?: Sort; sorting?: TransactionsSortingValue;
top: number; top: number;
showBlockInfo: boolean; showBlockInfo: boolean;
showSocketInfo: boolean; showSocketInfo: boolean;
...@@ -58,9 +57,9 @@ const TxsTable = ({ ...@@ -58,9 +57,9 @@ const TxsTable = ({
</Th> </Th>
{ !config.UI.views.tx.hiddenFields?.value && ( { !config.UI.views.tx.hiddenFields?.value && (
<Th width="20%" isNumeric> <Th width="20%" isNumeric>
<Link onClick={ sort('val') } display="flex" justifyContent="end"> <Link onClick={ sort('value') } display="flex" justifyContent="end">
{ sorting === 'val-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> } { sorting === 'value-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> }
{ sorting === 'val-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> } { sorting === 'value-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> }
{ `Value ${ config.chain.currency.symbol }` } { `Value ${ config.chain.currency.symbol }` }
</Link> </Link>
</Th> </Th>
......
...@@ -2,7 +2,7 @@ import React from 'react'; ...@@ -2,7 +2,7 @@ import React from 'react';
import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken'; import useRedirectForInvalidAuthToken from 'lib/hooks/useRedirectForInvalidAuthToken';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import TxsContent from 'ui/txs/TxsContent'; import TxsWithFrontendSorting from 'ui/txs/TxsWithFrontendSorting';
type Props = { type Props = {
query: QueryWithPagesResult<'txs_watchlist'>; query: QueryWithPagesResult<'txs_watchlist'>;
...@@ -10,7 +10,7 @@ type Props = { ...@@ -10,7 +10,7 @@ type Props = {
const TxsWatchlist = ({ query }: Props) => { const TxsWatchlist = ({ query }: Props) => {
useRedirectForInvalidAuthToken(); useRedirectForInvalidAuthToken();
return <TxsContent query={ query } showSocketInfo={ false }/>; return <TxsWithFrontendSorting query={ query } showSocketInfo={ false }/>;
}; };
export default TxsWatchlist; export default TxsWatchlist;
import React from 'react';
import type { AddressFromToFilter } from 'types/api/address';
import type { TransactionsSortingValue } from 'types/api/transaction';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import TxsContent from './TxsContent';
type Props = {
// eslint-disable-next-line max-len
query: QueryWithPagesResult<'address_txs'>;
showBlockInfo?: boolean;
showSocketInfo?: boolean;
socketInfoAlert?: string;
socketInfoNum?: number;
currentAddress?: string;
filter?: React.ReactNode;
filterValue?: AddressFromToFilter;
enableTimeIncrement?: boolean;
top?: number;
sorting: TransactionsSortingValue | undefined;
setSort: (value?: TransactionsSortingValue) => void;
}
const TxsWithAPISorting = ({
filter,
filterValue,
query,
showBlockInfo = true,
showSocketInfo = true,
socketInfoAlert,
socketInfoNum,
currentAddress,
enableTimeIncrement,
top,
sorting,
setSort,
}: Props) => {
const handleSortChange = React.useCallback((value?: TransactionsSortingValue) => {
setSort(value);
query.onSortingChange(getSortParamsFromValue(value));
}, [ setSort, query ]);
return (
<TxsContent
filter={ filter }
filterValue={ filterValue }
showBlockInfo={ showBlockInfo }
showSocketInfo={ showSocketInfo }
socketInfoAlert={ socketInfoAlert }
socketInfoNum={ socketInfoNum }
currentAddress={ currentAddress }
enableTimeIncrement={ enableTimeIncrement }
top={ top }
items={ query.data?.items }
isPlaceholderData={ query.isPlaceholderData }
isError={ query.isError }
setSorting={ handleSortChange }
sort={ sorting }
query={ query }
/>
);
};
export default TxsWithAPISorting;
import React from 'react';
import type { AddressFromToFilter } from 'types/api/address';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
import TxsContent from './TxsContent';
import useTxsSort from './useTxsSort';
type Props = {
// eslint-disable-next-line max-len
query: QueryWithPagesResult<'txs_validated' | 'txs_pending'> | QueryWithPagesResult<'txs_watchlist'> | QueryWithPagesResult<'block_txs'> | QueryWithPagesResult<'zkevm_l2_txn_batch_txs'>;
showBlockInfo?: boolean;
showSocketInfo?: boolean;
socketInfoAlert?: string;
socketInfoNum?: number;
currentAddress?: string;
filter?: React.ReactNode;
filterValue?: AddressFromToFilter;
enableTimeIncrement?: boolean;
top?: number;
}
const TxsWithFrontendSorting = ({
filter,
filterValue,
query,
showBlockInfo = true,
showSocketInfo = true,
socketInfoAlert,
socketInfoNum,
currentAddress,
enableTimeIncrement,
top,
}: Props) => {
const { data, isPlaceholderData, isError, setSortByValue, sorting } = useTxsSort(query);
return (
<TxsContent
filter={ filter }
filterValue={ filterValue }
showBlockInfo={ showBlockInfo }
showSocketInfo={ showSocketInfo }
socketInfoAlert={ socketInfoAlert }
socketInfoNum={ socketInfoNum }
currentAddress={ currentAddress }
enableTimeIncrement={ enableTimeIncrement }
top={ top }
items={ data?.items }
isPlaceholderData={ isPlaceholderData }
isError={ isError }
setSorting={ setSortByValue }
sort={ sorting }
query={ query }
/>
);
};
export default TxsWithFrontendSorting;
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import type { TxsResponse } from 'types/api/transaction'; import type { Transaction, TransactionsSortingValue, TxsResponse } from 'types/api/transaction';
import type { Sort } from 'types/client/txs-sort';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import compareBns from 'lib/bigint/compareBns';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import sortTxs from 'lib/tx/sortTxs'; import type { Option } from 'ui/shared/sort/Sort';
export const SORT_OPTIONS: Array<Option<TransactionsSortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Value ascending', id: 'value-asc' },
{ title: 'Value descending', id: 'value-desc' },
{ title: 'Fee ascending', id: 'fee-asc' },
{ title: 'Fee descending', id: 'fee-desc' },
];
type SortingValue = TransactionsSortingValue | undefined;
type HookResult = UseQueryResult<TxsResponse, ResourceError<unknown>> & { type HookResult = UseQueryResult<TxsResponse, ResourceError<unknown>> & {
sorting: Sort; sorting: SortingValue;
setSortByField: (field: 'val' | 'fee') => () => void; setSortByValue: (value: SortingValue) => void;
setSortByValue: (value: Sort | undefined) => void;
} }
const sortTxs = (sorting: SortingValue) => (tx1: Transaction, tx2: Transaction) => {
switch (sorting) {
case 'value-desc':
return compareBns(tx1.value, tx2.value);
case 'value-asc':
return compareBns(tx2.value, tx1.value);
case 'fee-desc':
return compareBns(tx1.fee.value || 0, tx2.fee.value || 0);
case 'fee-asc':
return compareBns(tx2.fee.value || 0, tx1.fee.value || 0);
default:
return 0;
}
};
export default function useTxsSort( export default function useTxsSort(
queryResult: UseQueryResult<TxsResponse, ResourceError<unknown>>, queryResult: UseQueryResult<TxsResponse, ResourceError<unknown>>,
): HookResult { ): HookResult {
const [ sorting, setSorting ] = React.useState<Sort>(cookies.get(cookies.NAMES.TXS_SORT) as Sort); const [ sorting, setSorting ] = React.useState<SortingValue>(cookies.get(cookies.NAMES.TXS_SORT) as SortingValue);
const setSortByField = React.useCallback((field: 'val' | 'fee') => () => {
if (queryResult.isPlaceholderData) {
return;
}
setSorting((prevVal) => {
let newVal: Sort = '';
if (field === 'val') {
if (prevVal === 'val-asc') {
newVal = '';
} else if (prevVal === 'val-desc') {
newVal = 'val-asc';
} else {
newVal = 'val-desc';
}
}
if (field === 'fee') {
if (prevVal === 'fee-asc') {
newVal = '';
} else if (prevVal === 'fee-desc') {
newVal = 'fee-asc';
} else {
newVal = 'fee-desc';
}
}
cookies.set(cookies.NAMES.TXS_SORT, newVal);
return newVal;
});
}, [ queryResult.isPlaceholderData ]);
const setSortByValue = React.useCallback((value: Sort | undefined) => { const setSortByValue = React.useCallback((value: SortingValue) => {
setSorting((prevVal: Sort) => { setSorting((prevVal: SortingValue) => {
let newVal: Sort = ''; let newVal: SortingValue = undefined;
if (value !== prevVal) { if (value !== prevVal) {
newVal = value as Sort; newVal = value as SortingValue;
} }
cookies.set(cookies.NAMES.TXS_SORT, newVal); cookies.set(cookies.NAMES.TXS_SORT, newVal ? newVal : '');
return newVal; return newVal;
}); });
}, []); }, []);
return React.useMemo(() => { return React.useMemo(() => {
if (queryResult.isError || queryResult.isPending) { if (queryResult.isError || queryResult.isPending) {
return { ...queryResult, setSortByField, setSortByValue, sorting }; return { ...queryResult, setSortByValue, sorting };
} }
return { return {
...queryResult, ...queryResult,
data: { ...queryResult.data, items: queryResult.data.items.slice().sort(sortTxs(sorting)) }, data: { ...queryResult.data, items: queryResult.data.items.slice().sort(sortTxs(sorting)) },
setSortByField,
setSortByValue, setSortByValue,
sorting, sorting,
}; };
}, [ queryResult, setSortByField, setSortByValue, sorting ]); }, [ queryResult, setSortByValue, sorting ]);
} }
...@@ -2,23 +2,30 @@ import { Table, Tbody, Tr, Th, Link, Icon } from '@chakra-ui/react'; ...@@ -2,23 +2,30 @@ import { Table, Tbody, Tr, Th, Link, Icon } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { VerifiedContract } from 'types/api/contracts'; import type { VerifiedContract } from 'types/api/contracts';
import type { VerifiedContractsSorting, VerifiedContractsSortingField, VerifiedContractsSortingValue } from 'types/api/verifiedContracts';
import config from 'configs/app'; import config from 'configs/app';
import arrowIcon from 'icons/arrows/east.svg'; import arrowIcon from 'icons/arrows/east.svg';
import getNextSortValue from 'ui/shared/sort/getNextSortValue';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import { SORT_SEQUENCE } from 'ui/verifiedContracts/utils';
import type { Sort, SortField } from './utils';
import VerifiedContractsTableItem from './VerifiedContractsTableItem'; import VerifiedContractsTableItem from './VerifiedContractsTableItem';
interface Props { interface Props {
data: Array<VerifiedContract>; data: Array<VerifiedContract>;
sort: Sort | undefined; sort: VerifiedContractsSortingValue | undefined;
onSortToggle: (field: SortField) => () => void; setSorting: (val: VerifiedContractsSortingValue | undefined) => void;
isLoading?: boolean; isLoading?: boolean;
} }
const VerifiedContractsTable = ({ data, sort, onSortToggle, isLoading }: Props) => { const VerifiedContractsTable = ({ data, sort, setSorting, isLoading }: Props) => {
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)'; const sortIconTransform = sort?.includes('asc' as VerifiedContractsSorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)';
const onSortToggle = React.useCallback((field: VerifiedContractsSortingField) => () => {
const value = getNextSortValue<VerifiedContractsSortingField, VerifiedContractsSortingValue>(SORT_SEQUENCE, field)(sort);
setSorting(value);
}, [ sort, setSorting ]);
return ( return (
<Table variant="simple" size="sm"> <Table variant="simple" size="sm">
...@@ -32,8 +39,8 @@ const VerifiedContractsTable = ({ data, sort, onSortToggle, isLoading }: Props) ...@@ -32,8 +39,8 @@ const VerifiedContractsTable = ({ data, sort, onSortToggle, isLoading }: Props)
</Link> </Link>
</Th> </Th>
<Th width="130px" isNumeric> <Th width="130px" isNumeric>
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ isLoading ? undefined : onSortToggle('txs') } columnGap={ 1 }> <Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ isLoading ? undefined : onSortToggle('txs_count') } columnGap={ 1 }>
{ sort?.includes('txs') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> } { sort?.includes('txs_count') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
Txs Txs
</Link> </Link>
</Th> </Th>
......
import type { VerifiedContract } from 'types/api/contracts'; import type { VerifiedContractsSortingValue, VerifiedContractsSortingField } from 'types/api/verifiedContracts';
import compareBns from 'lib/bigint/compareBns';
import { default as getNextSortValueShared } from 'ui/shared/sort/getNextSortValue';
import type { Option } from 'ui/shared/sort/Sort'; import type { Option } from 'ui/shared/sort/Sort';
export type SortField = 'balance' | 'txs'; export const SORT_OPTIONS: Array<Option<VerifiedContractsSortingValue>> = [
export type Sort = `${ SortField }-asc` | `${ SortField }-desc`;
export const SORT_OPTIONS: Array<Option<Sort>> = [
{ title: 'Default', id: undefined }, { title: 'Default', id: undefined },
{ title: 'Balance descending', id: 'balance-desc' }, { title: 'Balance descending', id: 'balance-desc' },
{ title: 'Balance ascending', id: 'balance-asc' }, { title: 'Balance ascending', id: 'balance-asc' },
{ title: 'Txs count descending', id: 'txs-desc' }, { title: 'Txs count descending', id: 'txs_count-desc' },
{ title: 'Txs count ascending', id: 'txs-asc' }, { title: 'Txs count ascending', id: 'txs_count-asc' },
]; ];
const SORT_SEQUENCE: Record<SortField, Array<Sort | undefined>> = { export const SORT_SEQUENCE: Record<VerifiedContractsSortingField, Array<VerifiedContractsSortingValue | undefined>> = {
balance: [ 'balance-desc', 'balance-asc', undefined ], balance: [ 'balance-desc', 'balance-asc', undefined ],
txs: [ 'txs-desc', 'txs-asc', undefined ], txs_count: [ 'txs_count-desc', 'txs_count-asc', undefined ],
};
export const getNextSortValue = (getNextSortValueShared<SortField, Sort>).bind(undefined, SORT_SEQUENCE);
export const sortFn = (sort: Sort | undefined) => (a: VerifiedContract, b: VerifiedContract) => {
switch (sort) {
case 'balance-asc':
case 'balance-desc': {
const result = compareBns(b.coin_balance, a.coin_balance) * (sort.includes('desc') ? 1 : -1);
return a.coin_balance === b.coin_balance ? 0 : result;
}
case 'txs-asc':
case 'txs-desc': {
const result = ((a.tx_count || 0) > (b.tx_count || 0) ? -1 : 1) * (sort.includes('desc') ? 1 : -1);
return a.tx_count === b.tx_count ? 0 : result;
}
default:
return 0;
}
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment