Skip to content

Commit

Permalink
advanced filters stage 2
Browse files Browse the repository at this point in the history
  • Loading branch information
isstuev committed Jun 17, 2024
1 parent 3b78c1f commit 7dc52c6
Show file tree
Hide file tree
Showing 19 changed files with 469 additions and 103 deletions.
6 changes: 4 additions & 2 deletions lib/api/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -801,8 +801,10 @@ export const RESOURCES = {
'age_from' as const,
'age_to' as const,
'age' as const /* frontend only */,
'from_address_hashes' as const,
'to_address_hashes' as const,
'from_address_hashes_to_include' as const,
'from_address_hashes_to_exclude' as const,
'to_address_hashes_to_include' as const,
'to_address_hashes_to_exclude' as const,
'address_relation' as const,
'amount_from' as const,
'amount_to' as const,
Expand Down
1 change: 1 addition & 0 deletions lib/metadata/getPageOgType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/name-domains/[name]': 'Regular page',
'/validators': 'Root page',
'/gas-tracker': 'Root page',
'/advanced-filter': 'Root page',

// service routes, added only to make typescript happy
'/login': 'Regular page',
Expand Down
1 change: 1 addition & 0 deletions lib/metadata/templates/description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/name-domains/[name]': DEFAULT_TEMPLATE,
'/validators': DEFAULT_TEMPLATE,
'/gas-tracker': DEFAULT_TEMPLATE,
'/advanced-filter': DEFAULT_TEMPLATE,

// service routes, added only to make typescript happy
'/login': DEFAULT_TEMPLATE,
Expand Down
1 change: 1 addition & 0 deletions lib/metadata/templates/title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/name-domains/[name]': '%network_name% %name% domain details',
'/validators': '%network_name% validators list',
'/gas-tracker': '%network_name% gas tracker - Current gas fees',
'/advanced-filter': '%network_name% advanced filter',

// service routes, added only to make typescript happy
'/login': '%network_name% login',
Expand Down
1 change: 1 addition & 0 deletions lib/mixpanel/getPageType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/name-domains/[name]': 'Domain details',
'/validators': 'Validators list',
'/gas-tracker': 'Gas tracker',
'/advanced-filter': 'Advanced filter',

// service routes, added only to make typescript happy
'/login': 'Login',
Expand Down
17 changes: 17 additions & 0 deletions stubs/advancedFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { AdvancedFilterResponseItem } from 'types/api/advancedFilter';

import { ADDRESS_PARAMS } from './addressParams';
import { TX_HASH } from './tx';

export const ADVANCED_FILTER_ITEM: AdvancedFilterResponseItem = {

Check failure on line 6 in stubs/advancedFilter.ts

View workflow job for this annotation

GitHub Actions / Code quality

Property 'created_contract' is missing in type '{ fee: string; from: AddressParam; hash: string; method: string; timestamp: string; to: AddressParam; token: null; total: null; type: string; value: string; }' but required in type 'AdvancedFilterResponseItem'.
fee: '215504444616317',
from: ADDRESS_PARAMS,
hash: TX_HASH,
method: 'approve',
timestamp: '2022-11-11T11:11:11.000000Z',
to: ADDRESS_PARAMS,
token: null,
total: null,
type: 'coin_transfer',
value: '42000420000000000000',
};
13 changes: 8 additions & 5 deletions types/api/advancedFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ export type AdvancedFilterParams = {
age_from?: string;
age_to?: string;
age?: AdvancedFilterAge; /* frontend only */
from_address_hashes?: Array<string>;
to_address_hashes?: Array<string>;
from_address_hashes_to_include?: Array<string>;
from_address_hashes_to_exclude?: Array<string>;
to_address_hashes_to_include?: Array<string>;
to_address_hashes_to_exclude?: Array<string>;
address_relation?: 'or' | 'and';
amount_from?: string;
amount_to?: string;
Expand All @@ -25,6 +27,7 @@ export type AdvancedFilterAge = typeof ADVANCED_FILTER_AGES[number];
export type AdvancedFilterResponseItem = {
fee: string;
from: AddressParam;
created_contract: AddressParam;
hash: string;
method: string | null;
timestamp: string;
Expand All @@ -39,8 +42,8 @@ export type AdvancedFilterResponseItem = {
}

export type AdvancedFiltersSearchParams = {
methods: Array<AdvancedFilterMethodInfo>;
tokens: Array<TokenInfo>;
methods: Record<string, AdvancedFilterMethodInfo>;
tokens: Record<string, TokenInfo>;
}

export type AdvancedFilterResponse = {
Expand All @@ -59,5 +62,5 @@ export type AdvancedFilterMethodsResponse = Array<AdvancedFilterMethodInfo>;

export type AdvancedFilterMethodInfo = {
method_id: string;
name: string;
name?: string;
}
107 changes: 56 additions & 51 deletions ui/advancedFilter/ColumnFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
useDisclosure,
IconButton,
chakra,
Flex,
Text,
Expand All @@ -13,63 +7,74 @@ import {
} from '@chakra-ui/react';
import React from 'react';

import IconSvg from 'ui/shared/IconSvg';
import ColumnFilterWrapper from './ColumnFilterWrapper';

interface Props {
type Props = {
columnName: string;
title: string;
isActive?: boolean;
isFilled?: boolean;
onFilter: () => void;
onReset: () => void;
onReset?: () => void;
onClose?: () => void;
isLoading?: boolean;
className?: string;
children: React.ReactNode;
}

const ColumnFilter = ({ columnName, title, isActive, isFilled, onFilter, onReset, className, children }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
type ContentProps = {
title: string;
isFilled?: boolean;
onFilter: () => void;
onReset?: () => void;
onClose?: () => void;
children: React.ReactNode;
}

const ColumnFilterContent = ({ title, isFilled, onFilter, onReset, onClose, children }: ContentProps) => {
const onFilterClick = React.useCallback(() => {
onClose && onClose();
onFilter();
}, [ onClose, onFilter ]);
return (
<>
<Flex alignItems="center" justifyContent="space-between" mb={ 3 }>
<Text color="text_secondary" fontWeight="600">{ title }</Text>
<Link
onClick={ onReset }
cursor={ isFilled ? 'pointer' : 'unset' }
opacity={ isFilled ? 1 : 0.2 }
_hover={{
color: isFilled ? 'link_hovered' : 'none',
}}
>
Reset
</Link>
</Flex>
{ children }
<Button
// ???
// isDisabled={ !isFilled }
mt={ 4 }
onClick={ onFilterClick }
w="fit-content"
>
Filter
</Button>
</>
);
};

const ColumnFilter = ({ columnName, isActive, className, isLoading, ...props }: Props) => {
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
<IconButton
onClick={ onToggle }
aria-label={ `filter by ${ columnName }` }
variant="ghost"
w="30px"
h="30px"
icon={ <IconSvg name="filter" w="20px" h="20px"/> }
isActive={ isActive }
/>
</PopoverTrigger>
<PopoverContent className={ className }>
<PopoverBody px={ 4 } py={ 6 } display="flex" flexDir="column" rowGap={ 5 }>
<chakra.form
noValidate
onSubmit={ onFilter }
textAlign="start"
>
<Flex alignItems="center" justifyContent="space-between" mb={ 3 }>
<Text color="text_secondary" fontWeight="600">{ title }</Text>
<Link
onClick={ onReset }
cursor={ isFilled ? 'pointer' : 'unset' }
opacity={ isFilled ? 1 : 0.2 }
_hover={{
color: isFilled ? 'link_hovered' : 'none',
}}
>
Reset
</Link>
</Flex>
{ children }
<Button type="submit" isDisabled={ !isFilled } mt={ 4 }>
Filter
</Button>
</chakra.form>
</PopoverBody>
</PopoverContent>
</Popover>
<ColumnFilterWrapper
isActive={ isActive }
columnName={ columnName }
className={ className }
isLoading={ isLoading }
>
<ColumnFilterContent { ...props }/>
</ColumnFilterWrapper>
);
};

Expand Down
57 changes: 57 additions & 0 deletions ui/advancedFilter/ColumnFilterWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
useDisclosure,
IconButton,
chakra,
} from '@chakra-ui/react';
import React from 'react';

import IconSvg from 'ui/shared/IconSvg';

interface Props {
columnName: string;
isActive?: boolean;
isLoading?: boolean;
className?: string;
children: React.ReactNode;
}

const ColumnFilterWrapper = ({ columnName, isActive, className, children, isLoading }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();

const child = React.Children.only(children) as React.ReactElement & {
ref?: React.Ref<React.ReactNode>;
};

const modifiedChildren = React.cloneElement(
child,
{ onClose },
);

return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy lazyBehavior="unmount">
<PopoverTrigger>
<IconButton
onClick={ onToggle }
aria-label={ `filter by ${ columnName }` }
variant="ghost"
w="30px"
h="30px"
icon={ <IconSvg name="filter" w="20px" h="20px"/> }
isActive={ isActive }
isDisabled={ isLoading }
/>
</PopoverTrigger>
<PopoverContent className={ className }>
<PopoverBody px={ 4 } py={ 6 } display="flex" flexDir="column" rowGap={ 5 }>
{ modifiedChildren }
</PopoverBody>
</PopoverContent>
</Popover>
);
};

export default chakra(ColumnFilterWrapper);
50 changes: 36 additions & 14 deletions ui/advancedFilter/FilterByColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import type { AdvancedFilterParams, AdvancedFiltersSearchParams } from 'types/ap

import type { ColumnsIds } from 'ui/pages/AdvancedFilter';

import type { AddressFilterMode } from './filters/AddressFilter';
import AddressFilter from './filters/AddressFilter';
import AddressRelationFilter from './filters/AddressRelationFilter';
import AgeFilter from './filters/AgeFilter';
import AmountFilter from './filters/AmountFilter';
import type { AssetFilterMode } from './filters/AssetFilter';
Expand All @@ -17,32 +20,51 @@ type Props = {
column: ColumnsIds;
columnName: string;
handleFilterChange: (field: keyof AdvancedFilterParams, val: unknown) => void;
isLoading?: boolean;
}
const FilterByColumn = ({ column, filters, searchParams, columnName, handleFilterChange }: Props) => {
const commonProps = { columnName, handleFilterChange };
const FilterByColumn = ({ column, filters, columnName, handleFilterChange, searchParams, isLoading }: Props) => {
const commonProps = { columnName, handleFilterChange, isLoading };
switch (column) {
case 'type': {
case 'type':
return <TypeFilter { ...commonProps } value={ filters.tx_types }/>;
case 'method': {
const value = filters.methods?.map(m => searchParams?.methods[m] || { method_id: m });
return <MethodFilter { ...commonProps } value={ value }/>;
}
case 'method':
// fix value
return <MethodFilter { ...commonProps } value={ searchParams?.methods }/>;
case 'age':
return <AgeFilter { ...commonProps } value={{ age: filters.age, from: filters.age_from, to: filters.age_to }}/>;
// case 'from':
// return <AddressEntity address={ item.from } truncation="constant"/>;
// case 'to':
// return <AddressEntity address={ item.to } truncation="constant"/>;
case 'or_and':
return <AddressRelationFilter { ...commonProps } value={ filters.address_relation }/>;
case 'from': {
const valueInclude = filters?.from_address_hashes_to_include?.map(hash => ({ address: hash, mode: 'include' as AddressFilterMode }));
const valueExclude = filters?.from_address_hashes_to_exclude?.map(hash => ({ address: hash, mode: 'exclude' as AddressFilterMode }));

const value = (valueInclude || []).concat(valueExclude || []);

return <AddressFilter { ...commonProps } type="from" value={ value }/>;

}
case 'to': {
const valueInclude = filters?.to_address_hashes_to_include?.map(hash => ({ address: hash, mode: 'include' as AddressFilterMode }));
const valueExclude = filters?.to_address_hashes_to_exclude?.map(hash => ({ address: hash, mode: 'exclude' as AddressFilterMode }));

const value = (valueInclude || []).concat(valueExclude || []);

return <AddressFilter { ...commonProps } type="to" value={ value }/>;
}
case 'amount':
// fix types
return <AmountFilter { ...commonProps } value={{ from: filters.amount_from, to: filters.amount_to }}/>;
case 'asset': {
const tokens = searchParams?.tokens;

const value = tokens ?
Object.entries(tokens).map(([ address, token ]) =>
({ token, mode: filters.token_contract_address_hashes_to_include?.includes(address) ? 'include' as AssetFilterMode : 'exclude' as AssetFilterMode }),
) : [];
Object.entries(tokens).map(([ address, token ]) => {
const mode = filters.token_contract_address_hashes_to_include?.find(i => i.toLowerCase() === address) ?
'include' as AssetFilterMode :
'exclude' as AssetFilterMode;
return ({ token, mode });
}) : [];

return <AssetFilter { ...commonProps } value={ value }/>;
}
default:
Expand Down
Loading

0 comments on commit 7dc52c6

Please sign in to comment.