mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
advanced filters page
This commit is contained in:
@@ -37,6 +37,7 @@ const RESTRICTED_MODULES = {
|
||||
'Tag', 'Switch', 'Image', 'Popover', 'PopoverTrigger', 'PopoverContent', 'PopoverBody', 'PopoverFooter',
|
||||
'DrawerRoot', 'DrawerBody', 'DrawerContent', 'DrawerOverlay', 'DrawerBackdrop', 'DrawerTrigger', 'Drawer',
|
||||
'Alert', 'AlertIcon', 'AlertTitle', 'AlertDescription',
|
||||
'Select', 'SelectRoot', 'SelectControl', 'SelectContent', 'SelectItem', 'SelectValueText',
|
||||
'Heading', 'Badge', 'Tabs', 'Show', 'Hide', 'Checkbox', 'CheckboxGroup',
|
||||
'Table', 'TableRoot', 'TableBody', 'TableHeader', 'TableRow', 'TableCell',
|
||||
'Menu', 'MenuRoot', 'MenuTrigger', 'MenuContent', 'MenuItem', 'MenuTriggerItem', 'MenuRadioItemGroup', 'MenuContextTrigger',
|
||||
|
||||
@@ -3,12 +3,12 @@ import React from 'react';
|
||||
|
||||
import PageNextJs from 'nextjs/PageNextJs';
|
||||
|
||||
// import AdvancedFilter from 'ui/pages/AdvancedFilter';
|
||||
import AdvancedFilter from 'ui/pages/AdvancedFilter';
|
||||
|
||||
const Page: NextPage = () => {
|
||||
return (
|
||||
<PageNextJs pathname="/advanced-filter">
|
||||
{ /* <AdvancedFilter/> */ }
|
||||
<AdvancedFilter/>
|
||||
</PageNextJs>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,18 +9,18 @@ import config from 'configs/app';
|
||||
|
||||
const validatorsFeature = config.features.validators;
|
||||
|
||||
// const ValidatorDetails = dynamic(() => {
|
||||
// if (validatorsFeature.isEnabled && validatorsFeature.chainType === 'zilliqa') {
|
||||
// return import('ui/pages/ValidatorZilliqa');
|
||||
// }
|
||||
const ValidatorDetails = dynamic(() => {
|
||||
if (validatorsFeature.isEnabled && validatorsFeature.chainType === 'zilliqa') {
|
||||
return import('ui/pages/ValidatorZilliqa');
|
||||
}
|
||||
|
||||
// throw new Error('Validators feature is not enabled.');
|
||||
// }, { ssr: false });
|
||||
throw new Error('Validators feature is not enabled.');
|
||||
}, { ssr: false });
|
||||
|
||||
const Page: NextPage<Props> = (props) => {
|
||||
return (
|
||||
<PageNextJs pathname="/validators/[id]" query={ props.query }>
|
||||
{ /* <ValidatorDetails/> */ }
|
||||
<ValidatorDetails/>
|
||||
</PageNextJs>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ const semanticTokens = {
|
||||
'default': 'red.500',
|
||||
_dark: 'red.500',
|
||||
},
|
||||
dialog.bg: {
|
||||
dialog_bg: {
|
||||
'default': 'white',
|
||||
_dark: 'gray.900',
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Checkbox as ArkCheckbox } from '@ark-ui/react/checkbox';
|
||||
import type { HTMLChakraProps } from '@chakra-ui/react';
|
||||
import { Checkbox as ChakraCheckbox, CheckboxGroup as ChakraCheckboxGroup } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -25,7 +26,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
},
|
||||
);
|
||||
|
||||
export interface CheckboxGroupProps extends ArkCheckbox.GroupProps {
|
||||
export interface CheckboxGroupProps extends HTMLChakraProps<'div', ArkCheckbox.GroupProps> {
|
||||
orientation?: 'vertical' | 'horizontal';
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import type { CollectionItem } from '@chakra-ui/react';
|
||||
import type { CollectionItem, ListCollection } from '@chakra-ui/react';
|
||||
import { Select as ChakraSelect, Portal, useSelectContext } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -173,3 +173,33 @@ export const SelectItemGroup = React.forwardRef<
|
||||
|
||||
export const SelectLabel = ChakraSelect.Label;
|
||||
export const SelectItemText = ChakraSelect.ItemText;
|
||||
|
||||
export interface SelectProps extends SelectRootProps {
|
||||
collection: ListCollection<CollectionItem>;
|
||||
placeholder: string;
|
||||
portalled?: boolean;
|
||||
}
|
||||
|
||||
// TODO @tom2drum refactor selects
|
||||
export const Select = React.forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
|
||||
const { collection, placeholder, portalled = true, ...rest } = props;
|
||||
return (
|
||||
<SelectRoot
|
||||
ref={ ref }
|
||||
collection={ collection }
|
||||
variant="outline"
|
||||
{ ...rest }
|
||||
>
|
||||
<SelectControl>
|
||||
<SelectValueText placeholder={ placeholder }/>
|
||||
</SelectControl>
|
||||
<SelectContent portalled={ portalled }>
|
||||
{ collection.items.map((item) => (
|
||||
<SelectItem item={ item } key={ item.value }>
|
||||
{ item.label }
|
||||
</SelectItem>
|
||||
)) }
|
||||
</SelectContent>
|
||||
</SelectRoot>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { TokenDefinition } from '@chakra-ui/react/dist/types/styled-system/types';
|
||||
import type { ThemingConfig } from '@chakra-ui/react';
|
||||
|
||||
export const radii: TokenDefinition['radii'] = {
|
||||
import type { ExcludeUndefined } from 'types/utils';
|
||||
|
||||
export const radii: ExcludeUndefined<ThemingConfig['tokens']>['radii'] = {
|
||||
none: { value: '0' },
|
||||
sm: { value: '4px' },
|
||||
base: { value: '8px' },
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { TokenDefinition } from '@chakra-ui/react/dist/types/styled-system/types';
|
||||
import type { ThemingConfig } from '@chakra-ui/react';
|
||||
|
||||
export const zIndex: TokenDefinition['zIndex'] = {
|
||||
import type { ExcludeUndefined } from 'types/utils';
|
||||
|
||||
export const zIndex: ExcludeUndefined<ThemingConfig['tokens']>['zIndex'] = {
|
||||
hide: { value: -1 },
|
||||
auto: { value: 'auto' },
|
||||
base: { value: 0 },
|
||||
|
||||
@@ -26,6 +26,7 @@ export const recipe = defineSlotRecipe({
|
||||
label: {
|
||||
fontWeight: 'normal',
|
||||
userSelect: 'none',
|
||||
flexGrow: 1,
|
||||
_disabled: {
|
||||
opacity: 'control.disabled',
|
||||
},
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
import {
|
||||
Button,
|
||||
Grid,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverBody,
|
||||
useDisclosure,
|
||||
Checkbox,
|
||||
} from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import type { ChangeEvent } from 'react';
|
||||
|
||||
import { Button } from 'toolkit/chakra/button';
|
||||
import { Checkbox, CheckboxGroup } from 'toolkit/chakra/checkbox';
|
||||
import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover';
|
||||
import type { ColumnsIds } from 'ui/advancedFilter/constants';
|
||||
import { TABLE_COLUMNS } from 'ui/advancedFilter/constants';
|
||||
import Popover from 'ui/shared/chakra/Popover';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
interface Props {
|
||||
@@ -21,46 +13,47 @@ interface Props {
|
||||
}
|
||||
|
||||
const ColumnsButton = ({ columns, onChange }: Props) => {
|
||||
const { isOpen, onToggle, onClose } = useDisclosure();
|
||||
|
||||
const onCheckboxClick = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
const newCols = { ...columns };
|
||||
const id = event.target.id as ColumnsIds;
|
||||
newCols[id] = event.target.checked;
|
||||
const handleValueChange = React.useCallback((value: Array<string>) => {
|
||||
const newCols = value.reduce((acc, key) => {
|
||||
acc[key as ColumnsIds] = true;
|
||||
return acc;
|
||||
}, {} as Record<ColumnsIds, boolean>);
|
||||
onChange(newCols);
|
||||
}, [ onChange, columns ]);
|
||||
}, [ onChange ]);
|
||||
|
||||
return (
|
||||
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
|
||||
<PopoverRoot>
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
onClick={ onToggle }
|
||||
variant="outline"
|
||||
colorScheme="gray"
|
||||
variant="dropdown"
|
||||
size="sm"
|
||||
leftIcon={ <IconSvg name="columns" boxSize={ 5 } color="inherit"/> }
|
||||
>
|
||||
<IconSvg name="columns" boxSize={ 5 } color="inherit"/>
|
||||
Columns
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverBody px={ 4 } py={ 6 } display="flex" flexDir="column" rowGap={ 5 }>
|
||||
<Grid gridTemplateColumns="160px 160px" gap={ 3 }>
|
||||
<CheckboxGroup
|
||||
defaultValue={ Object.keys(columns).filter((key) => columns[key as ColumnsIds]) }
|
||||
onValueChange={ handleValueChange }
|
||||
display="grid"
|
||||
gridTemplateColumns="160px 160px"
|
||||
gap={ 3 }
|
||||
>
|
||||
{ TABLE_COLUMNS.map(col => (
|
||||
<Checkbox
|
||||
key={ col.id }
|
||||
defaultChecked={ columns[col.id] }
|
||||
onChange={ onCheckboxClick }
|
||||
id={ col.id }
|
||||
size="lg"
|
||||
value={ col.id }
|
||||
size="md"
|
||||
>
|
||||
{ col.id === 'or_and' ? 'And/Or' : col.name }
|
||||
</Checkbox>
|
||||
)) }
|
||||
</Grid>
|
||||
</CheckboxGroup>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</PopoverRoot>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { AdvancedFilterParams } from 'types/api/advancedFilter';
|
||||
@@ -7,7 +6,8 @@ import config from 'configs/app';
|
||||
import buildUrl from 'lib/api/buildUrl';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import downloadBlob from 'lib/downloadBlob';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import { Button } from 'toolkit/chakra/button';
|
||||
import { toaster } from 'toolkit/chakra/toaster';
|
||||
import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha';
|
||||
import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha';
|
||||
|
||||
@@ -17,7 +17,6 @@ type Props = {
|
||||
|
||||
const ExportCSV = ({ filters }: Props) => {
|
||||
const recaptcha = useReCaptcha();
|
||||
const toast = useToast();
|
||||
const [ isLoading, setIsLoading ] = React.useState(false);
|
||||
|
||||
const handleExportCSV = React.useCallback(async() => {
|
||||
@@ -49,18 +48,14 @@ const ExportCSV = ({ filters }: Props) => {
|
||||
downloadBlob(blob, fileName);
|
||||
|
||||
} catch (error) {
|
||||
toast({
|
||||
position: 'top-right',
|
||||
toaster.error({
|
||||
title: 'Error',
|
||||
description: (error as Error)?.message || 'Something went wrong. Try again later.',
|
||||
status: 'error',
|
||||
variant: 'subtle',
|
||||
isClosable: true,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [ toast, filters, recaptcha ]);
|
||||
}, [ filters, recaptcha ]);
|
||||
|
||||
if (!config.services.reCaptchaV2.siteKey) {
|
||||
return null;
|
||||
@@ -71,7 +66,7 @@ const ExportCSV = ({ filters }: Props) => {
|
||||
<Button
|
||||
onClick={ handleExportCSV }
|
||||
variant="outline"
|
||||
isLoading={ isLoading }
|
||||
loading={ isLoading }
|
||||
size="sm"
|
||||
mr={ 3 }
|
||||
>
|
||||
|
||||
@@ -34,7 +34,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
|
||||
<TableColumnFilterWrapper
|
||||
columnName="Type"
|
||||
isLoading={ isLoading }
|
||||
isActive={ Boolean(value && value.length) }
|
||||
selected={ Boolean(value && value.length) }
|
||||
>
|
||||
<TypeFilter { ...commonProps } value={ value }/>
|
||||
</TableColumnFilterWrapper>
|
||||
@@ -46,7 +46,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
|
||||
<TableColumnFilterWrapper
|
||||
columnName="Method"
|
||||
isLoading={ isLoading }
|
||||
isActive={ Boolean(value && value.length) }
|
||||
selected={ Boolean(value && value.length) }
|
||||
w="350px"
|
||||
>
|
||||
<MethodFilter { ...commonProps } value={ value }/>
|
||||
@@ -59,7 +59,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
|
||||
<TableColumnFilterWrapper
|
||||
columnName="Age"
|
||||
isLoading={ isLoading }
|
||||
isActive={ Boolean(value.from || value.to || value.age) }
|
||||
selected={ Boolean(value.from || value.to || value.age) }
|
||||
w="382px"
|
||||
>
|
||||
<AgeFilter { ...commonProps } value={ value }/>
|
||||
@@ -71,7 +71,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
|
||||
<TableColumnFilterWrapper
|
||||
columnName="And/Or"
|
||||
isLoading={ isLoading }
|
||||
isActive={ false }
|
||||
selected={ false }
|
||||
w="106px"
|
||||
value={ filters.address_relation === 'and' ? 'AND' : 'OR' }
|
||||
>
|
||||
@@ -88,7 +88,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
|
||||
<TableColumnFilterWrapper
|
||||
columnName="Address from"
|
||||
isLoading={ isLoading }
|
||||
isActive={ Boolean(value.length) }
|
||||
selected={ Boolean(value.length) }
|
||||
w="480px"
|
||||
>
|
||||
<AddressFilter { ...commonProps } type="from" value={ value }/>
|
||||
@@ -105,7 +105,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
|
||||
<TableColumnFilterWrapper
|
||||
columnName="Address to"
|
||||
isLoading={ isLoading }
|
||||
isActive={ Boolean(value.length) }
|
||||
selected={ Boolean(value.length) }
|
||||
w="480px"
|
||||
>
|
||||
<AddressFilter { ...commonProps } type="to" value={ value }/>
|
||||
@@ -118,7 +118,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
|
||||
<TableColumnFilterWrapper
|
||||
columnName="Amount"
|
||||
isLoading={ isLoading }
|
||||
isActive={ Boolean(value.from || value.to) }
|
||||
selected={ Boolean(value.from || value.to) }
|
||||
w="382px"
|
||||
>
|
||||
<AmountFilter { ...commonProps } value={ value }/>
|
||||
@@ -145,7 +145,7 @@ const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searc
|
||||
<TableColumnFilterWrapper
|
||||
columnName="Asset"
|
||||
isLoading={ isLoading }
|
||||
isActive={ Boolean(value.length) }
|
||||
selected={ Boolean(value.length) }
|
||||
w="382px"
|
||||
>
|
||||
<AssetFilter { ...commonProps } value={ value }/>
|
||||
|
||||
@@ -5,10 +5,10 @@ import type { AdvancedFilterResponseItem } from 'types/api/advancedFilter';
|
||||
|
||||
import config from 'configs/app';
|
||||
import getCurrencyValue from 'lib/getCurrencyValue';
|
||||
import { Badge } from 'toolkit/chakra/badge';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import type { ColumnsIds } from 'ui/advancedFilter/constants';
|
||||
import AddressFromToIcon from 'ui/shared/address/AddressFromToIcon';
|
||||
import Skeleton from 'ui/shared/chakra/Skeleton';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
@@ -31,10 +31,10 @@ const ItemByColumn = ({ item, column, isLoading }: Props) => {
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
return <Tag isLoading={ isLoading }>{ type.name }</Tag>;
|
||||
return <Badge loading={ isLoading }>{ type.name }</Badge>;
|
||||
}
|
||||
case 'method':
|
||||
return item.method ? <Tag isLoading={ isLoading } isTruncated colorScheme="gray">{ item.method }</Tag> : null;
|
||||
return item.method ? <Badge loading={ isLoading } truncated>{ item.method }</Badge> : null;
|
||||
case 'age':
|
||||
return <TimeAgoWithTooltip timestamp={ item.timestamp } isLoading={ isLoading } color="text_secondary" fontWeight={ 400 }/>;
|
||||
case 'from':
|
||||
@@ -63,18 +63,18 @@ const ItemByColumn = ({ item, column, isLoading }: Props) => {
|
||||
);
|
||||
case 'amount': {
|
||||
if (item.token?.type === 'ERC-721') {
|
||||
return <Skeleton isLoaded={ !isLoading }>1</Skeleton>;
|
||||
return <Skeleton loading={ isLoading }>1</Skeleton>;
|
||||
}
|
||||
if (item.total) {
|
||||
return (
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
<Skeleton loading={ isLoading }>
|
||||
{ getCurrencyValue({ value: item.total?.value, decimals: item.total.decimals, accuracy: 8 }).valueStr }
|
||||
</Skeleton>
|
||||
);
|
||||
}
|
||||
if (item.value) {
|
||||
return (
|
||||
<Skeleton isLoaded={ !isLoading }>
|
||||
<Skeleton loading={ isLoading }>
|
||||
{ getCurrencyValue({ value: item.value, decimals: config.chain.currency.decimals.toString(), accuracy: 8 }).valueStr }
|
||||
</Skeleton>
|
||||
);
|
||||
@@ -84,9 +84,9 @@ const ItemByColumn = ({ item, column, isLoading }: Props) => {
|
||||
case 'asset':
|
||||
return item.token ?
|
||||
<TokenEntity token={ item.token } isLoading={ isLoading } fontWeight={ 700 } onlySymbol noCopy/> :
|
||||
<Skeleton isLoaded={ !isLoading } fontWeight={ 700 }>{ config.chain.currency.symbol }</Skeleton>;
|
||||
<Skeleton loading={ isLoading } fontWeight={ 700 }>{ config.chain.currency.symbol }</Skeleton>;
|
||||
case 'fee':
|
||||
return <Skeleton isLoaded={ !isLoading }>{ item.fee ? getCurrencyValue({ value: item.fee, accuracy: 8 }).valueStr : '-' }</Skeleton>;
|
||||
return <Skeleton loading={ isLoading }>{ item.fee ? getCurrencyValue({ value: item.fee, accuracy: 8 }).valueStr : '-' }</Skeleton>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export const TABLE_COLUMNS: Array<TxTableColumn> = [
|
||||
{
|
||||
id: 'or_and',
|
||||
name: '',
|
||||
width: '60px',
|
||||
width: '65px',
|
||||
},
|
||||
{
|
||||
id: 'to',
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { Flex, Select, Input, InputGroup, InputRightElement, VStack, IconButton } from '@chakra-ui/react';
|
||||
import { createListCollection, Flex, VStack } from '@chakra-ui/react';
|
||||
import { isEqual } from 'es-toolkit';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { AdvancedFilterParams } from 'types/api/advancedFilter';
|
||||
|
||||
import { IconButton } from 'toolkit/chakra/icon-button';
|
||||
import { Input } from 'toolkit/chakra/input';
|
||||
import { InputGroup } from 'toolkit/chakra/input-group';
|
||||
import { Select } from 'toolkit/chakra/select';
|
||||
import ClearButton from 'ui/shared/ClearButton';
|
||||
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
@@ -16,6 +20,13 @@ const FILTER_PARAM_FROM_EXCLUDE = 'from_address_hashes_to_exclude';
|
||||
|
||||
export type AddressFilterMode = 'include' | 'exclude';
|
||||
|
||||
const collection = createListCollection({
|
||||
items: [
|
||||
{ label: 'Include', value: 'include' },
|
||||
{ label: 'Exclude', value: 'exclude' },
|
||||
],
|
||||
});
|
||||
|
||||
type Value = Array<{ address: string; mode: AddressFilterMode }>;
|
||||
|
||||
type Props = {
|
||||
@@ -24,14 +35,13 @@ type Props = {
|
||||
columnName: string;
|
||||
type: 'from' | 'to';
|
||||
isLoading?: boolean;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
type InputProps = {
|
||||
address?: string;
|
||||
mode?: AddressFilterMode;
|
||||
isLast: boolean;
|
||||
onModeChange: (event: ChangeEvent<HTMLSelectElement>) => void;
|
||||
onModeChange: ({ value }: { value: Array<string> }) => void;
|
||||
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
onClear: () => void;
|
||||
onAddFieldClick: () => void;
|
||||
@@ -50,22 +60,19 @@ const AddressFilterInput = ({ address, mode, onModeChange, onChange, onClear, is
|
||||
return (
|
||||
<Flex alignItems="center" w="100%">
|
||||
<Select
|
||||
size="xs"
|
||||
borderRadius="base"
|
||||
value={ mode || 'include' }
|
||||
onChange={ onModeChange }
|
||||
minW="105px"
|
||||
collection={ collection }
|
||||
placeholder="Select mode"
|
||||
defaultValue={ [ mode || 'include' ] }
|
||||
onValueChange={ onModeChange }
|
||||
portalled={ false }
|
||||
w="105px"
|
||||
mr={ 3 }
|
||||
/>
|
||||
<InputGroup
|
||||
flexGrow={ 1 }
|
||||
endElement={ <ClearButton onClick={ onClear } isDisabled={ !address }/> }
|
||||
>
|
||||
<option value="include">Include</option>
|
||||
<option value="exclude">Exclude</option>
|
||||
</Select>
|
||||
<InputGroup size="xs" flexGrow={ 1 }>
|
||||
<Input value={ address } onChange={ onChange } placeholder="Smart contract / Address (0x...)*" size="xs" autoComplete="off"/>
|
||||
<InputRightElement>
|
||||
<ClearButton onClick={ onClear } isDisabled={ !address }/>
|
||||
</InputRightElement>
|
||||
<Input value={ address } onChange={ onChange } placeholder="Smart contract / Address (0x...)*" size="sm" autoComplete="off"/>
|
||||
</InputGroup>
|
||||
{ isLast && (
|
||||
<IconButton
|
||||
@@ -76,8 +83,9 @@ const AddressFilterInput = ({ address, mode, onModeChange, onChange, onClear, is
|
||||
h="30px"
|
||||
ml={ 2 }
|
||||
onClick={ onAddFieldClick }
|
||||
icon={ <IconSvg name="plus" w="20px" h="20px"/> }
|
||||
/>
|
||||
>
|
||||
<IconSvg name="plus" w="20px" h="20px"/>
|
||||
</IconButton>
|
||||
) }
|
||||
</Flex>
|
||||
);
|
||||
@@ -85,14 +93,13 @@ const AddressFilterInput = ({ address, mode, onModeChange, onChange, onClear, is
|
||||
|
||||
const emptyItem = { address: '', mode: 'include' as AddressFilterMode };
|
||||
|
||||
const AddressFilter = ({ type, value = [], handleFilterChange, onClose }: Props) => {
|
||||
const AddressFilter = ({ type, value = [], handleFilterChange }: Props) => {
|
||||
const [ currentValue, setCurrentValue ] =
|
||||
React.useState<Array<AddressFilter>>([ ...value, emptyItem ]);
|
||||
|
||||
const handleModeSelectChange = React.useCallback((index: number) => (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = event.target.value as AddressFilterMode;
|
||||
const handleModeSelectChange = React.useCallback((index: number) => ({ value }: { value: Array<string> }) => {
|
||||
setCurrentValue(prev => {
|
||||
prev[index] = { ...prev[index], mode: value };
|
||||
prev[index] = { ...prev[index], mode: value[0] as AddressFilterMode };
|
||||
return [ ...prev ];
|
||||
});
|
||||
}, []);
|
||||
@@ -138,7 +145,6 @@ const AddressFilter = ({ type, value = [], handleFilterChange, onClose }: Props)
|
||||
isTouched={ !isEqual(currentValue.filter(i => i.address).map(addressFilterToKey).sort(), value.map(addressFilterToKey).sort()) }
|
||||
onFilter={ onFilter }
|
||||
onReset={ onReset }
|
||||
onClose={ onClose }
|
||||
hasReset
|
||||
>
|
||||
<VStack gap={ 2 }>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Radio, RadioGroup, Stack, Box } from '@chakra-ui/react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { type AdvancedFilterParams } from 'types/api/advancedFilter';
|
||||
|
||||
import { Radio, RadioGroup } from 'toolkit/chakra/radio';
|
||||
|
||||
const FILTER_PARAM = 'address_relation';
|
||||
|
||||
type Value = 'or' | 'and';
|
||||
@@ -18,18 +20,16 @@ type Props = {
|
||||
};
|
||||
|
||||
const AddressRelationFilter = ({ value = DEFAULT_VALUE, handleFilterChange, onClose }: Props) => {
|
||||
const onFilter = React.useCallback((val: Value) => {
|
||||
const onFilter = React.useCallback(({ value }: { value: string }) => {
|
||||
onClose && onClose();
|
||||
handleFilterChange(FILTER_PARAM, val);
|
||||
handleFilterChange(FILTER_PARAM, value as Value);
|
||||
}, [ handleFilterChange, onClose ]);
|
||||
|
||||
return (
|
||||
<Box w="120px">
|
||||
<RadioGroup onChange={ onFilter } value={ value }>
|
||||
<Stack direction="column">
|
||||
<Radio value="or">OR</Radio>
|
||||
<Radio value="and">AND</Radio>
|
||||
</Stack>
|
||||
<RadioGroup onValueChange={ onFilter } value={ value } orientation="vertical">
|
||||
<Radio value="or">OR</Radio>
|
||||
<Radio value="and">AND</Radio>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Flex, Input, Text } from '@chakra-ui/react';
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { isEqual } from 'es-toolkit';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import React from 'react';
|
||||
@@ -7,6 +7,8 @@ import { ADVANCED_FILTER_AGES, type AdvancedFilterAge, type AdvancedFilterParams
|
||||
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import { ndash } from 'lib/html-entities';
|
||||
import { Input } from 'toolkit/chakra/input';
|
||||
import { PopoverCloseTriggerWrapper } from 'toolkit/chakra/popover';
|
||||
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
|
||||
import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect';
|
||||
|
||||
@@ -48,8 +50,8 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props)
|
||||
const to = dayjs().toISOString();
|
||||
handleFilterChange(FILTER_PARAM_TO, to);
|
||||
handleFilterChange(FILTER_PARAM_AGE, age);
|
||||
onClose && onClose();
|
||||
}, [ onClose, handleFilterChange ]);
|
||||
onClose?.();
|
||||
}, [ handleFilterChange, onClose ]);
|
||||
|
||||
const onReset = React.useCallback(() => setCurrentValue(defaultValue), []);
|
||||
|
||||
@@ -76,15 +78,16 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props)
|
||||
isTouched={ value.age ? value.age !== currentValue.age : !isEqual(currentValue, value) }
|
||||
onFilter={ onFilter }
|
||||
onReset={ onReset }
|
||||
onClose={ onClose }
|
||||
hasReset
|
||||
>
|
||||
<Flex gap={ 3 }>
|
||||
<TagGroupSelect<AdvancedFilterAge>
|
||||
items={ ADVANCED_FILTER_AGES.map(val => ({ id: val, title: val })) }
|
||||
onChange={ onPresetChange }
|
||||
value={ currentValue.age || undefined }
|
||||
/>
|
||||
<PopoverCloseTriggerWrapper>
|
||||
<TagGroupSelect<AdvancedFilterAge>
|
||||
items={ ADVANCED_FILTER_AGES.map(val => ({ id: val, title: val })) }
|
||||
onChange={ onPresetChange }
|
||||
value={ currentValue.age || undefined }
|
||||
/>
|
||||
</PopoverCloseTriggerWrapper>
|
||||
</Flex>
|
||||
<Flex mt={ 3 }>
|
||||
<Input
|
||||
@@ -92,7 +95,7 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props)
|
||||
onChange={ handleFromChange }
|
||||
placeholder="From"
|
||||
type="date"
|
||||
size="xs"
|
||||
size="sm"
|
||||
/>
|
||||
<Text mx={ 3 }>{ ndash }</Text>
|
||||
<Input
|
||||
@@ -100,7 +103,7 @@ const AgeFilter = ({ value = defaultValue, handleFilterChange, onClose }: Props)
|
||||
onChange={ handleToChange }
|
||||
placeholder="To"
|
||||
type="date"
|
||||
size="xs"
|
||||
size="sm"
|
||||
/>
|
||||
</Flex>
|
||||
</TableColumnFilter>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Flex, Input, Tag, Text } from '@chakra-ui/react';
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { isEqual } from 'es-toolkit';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import React from 'react';
|
||||
@@ -6,6 +6,9 @@ import React from 'react';
|
||||
import type { AdvancedFilterParams } from 'types/api/advancedFilter';
|
||||
|
||||
import { ndash } from 'lib/html-entities';
|
||||
import { Input } from 'toolkit/chakra/input';
|
||||
import { PopoverCloseTriggerWrapper } from 'toolkit/chakra/popover';
|
||||
import { Tag } from 'toolkit/chakra/tag';
|
||||
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
|
||||
|
||||
const FILTER_PARAM_FROM = 'amount_from';
|
||||
@@ -44,10 +47,9 @@ type AmountValue = { from?: string; to?: string };
|
||||
type Props = {
|
||||
value?: AmountValue;
|
||||
handleFilterChange: (filed: keyof AdvancedFilterParams, value?: string) => void;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
const AmountFilter = ({ value = {}, handleFilterChange, onClose }: Props) => {
|
||||
const AmountFilter = ({ value = {}, handleFilterChange }: Props) => {
|
||||
const [ currentValue, setCurrentValue ] = React.useState<AmountValue>(value || defaultValue);
|
||||
|
||||
const handleFromChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -69,8 +71,7 @@ const AmountFilter = ({ value = {}, handleFilterChange, onClose }: Props) => {
|
||||
const to = (event.currentTarget as HTMLDivElement).getAttribute('data-id') as string;
|
||||
handleFilterChange(FILTER_PARAM_FROM, '');
|
||||
handleFilterChange(FILTER_PARAM_TO, to);
|
||||
onClose && onClose();
|
||||
}, [ handleFilterChange, onClose ]);
|
||||
}, [ handleFilterChange ]);
|
||||
|
||||
return (
|
||||
<TableColumnFilter
|
||||
@@ -79,25 +80,26 @@ const AmountFilter = ({ value = {}, handleFilterChange, onClose }: Props) => {
|
||||
isTouched={ !isEqual(currentValue, value) }
|
||||
onFilter={ onFilter }
|
||||
onReset={ onReset }
|
||||
onClose={ onClose }
|
||||
hasReset
|
||||
>
|
||||
<Flex gap={ 3 }>
|
||||
{ PRESETS.map(preset => (
|
||||
<Tag
|
||||
key={ preset.value }
|
||||
data-id={ preset.value }
|
||||
onClick={ onPresetClick }
|
||||
variant="select"
|
||||
>
|
||||
{ preset.name }
|
||||
</Tag>
|
||||
)) }
|
||||
</Flex>
|
||||
<PopoverCloseTriggerWrapper>
|
||||
<Flex gap={ 3 }>
|
||||
{ PRESETS.map(preset => (
|
||||
<Tag
|
||||
key={ preset.value }
|
||||
data-id={ preset.value }
|
||||
onClick={ onPresetClick }
|
||||
variant="select"
|
||||
>
|
||||
{ preset.name }
|
||||
</Tag>
|
||||
)) }
|
||||
</Flex>
|
||||
</PopoverCloseTriggerWrapper>
|
||||
<Flex mt={ 3 } alignItems="center">
|
||||
<Input value={ currentValue.from } onChange={ handleFromChange } placeholder="From" type="number" size="xs"/>
|
||||
<Input value={ currentValue.from } onChange={ handleFromChange } placeholder="From" type="number" size="sm"/>
|
||||
<Text mx={ 3 }>{ ndash }</Text>
|
||||
<Input value={ currentValue.to } onChange={ handleToChange } placeholder="To" type="number" size="xs"/>
|
||||
<Input value={ currentValue.to } onChange={ handleToChange } placeholder="To" type="number" size="sm"/>
|
||||
</Flex>
|
||||
</TableColumnFilter>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Flex, Checkbox, CheckboxGroup, Text, Spinner, Select } from '@chakra-ui/react';
|
||||
import { Flex, Text, Spinner, createListCollection } from '@chakra-ui/react';
|
||||
import { isEqual } from 'es-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
@@ -7,7 +7,9 @@ import type { TokenInfo } from 'types/api/token';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import useDebounce from 'lib/hooks/useDebounce';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import { Checkbox, CheckboxGroup } from 'toolkit/chakra/checkbox';
|
||||
import { Select } from 'toolkit/chakra/select';
|
||||
import { Tag } from 'toolkit/chakra/tag';
|
||||
import ClearButton from 'ui/shared/ClearButton';
|
||||
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
|
||||
import FilterInput from 'ui/shared/filters/FilterInput';
|
||||
@@ -23,6 +25,13 @@ const NAME_PARAM_EXCLUDE = 'token_contract_symbols_to_exclude';
|
||||
|
||||
export type AssetFilterMode = 'include' | 'exclude';
|
||||
|
||||
const collection = createListCollection({
|
||||
items: [
|
||||
{ label: 'Include', value: 'include' },
|
||||
{ label: 'Exclude', value: 'exclude' },
|
||||
],
|
||||
});
|
||||
|
||||
// add native token
|
||||
type Value = Array<{ token: TokenInfo; mode: AssetFilterMode }>;
|
||||
|
||||
@@ -31,10 +40,9 @@ type Props = {
|
||||
handleFilterChange: (filed: keyof AdvancedFilterParams, val: Array<string>) => void;
|
||||
columnName: string;
|
||||
isLoading?: boolean;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
|
||||
const AssetFilter = ({ value = [], handleFilterChange }: Props) => {
|
||||
const [ currentValue, setCurrentValue ] = React.useState<Value>([ ...value ]);
|
||||
const [ searchTerm, setSearchTerm ] = React.useState<string>('');
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 300);
|
||||
@@ -43,11 +51,10 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
|
||||
setSearchTerm(value);
|
||||
}, []);
|
||||
|
||||
const handleModeSelectChange = React.useCallback((index: number) => (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = event.target.value as AssetFilterMode;
|
||||
const handleModeSelectChange = React.useCallback((index: number) => ({ value }: { value: Array<string> }) => {
|
||||
setCurrentValue(prev => {
|
||||
const newValue = [ ...prev ];
|
||||
newValue[index] = { ...prev[index], mode: value };
|
||||
newValue[index] = { ...prev[index], mode: value[0] as AssetFilterMode };
|
||||
return newValue;
|
||||
});
|
||||
}, []);
|
||||
@@ -88,11 +95,10 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
|
||||
isTouched={ !isEqual(currentValue.map(i => JSON.stringify(i)).sort(), value.map(i => JSON.stringify(i)).sort()) }
|
||||
onFilter={ onFilter }
|
||||
onReset={ onReset }
|
||||
onClose={ onClose }
|
||||
hasReset
|
||||
>
|
||||
<FilterInput
|
||||
size="xs"
|
||||
size="sm"
|
||||
onChange={ onSearchChange }
|
||||
placeholder="Token name or symbol"
|
||||
initialValue={ searchTerm }
|
||||
@@ -100,17 +106,15 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
|
||||
{ !searchTerm && currentValue.map((item, index) => (
|
||||
<Flex key={ item.token.address } alignItems="center">
|
||||
<Select
|
||||
size="xs"
|
||||
borderRadius="base"
|
||||
value={ item.mode }
|
||||
onChange={ handleModeSelectChange(index) }
|
||||
size="sm"
|
||||
value={ [ item.mode ] }
|
||||
onValueChange={ handleModeSelectChange(index) }
|
||||
collection={ collection }
|
||||
placeholder="Select mode"
|
||||
minW="105px"
|
||||
w="105px"
|
||||
mr={ 3 }
|
||||
>
|
||||
<option value="include">Include</option>
|
||||
<option value="exclude">Exclude</option>
|
||||
</Select>
|
||||
/>
|
||||
<TokenEntity.default token={ item.token } noLink noCopy flexGrow={ 1 }/>
|
||||
<ClearButton onClick={ handleRemove(index) }/>
|
||||
</Flex>
|
||||
@@ -139,25 +143,19 @@ const AssetFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
|
||||
{ searchTerm && tokensQuery.data && !tokensQuery.data?.items.length && <Text>No tokens found</Text> }
|
||||
{ searchTerm && tokensQuery.data && Boolean(tokensQuery.data?.items.length) && (
|
||||
<Flex display="flex" flexDir="column" rowGap={ 3 } maxH="250px" overflowY="scroll" mt={ 3 } ml="-4px">
|
||||
<CheckboxGroup value={ currentValue.map(i => i.token.address) }>
|
||||
<CheckboxGroup value={ currentValue.map(i => i.token.address) } orientation="vertical">
|
||||
{ tokensQuery.data.items.map(token => (
|
||||
<Flex key={ token.address }>
|
||||
<Checkbox
|
||||
value={ token.address }
|
||||
id={ token.address }
|
||||
onChange={ onTokenClick(token) }
|
||||
overflow="hidden"
|
||||
w="100%"
|
||||
pl={ 1 }
|
||||
sx={{
|
||||
'.chakra-checkbox__label': {
|
||||
flexGrow: 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TokenEntity.default token={ token } noLink noCopy/>
|
||||
</Checkbox>
|
||||
</Flex>
|
||||
<Checkbox
|
||||
key={ token.address }
|
||||
value={ token.address }
|
||||
id={ token.address }
|
||||
onChange={ onTokenClick(token) }
|
||||
overflow="hidden"
|
||||
w="100%"
|
||||
pl={ 1 }
|
||||
>
|
||||
<TokenEntity.default token={ token } noLink noCopy/>
|
||||
</Checkbox>
|
||||
)) }
|
||||
</CheckboxGroup>
|
||||
</Flex>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Flex, Checkbox, CheckboxGroup, Spinner, chakra } from '@chakra-ui/react';
|
||||
import { Flex, Spinner, chakra } from '@chakra-ui/react';
|
||||
import { isEqual, differenceBy } from 'es-toolkit';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { AdvancedFilterMethodInfo, AdvancedFilterParams } from 'types/api/advancedFilter';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import useDebounce from 'lib/hooks/useDebounce';
|
||||
import Tag from 'ui/shared/chakra/Tag';
|
||||
import { Badge } from 'toolkit/chakra/badge';
|
||||
import { Checkbox, CheckboxGroup } from 'toolkit/chakra/checkbox';
|
||||
import FilterInput from 'ui/shared/filters/FilterInput';
|
||||
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
|
||||
|
||||
@@ -19,10 +19,9 @@ const NAMES_PARAM = 'methods_names';
|
||||
type Props = {
|
||||
value?: Array<AdvancedFilterMethodInfo>;
|
||||
handleFilterChange: (filed: keyof AdvancedFilterParams, val: Array<string>) => void;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
const MethodFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
|
||||
const MethodFilter = ({ value = [], handleFilterChange }: Props) => {
|
||||
const [ currentValue, setCurrentValue ] = React.useState<Array<AdvancedFilterMethodInfo>>([ ...value ]);
|
||||
const [ searchTerm, setSearchTerm ] = React.useState<string>('');
|
||||
const debouncedSearchTerm = useDebounce(searchTerm, 300);
|
||||
@@ -43,9 +42,10 @@ const MethodFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
|
||||
}
|
||||
}, [ methodsQuery.data, value, methodsList ]);
|
||||
|
||||
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
const checked = event.target.checked;
|
||||
const id = event.target.id as string | typeof RESET_VALUE;
|
||||
const handleChange: React.FormEventHandler<HTMLLabelElement> = React.useCallback((event) => {
|
||||
const checked = (event.target as HTMLInputElement).checked;
|
||||
const id = event.currentTarget.getAttribute('data-id');
|
||||
|
||||
if (id === RESET_VALUE) {
|
||||
setCurrentValue([]);
|
||||
setMethodsList(methodsQuery.data || []);
|
||||
@@ -75,11 +75,10 @@ const MethodFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
|
||||
isTouched={ !isEqual(currentValue.map(i => JSON.stringify(i)).sort(), value.map(i => JSON.stringify(i)).sort()) }
|
||||
onFilter={ onFilter }
|
||||
onReset={ onReset }
|
||||
onClose={ onClose }
|
||||
hasReset
|
||||
>
|
||||
<FilterInput
|
||||
size="xs"
|
||||
size="sm"
|
||||
onChange={ onSearchChange }
|
||||
placeholder="Find by function name/ method ID"
|
||||
mb={ 3 }
|
||||
@@ -89,26 +88,23 @@ const MethodFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
|
||||
{ Boolean(searchTerm) && methodsQuery.data?.length === 0 && <span>No results found.</span> }
|
||||
{ methodsQuery.data && (
|
||||
// added negative margin because of checkbox focus styles & overflow hidden
|
||||
<Flex display="flex" flexDir="column" rowGap={ 3 } maxH="250px" overflowY="scroll" ml="-4px">
|
||||
<CheckboxGroup value={ currentValue.length ? currentValue.map(i => i.method_id) : [ RESET_VALUE ] }>
|
||||
<Flex display="flex" flexDir="column" rowGap={ 3 } maxH="250px" overflowY="scroll">
|
||||
<CheckboxGroup
|
||||
value={ currentValue.length ? currentValue.map(i => i.method_id) : [ ] }
|
||||
orientation="vertical"
|
||||
>
|
||||
{ (searchTerm ? methodsQuery.data : (methodsList || [])).map(method => (
|
||||
<Checkbox
|
||||
key={ method.method_id }
|
||||
value={ method.method_id }
|
||||
id={ method.method_id }
|
||||
data-id={ method.method_id }
|
||||
onChange={ handleChange }
|
||||
pl={ 1 }
|
||||
sx={{
|
||||
'.chakra-checkbox__label': {
|
||||
flexGrow: 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Flex justifyContent="space-between" alignItems="center" id={ method.method_id }>
|
||||
<chakra.span overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis">{ method.name || method.method_id }</chakra.span>
|
||||
<Tag colorScheme="gray" isTruncated ml={ 2 }>
|
||||
<Badge colorPalette="gray" truncated ml="auto">
|
||||
{ method.method_id }
|
||||
</Tag>
|
||||
</Badge>
|
||||
</Flex>
|
||||
</Checkbox>
|
||||
)) }
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Flex, Checkbox, CheckboxGroup } from '@chakra-ui/react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { isEqual, without } from 'es-toolkit';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { AdvancedFilterParams, AdvancedFilterType } from 'types/api/advancedFilter';
|
||||
|
||||
import { Checkbox, CheckboxGroup } from 'toolkit/chakra/checkbox';
|
||||
import TableColumnFilter from 'ui/shared/filters/TableColumnFilter';
|
||||
|
||||
import { ADVANCED_FILTER_TYPES_WITH_ALL } from '../constants';
|
||||
@@ -14,48 +14,49 @@ const RESET_VALUE = 'all';
|
||||
const FILTER_PARAM = 'transaction_types';
|
||||
|
||||
type Props = {
|
||||
value?: Array<AdvancedFilterType>;
|
||||
value?: Array<AdvancedFilterType | typeof RESET_VALUE>;
|
||||
handleFilterChange: (filed: keyof AdvancedFilterParams, value: Array<AdvancedFilterType>) => void;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
const TypeFilter = ({ value = [], handleFilterChange, onClose }: Props) => {
|
||||
const [ currentValue, setCurrentValue ] = React.useState<Array<AdvancedFilterType>>([ ...value ]);
|
||||
const TypeFilter = ({ value = [ RESET_VALUE ], handleFilterChange }: Props) => {
|
||||
const [ currentValue, setCurrentValue ] = React.useState<Array<AdvancedFilterType | typeof RESET_VALUE>>([ ...value ]);
|
||||
|
||||
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
const checked = event.target.checked;
|
||||
const id = event.target.id as AdvancedFilterType | typeof RESET_VALUE;
|
||||
if (id === RESET_VALUE) {
|
||||
setCurrentValue([]);
|
||||
} else {
|
||||
setCurrentValue(prev => checked ? [ ...prev, id ] : without(prev, id));
|
||||
}
|
||||
const handleChange = React.useCallback((value: Array<string>) => {
|
||||
setCurrentValue((prev) => {
|
||||
if (value.length === 0) {
|
||||
return [ RESET_VALUE ];
|
||||
}
|
||||
|
||||
const diff = value.filter(item => !prev.includes(item));
|
||||
if (diff.includes(RESET_VALUE)) {
|
||||
return [ RESET_VALUE ];
|
||||
}
|
||||
|
||||
return without(value as Array<AdvancedFilterType>, RESET_VALUE);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onReset = React.useCallback(() => setCurrentValue([]), []);
|
||||
const onReset = React.useCallback(() => setCurrentValue([ RESET_VALUE ]), []);
|
||||
|
||||
const onFilter = React.useCallback(() => {
|
||||
handleFilterChange(FILTER_PARAM, currentValue);
|
||||
handleFilterChange(FILTER_PARAM, currentValue.filter(item => item !== RESET_VALUE));
|
||||
}, [ handleFilterChange, currentValue ]);
|
||||
|
||||
return (
|
||||
<TableColumnFilter
|
||||
title="Type of transfer"
|
||||
isFilled={ currentValue.length > 0 }
|
||||
isFilled={ !(currentValue.length === 1 && currentValue[0] === RESET_VALUE) }
|
||||
isTouched={ !isEqual(currentValue.sort(), value.sort()) }
|
||||
onFilter={ onFilter }
|
||||
onReset={ onReset }
|
||||
onClose={ onClose }
|
||||
hasReset
|
||||
>
|
||||
<Flex display="flex" flexDir="column" rowGap={ 3 }>
|
||||
<CheckboxGroup value={ currentValue.length ? currentValue : [ RESET_VALUE ] }>
|
||||
<CheckboxGroup value={ currentValue } onValueChange={ handleChange } orientation="vertical">
|
||||
{ ADVANCED_FILTER_TYPES_WITH_ALL.map(type => (
|
||||
<Checkbox
|
||||
key={ type.id }
|
||||
value={ type.id }
|
||||
id={ type.id }
|
||||
onChange={ handleChange }
|
||||
>
|
||||
{ type.name }
|
||||
</Checkbox>
|
||||
|
||||
+53
-54
@@ -1,19 +1,9 @@
|
||||
import {
|
||||
Table,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
Thead,
|
||||
Box,
|
||||
Text,
|
||||
Tag,
|
||||
TagCloseButton,
|
||||
chakra,
|
||||
Flex,
|
||||
TagLabel,
|
||||
HStack,
|
||||
Link,
|
||||
} from '@chakra-ui/react';
|
||||
import { omit } from 'es-toolkit';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -31,6 +21,9 @@ import getValuesArrayFromQuery from 'lib/getValuesArrayFromQuery';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import { ADVANCED_FILTER_ITEM } from 'stubs/advancedFilter';
|
||||
import { generateListStub } from 'stubs/utils';
|
||||
import { Link } from 'toolkit/chakra/link';
|
||||
import { TableBody, TableCell, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
|
||||
import { Tag } from 'toolkit/chakra/tag';
|
||||
import ColumnsButton from 'ui/advancedFilter/ColumnsButton';
|
||||
import type { ColumnsIds } from 'ui/advancedFilter/constants';
|
||||
import { TABLE_COLUMNS } from 'ui/advancedFilter/constants';
|
||||
@@ -145,12 +138,12 @@ const AdvancedFilter = () => {
|
||||
const content = (
|
||||
<AddressHighlightProvider>
|
||||
<Box maxW="100%" overflowX="scroll" whiteSpace="nowrap">
|
||||
<Table style={{ tableLayout: 'fixed' }} minWidth="950px" w="100%">
|
||||
<Thead w="100%" display="table">
|
||||
<Tr>
|
||||
<TableRoot tableLayout="fixed" minWidth="950px" w="100%">
|
||||
<TableHeaderSticky>
|
||||
<TableRow>
|
||||
{ columnsToShow.map(column => {
|
||||
return (
|
||||
<Th
|
||||
<TableColumnHeader
|
||||
key={ column.id }
|
||||
isNumeric={ column.isNumeric }
|
||||
minW={ column.width }
|
||||
@@ -167,33 +160,45 @@ const AdvancedFilter = () => {
|
||||
searchParams={ data?.search_params }
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
</Th>
|
||||
</TableColumnHeader>
|
||||
);
|
||||
}) }
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody w="100%" display="table">
|
||||
</TableRow>
|
||||
</TableHeaderSticky>
|
||||
<TableBody>
|
||||
{ data?.items.map((item, index) => (
|
||||
<Tr key={ item.hash + String(index) }>
|
||||
{ columnsToShow.map(column => (
|
||||
<Td
|
||||
key={ item.hash + column.id }
|
||||
isNumeric={ column.isNumeric }
|
||||
minW={ column.width }
|
||||
maxW={ column.width }
|
||||
w={ column.width }
|
||||
wordBreak="break-word"
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
textAlign={ column.id === 'or_and' ? 'center' : 'start' }
|
||||
>
|
||||
<ItemByColumn item={ item } column={ column.id } isLoading={ isPlaceholderData }/>
|
||||
</Td>
|
||||
)) }
|
||||
</Tr>
|
||||
<TableRow key={ item.hash + String(index) }>
|
||||
{ columnsToShow.map(column => {
|
||||
const textAlign = (() => {
|
||||
if (column.id === 'or_and') {
|
||||
return 'center';
|
||||
}
|
||||
if (column.isNumeric) {
|
||||
return 'right';
|
||||
}
|
||||
return 'start';
|
||||
})();
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
key={ item.hash + column.id }
|
||||
isNumeric={ column.isNumeric }
|
||||
minW={ column.width }
|
||||
maxW={ column.width }
|
||||
w={ column.width }
|
||||
wordBreak="break-word"
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
textAlign={ textAlign }
|
||||
>
|
||||
<ItemByColumn item={ item } column={ column.id } isLoading={ isPlaceholderData }/>
|
||||
</TableCell>
|
||||
);
|
||||
}) }
|
||||
</TableRow>
|
||||
)) }
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableBody>
|
||||
</TableRoot>
|
||||
</Box>
|
||||
</AddressHighlightProvider>
|
||||
);
|
||||
@@ -223,42 +228,36 @@ const AdvancedFilter = () => {
|
||||
</Flex>
|
||||
<HStack gap={ 2 } flexWrap="wrap" mb={ 6 }>
|
||||
{ filterTags.map(t => (
|
||||
<Tag key={ t.name } colorScheme="blue" display="inline-flex">
|
||||
<TagLabel>
|
||||
<chakra.span color="text_secondary">{ t.name }: </chakra.span>
|
||||
<chakra.span color="text">{ t.value }</chakra.span>
|
||||
</TagLabel>
|
||||
<TagCloseButton onClick={ onClearFilter(t.key) }/>
|
||||
<Tag key={ t.name } colorScheme="blue" display="inline-flex" onClose={ onClearFilter(t.key) } closable>
|
||||
<chakra.span color="text_secondary">{ t.name }: </chakra.span>
|
||||
<chakra.span color="text">{ t.value }</chakra.span>
|
||||
</Tag>
|
||||
)) }
|
||||
{ filterTags.length === 0 && (
|
||||
<>
|
||||
<Tag colorScheme="blue" display="inline-flex">
|
||||
<TagLabel>
|
||||
<chakra.span color="text_secondary">Type: </chakra.span>
|
||||
<chakra.span color="text">All</chakra.span>
|
||||
</TagLabel>
|
||||
<chakra.span color="text_secondary">Type: </chakra.span>
|
||||
<chakra.span color="text">All</chakra.span>
|
||||
</Tag>
|
||||
<Tag colorScheme="blue" display="inline-flex">
|
||||
<TagLabel>
|
||||
<chakra.span color="text_secondary">Age: </chakra.span>
|
||||
<chakra.span color="text">7d</chakra.span>
|
||||
</TagLabel>
|
||||
<chakra.span color="text_secondary">Age: </chakra.span>
|
||||
<chakra.span color="text">7d</chakra.span>
|
||||
</Tag>
|
||||
</>
|
||||
) }
|
||||
</HStack>
|
||||
<DataListDisplay
|
||||
isError={ isError }
|
||||
items={ data?.items }
|
||||
itemsNum={ data?.items.length }
|
||||
emptyText="There are no transactions."
|
||||
content={ content }
|
||||
actionBar={ actionBar }
|
||||
filterProps={{
|
||||
hasActiveFilters: Object.values(filters).some(Boolean),
|
||||
emptyFilteredText: 'No match found for current filter',
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{ content }
|
||||
</DataListDisplay>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ const RawInputData = ({ hex, rightSlot: rightSlotProp, defaultDataType = 'Hex',
|
||||
mr="auto"
|
||||
>
|
||||
<SelectControl loading={ isLoading }>
|
||||
<SelectValueText placeholder="Select framework"/>
|
||||
<SelectValueText placeholder="Select type"/>
|
||||
</SelectControl>
|
||||
<SelectContent>
|
||||
{ collection.items.map((item) => (
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Box, useColorModeValue } from '@chakra-ui/react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { useColorModeValue } from 'toolkit/chakra/color-mode';
|
||||
|
||||
interface Props {
|
||||
isVerified: boolean;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { As } from '@chakra-ui/react';
|
||||
import { chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
@@ -60,21 +59,20 @@ export interface EntityProps extends EntityBase.EntityBaseProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
const UserOpEntity = (props: EntityProps) => {
|
||||
const ValidatorEntity = (props: EntityProps) => {
|
||||
const partsProps = distributeEntityProps(props);
|
||||
const content = <Content { ...partsProps.content }/>;
|
||||
|
||||
return (
|
||||
<Container { ...partsProps.container }>
|
||||
<Icon { ...partsProps.icon }/>
|
||||
<Link { ...partsProps.link }>
|
||||
<Content { ...partsProps.content }/>
|
||||
</Link>
|
||||
{ props.noLink ? content : <Link { ...partsProps.link }>{ content }</Link> }
|
||||
<Copy { ...partsProps.copy }/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(chakra<As, EntityProps>(UserOpEntity));
|
||||
export default React.memo(chakra(ValidatorEntity));
|
||||
|
||||
export {
|
||||
Container,
|
||||
|
||||
@@ -24,7 +24,7 @@ const TableColumnFilter = ({ title, isFilled, isTouched, hasReset, onFilter, onR
|
||||
}, [ onFilter ]);
|
||||
return (
|
||||
<>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<Flex alignItems="center" justifyContent="space-between" columnGap={ 6 }>
|
||||
<Text color="text.secondary" fontWeight="600">{ title }</Text>
|
||||
{ hasReset && (
|
||||
<Button
|
||||
|
||||
@@ -19,6 +19,7 @@ const TableColumnFilterWrapper = ({ columnName, className, children, isLoading,
|
||||
<PopoverRoot>
|
||||
<PopoverTrigger>
|
||||
<Button
|
||||
display="inline-flex"
|
||||
aria-label={ `filter by ${ columnName }` }
|
||||
variant="dropdown"
|
||||
borderWidth="0"
|
||||
@@ -28,16 +29,9 @@ const TableColumnFilterWrapper = ({ columnName, className, children, isLoading,
|
||||
selected={ selected }
|
||||
borderRadius="4px"
|
||||
size="sm"
|
||||
textStyle="sm"
|
||||
fontWeight={ 500 }
|
||||
padding={ 0 }
|
||||
css={{
|
||||
'span:only-child': {
|
||||
mx: 0,
|
||||
},
|
||||
'span:not(:only-child)': {
|
||||
mr: '2px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<IconSvg name="filter" w="19px" h="19px"/>
|
||||
{ Boolean(value) && <chakra.span>{ value }</chakra.span> }
|
||||
|
||||
@@ -19,7 +19,7 @@ type Props<T extends string> = {
|
||||
}
|
||||
);
|
||||
|
||||
const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tagSize }: Props<T>) => {
|
||||
const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tagSize, ...rest }: Props<T>) => {
|
||||
const onItemClick = React.useCallback((event: React.SyntheticEvent) => {
|
||||
const itemValue = (event.currentTarget as HTMLDivElement).getAttribute('data-id') as T;
|
||||
if (isMulti) {
|
||||
@@ -36,7 +36,7 @@ const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tag
|
||||
}, [ isMulti, onChange, value ]);
|
||||
|
||||
return (
|
||||
<HStack>
|
||||
<HStack { ...rest }>
|
||||
{ items.map(item => {
|
||||
const isSelected = isMulti ? value.includes(item.id) : value === item.id;
|
||||
return (
|
||||
|
||||
@@ -47,6 +47,15 @@ const TagShowcase = () => {
|
||||
</SamplesStack>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<SectionHeader>Closable</SectionHeader>
|
||||
<SamplesStack>
|
||||
<Sample label="closable: true">
|
||||
<Tag closable>My tag</Tag>
|
||||
</Sample>
|
||||
</SamplesStack>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<SectionHeader>Loading</SectionHeader>
|
||||
<SamplesStack>
|
||||
|
||||
Reference in New Issue
Block a user