From 5ceb728edf4b297368b864ff680c3304594a9709 Mon Sep 17 00:00:00 2001 From: Kristof Csillag Date: Sun, 31 Mar 2024 20:59:23 +0200 Subject: [PATCH] Proposal votes: add search by validator name --- .../components/Proposals/VoterSearchBar.tsx | 70 +++++++++++++++++++ .../Validators/DeferredValidatorLink.tsx | 12 +++- .../components/Validators/ValidatorLink.tsx | 31 ++++++-- .../ProposalDetailsPage/ProposalVotesCard.tsx | 21 +++++- src/app/pages/ProposalDetailsPage/hooks.ts | 25 ++++++- 5 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 src/app/components/Proposals/VoterSearchBar.tsx diff --git a/src/app/components/Proposals/VoterSearchBar.tsx b/src/app/components/Proposals/VoterSearchBar.tsx new file mode 100644 index 0000000000..7b1781a3d4 --- /dev/null +++ b/src/app/components/Proposals/VoterSearchBar.tsx @@ -0,0 +1,70 @@ +import { FC } from 'react' +import TextField from '@mui/material/TextField' +import SearchIcon from '@mui/icons-material/Search' +import HighlightOffIcon from '@mui/icons-material/HighlightOff' +import InputAdornment from '@mui/material/InputAdornment' +import { COLORS } from '../../../styles/theme/colors' +import { SearchVariant } from '../Search' +import { useTranslation } from 'react-i18next' +import IconButton from '@mui/material/IconButton' + +export interface SearchBarProps { + variant: SearchVariant + value: string + onChange: (value: string) => void +} + +export const VoterSearchBar: FC = ({ variant, value, onChange }) => { + const { t } = useTranslation() + const startAdornment = variant === 'button' && ( + + + + ) + + const onClearValue = () => onChange('') + + const endAdornment = ( + + <> + {value && ( + + + + )} + + + ) + + return ( + <> + onChange(e.target.value)} + InputProps={{ + inputProps: { + sx: { + p: 0, + marginRight: 2, + }, + }, + startAdornment, + endAdornment, + }} + placeholder={t('networkProposal.searchForVoters')} + // FormHelperTextProps={{ + // component: 'div', // replace p with div tag + // sx: { + // marginTop: 0, + // marginBottom: 0, + // marginLeft: variant === 'button' ? '48px' : '17px', + // marginRight: variant === 'button' ? '48px' : '17px', + // }, + // }} + /> + + ) +} diff --git a/src/app/components/Validators/DeferredValidatorLink.tsx b/src/app/components/Validators/DeferredValidatorLink.tsx index e703a6b53a..79df097899 100644 --- a/src/app/components/Validators/DeferredValidatorLink.tsx +++ b/src/app/components/Validators/DeferredValidatorLink.tsx @@ -9,12 +9,20 @@ export const DeferredValidatorLink: FC<{ address: string validator: Validator | undefined isError: boolean -}> = ({ network, address, validator, isError }) => { + highlightedPart?: string | undefined +}> = ({ network, address, validator, isError, highlightedPart }) => { const scope: SearchScope = { network, layer: Layer.consensus } if (isError) { console.log('Warning: failed to look up validators!') } - return + return ( + + ) } diff --git a/src/app/components/Validators/ValidatorLink.tsx b/src/app/components/Validators/ValidatorLink.tsx index f2b3c0102d..670625b853 100644 --- a/src/app/components/Validators/ValidatorLink.tsx +++ b/src/app/components/Validators/ValidatorLink.tsx @@ -7,23 +7,37 @@ import { RouteUtils } from '../../utils/route-utils' import Typography from '@mui/material/Typography' import { COLORS } from '../../../styles/theme/colors' import { Network } from '../../../types/network' +import { HighlightedText } from '../HighlightedText' type ValidatorLinkProps = { address: string name?: string network: Network alwaysTrim?: boolean + highlightedPart?: string } -export const ValidatorLink: FC = ({ address, name, network, alwaysTrim }) => { +export const ValidatorLink: FC = ({ + address, + name, + network, + alwaysTrim, + highlightedPart, +}) => { const { isTablet } = useScreenSize() const to = RouteUtils.getValidatorRoute(network, address) return ( {isTablet ? ( - + ) : ( - + )} ) @@ -42,6 +56,7 @@ type TabletValidatorLinkProps = { address: string name?: string to: string + highlightedPart?: string // TODO: add support for highlighting on tablet } const TabletValidatorLink: FC = ({ address, name, to }) => { @@ -55,13 +70,19 @@ type DesktopValidatorLinkProps = TabletValidatorLinkProps & { alwaysTrim?: boolean } -const DesktopValidatorLink: FC = ({ address, name, to, alwaysTrim }) => { +const DesktopValidatorLink: FC = ({ + address, + name, + to, + alwaysTrim, + highlightedPart, +}) => { if (alwaysTrim) { return } return ( - {name ?? address} + {name ? : address} ) } diff --git a/src/app/pages/ProposalDetailsPage/ProposalVotesCard.tsx b/src/app/pages/ProposalDetailsPage/ProposalVotesCard.tsx index 0437fd965e..270671d159 100644 --- a/src/app/pages/ProposalDetailsPage/ProposalVotesCard.tsx +++ b/src/app/pages/ProposalDetailsPage/ProposalVotesCard.tsx @@ -6,13 +6,21 @@ import { TablePaginationProps } from '../../components/Table/TablePagination' import { useTranslation } from 'react-i18next' import { Table, TableCellAlign, TableColProps } from '../../components/Table' import { ExtendedVote, ProposalVoteValue } from '../../../types/vote' -import { PAGE_SIZE, useAllVotes, useDisplayedVotes, useWantedVoteType } from './hooks' +import { + PAGE_SIZE, + useAllVotes, + useDisplayedVotes, + useVoterSearch, + useVoterSearchPattern, + useWantedVoteType, +} from './hooks' import { ProposalVoteIndicator } from '../../components/Proposals/ProposalVoteIndicator' import { DeferredValidatorLink } from '../../components/Validators/DeferredValidatorLink' import { CardHeaderWithResponsiveActions } from '../../components/CardHeaderWithResponsiveActions' import { VoteTypePills } from '../../components/Proposals/VoteTypePills' import { AppErrors } from '../../../types/errors' import { ErrorBoundary } from '../../components/ErrorBoundary' +import { VoterSearchBar } from '../../components/Proposals/VoterSearchBar' type ProposalVotesProps = { isLoading: boolean @@ -25,6 +33,8 @@ const ProposalVotes: FC = ({ isLoading, votes, limit, pagina const { t } = useTranslation() const scope = useRequiredScopeParam() + const voterNameFragment = useVoterSearchPattern() + const tableColumns: TableColProps[] = [ { key: 'index', content: <>, width: '50px' }, { key: 'voter', content: t('common.voter'), align: TableCellAlign.Left }, @@ -46,6 +56,7 @@ const ProposalVotes: FC = ({ isLoading, votes, limit, pagina address={vote.address} isError={vote.haveValidatorsFailed} validator={vote.validator} + highlightedPart={voterNameFragment} /> ), }, @@ -94,11 +105,17 @@ export const ProposalVotesCard: FC = () => { const { t } = useTranslation() const { wantedVoteType, setWantedVoteType } = useWantedVoteType() + const { voterSearchInput, setVoterSearchPattern } = useVoterSearch() return ( } + action={ + <> + + + + } disableTypography component="h3" title={t('common.votes')} diff --git a/src/app/pages/ProposalDetailsPage/hooks.ts b/src/app/pages/ProposalDetailsPage/hooks.ts index 87b1c33a54..d18f6da723 100644 --- a/src/app/pages/ProposalDetailsPage/hooks.ts +++ b/src/app/pages/ProposalDetailsPage/hooks.ts @@ -132,7 +132,30 @@ export const useWantedVoteType = () => { } } -const useWantedVoteFilter = (): VoteFilter => getFilterForVoteType(useWantedVoteType().wantedVoteType) +export const useVoterSearch = () => { + const { value, setValue } = useStringInUrl('voter') + return { + voterSearchInput: value, + setVoterSearchPattern: (newValue: string) => setValue(newValue, { deleteParams: ['page'] }), + } +} + +export const useVoterSearchPattern = () => { + const { voterSearchInput } = useVoterSearch() + return voterSearchInput.length < 3 ? undefined : voterSearchInput +} + +const useWantedVoteFilter = (): VoteFilter => { + const typeFilter = getFilterForVoteType(useWantedVoteType().wantedVoteType) + const voterSearchPattern = useVoterSearchPattern() + + if (!voterSearchPattern) { + return typeFilter + } else { + return (vote: ExtendedVote) => + typeFilter(vote) && !!vote.validator?.media?.name?.toLowerCase().includes(voterSearchPattern) + } +} export const PAGE_SIZE = NUMBER_OF_ITEMS_ON_SEPARATE_PAGE