From 9b2a36a0e6485297e482f7050f73de7132f6d5c2 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 --- src/app/components/Search/TableSearchBar.tsx | 1 - .../Validators/DeferredValidatorLink.tsx | 12 +++++- .../components/Validators/ValidatorLink.tsx | 40 ++++++++++++++----- .../ProposalDetailsPage/ProposalVotesCard.tsx | 40 +++++++++++++++++-- src/app/pages/ProposalDetailsPage/hooks.ts | 38 +++++++++++++++++- src/locales/en/translation.json | 1 + 6 files changed, 116 insertions(+), 16 deletions(-) diff --git a/src/app/components/Search/TableSearchBar.tsx b/src/app/components/Search/TableSearchBar.tsx index 8b3106ad95..c821355cf9 100644 --- a/src/app/components/Search/TableSearchBar.tsx +++ b/src/app/components/Search/TableSearchBar.tsx @@ -6,7 +6,6 @@ import InputAdornment from '@mui/material/InputAdornment' import { COLORS } from '../../../styles/theme/colors' import IconButton from '@mui/material/IconButton' import { useScreenSize } from '../../hooks/useScreensize' -import Box from '@mui/material/Box' import WarningIcon from '@mui/icons-material/WarningAmber' import { typingDelay } from '../../../styles/theme' import Typography from '@mui/material/Typography' 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..85f11dd30b 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 ? ( - + ) : ( - + )} ) @@ -32,21 +46,23 @@ export const ValidatorLink: FC = ({ address, name, network, type TrimValidatorEndLinkLabelProps = { name: string to: string + highlightedPart?: string } -const TrimValidatorEndLinkLabel: FC = ({ name, to }) => ( - +const TrimValidatorEndLinkLabel: FC = ({ name, to, highlightedPart }) => ( + ) type TabletValidatorLinkProps = { address: string name?: string to: string + highlightedPart?: string } -const TabletValidatorLink: FC = ({ address, name, to }) => { +const TabletValidatorLink: FC = ({ address, name, to, highlightedPart }) => { if (name) { - return + return } return } @@ -55,13 +71,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 7cf5de63f2..d6254d96be 100644 --- a/src/app/pages/ProposalDetailsPage/ProposalVotesCard.tsx +++ b/src/app/pages/ProposalDetailsPage/ProposalVotesCard.tsx @@ -6,13 +6,24 @@ 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, + useVoteFilters, + useVoterSearch, + useVoterSearchError, + useVoterSearchPattern, + useWantedVoteType, +} from './hooks' import { ProposalVoteIndicator } from '../../components/Proposals/ProposalVoteIndicator' import { DeferredValidatorLink } from '../../components/Validators/DeferredValidatorLink' import { CardHeaderWithResponsiveActions } from '../../components/CardHeaderWithResponsiveActions' import { VoteTypeFilter } from '../../components/Proposals/VoteTypeFilter' import { AppErrors } from '../../../types/errors' import { ErrorBoundary } from '../../components/ErrorBoundary' +import Box from '@mui/material/Box' +import { NoMatchingDataMaybeClearFilters, TableSearchBar } from '../../components/Search/TableSearchBar' type ProposalVotesProps = { isLoading: boolean @@ -25,6 +36,8 @@ const ProposalVotes: FC = ({ isLoading, votes, rowsNumber, p 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 +59,7 @@ const ProposalVotes: FC = ({ isLoading, votes, rowsNumber, p address={vote.address} isError={vote.haveValidatorsFailed} validator={vote.validator} + highlightedPart={voterNameFragment} /> ), }, @@ -79,11 +93,19 @@ export const ProposalVotesView: FC = () => { const { isLoading } = useAllVotes(network, proposalId) const displayedVotes = useDisplayedVotes(network, proposalId) + const [hasFilters, clearFilters] = useVoteFilters() + + const onFirstPage = displayedVotes.tablePaginationProps.selectedPage === 1 + const hasData = !!displayedVotes.data?.length - if (!isLoading && displayedVotes.tablePaginationProps.selectedPage > 1 && !displayedVotes.data?.length) { + if (!isLoading && !onFirstPage && !hasData) { throw AppErrors.PageDoesNotExist } + if (!isLoading && onFirstPage && !hasData && hasFilters) { + return + } + return ( { const { t } = useTranslation() const [wantedVoteType, setWantedVoteType] = useWantedVoteType() + const [voterSearchInput, setVoterSearchPattern] = useVoterSearch() + const searchError = useVoterSearchError(t) 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 d60119cc4c..7279f8408f 100644 --- a/src/app/pages/ProposalDetailsPage/hooks.ts +++ b/src/app/pages/ProposalDetailsPage/hooks.ts @@ -11,6 +11,7 @@ import { getFilterForVoteType, getRandomVote } from '../../utils/vote' import { useClientSidePagination } from '../../components/Table/useClientSidePagination' import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE } from '../../config' import { useTypedSearchParam } from '../../hooks/useTypedSearchParam' +import { TFunction } from 'i18next' export type AllVotesData = List & { isLoading: boolean @@ -121,7 +122,42 @@ export const useWantedVoteType = () => deleteParams: ['page'], }) -const useWantedVoteFilter = (): VoteFilter => getFilterForVoteType(useWantedVoteType()[0]) +export const useVoterSearch = () => useTypedSearchParam('voter', '', { deleteParams: ['page'] }) + +export const useVoterSearchPattern = () => { + const [voterSearchInput] = useVoterSearch() + return voterSearchInput.length < 3 ? undefined : voterSearchInput +} + +export const useVoterSearchError = (t: TFunction) => { + const [input] = useVoterSearch() + const pattern = useVoterSearchPattern() + return !!input && !pattern ? t('tableSearch.error.tooShort') : undefined +} + +const useWantedVoteFilter = (): VoteFilter => { + const typeFilter = getFilterForVoteType(useWantedVoteType()[0]) + const voterSearchPattern = useVoterSearchPattern() + + if (!voterSearchPattern) { + return typeFilter + } else { + return (vote: ExtendedVote) => + typeFilter(vote) && !!vote.validator?.media?.name?.toLowerCase().includes(voterSearchPattern) + } +} + +export const useVoteFilters = (): [boolean, () => void] => { + const [wantedType, setWantedType] = useWantedVoteType() + const searchPattern = useVoterSearchPattern() + + return [ + wantedType !== 'any' || !!searchPattern, + () => { + setWantedType('any', { deleteParams: ['voter'] }) + }, + ] +} export const PAGE_SIZE = NUMBER_OF_ITEMS_ON_SEPARATE_PAGE diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 10c63f1e9a..57efd43f94 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -197,6 +197,7 @@ "passed": "Passed", "rejected": "Rejected" }, + "searchForVoters": "Search for voters", "type": { "upgrade": "Upgrade", "parameterUpgrade": "Parameter upgrade",