diff --git a/CHANGELOG.md b/CHANGELOG.md index b03bbb6c1..0b94c478d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#860](https://github.com/alleslabs/celatone-frontend/pull/860) Add voted proposals in voted tab - [#862](https://github.com/alleslabs/celatone-frontend/pull/862) View failed reason - [#853](https://github.com/alleslabs/celatone-frontend/pull/853) Add voted proposals in overview - [#847](https://github.com/alleslabs/celatone-frontend/pull/847) Add amp proposal details page diff --git a/src/lib/pages/proposal-details/components/vote-details/voting-period/validator-votes-table/index.tsx b/src/lib/pages/proposal-details/components/vote-details/voting-period/validator-votes-table/index.tsx index 3590e022a..d66107c61 100644 --- a/src/lib/pages/proposal-details/components/vote-details/voting-period/validator-votes-table/index.tsx +++ b/src/lib/pages/proposal-details/components/vote-details/voting-period/validator-votes-table/index.tsx @@ -20,7 +20,7 @@ import { EmptyState, ErrorFetching } from "lib/components/state"; import { useDebounce } from "lib/hooks"; import type { ProposalAnswerCountsResponse } from "lib/services/proposal"; import { useProposalValidatorVotes } from "lib/services/proposalService"; -import { ProposalValidatorVoteType } from "lib/types"; +import { ProposalVoteType } from "lib/types"; import type { Option, ProposalValidatorVote } from "lib/types"; import { ValidatorVotesTableHeader } from "./ValidatorVotesTableHeader"; @@ -112,8 +112,8 @@ export const ValidatorVotesTable = ({ isProposalResolved, onViewMore, }: ValidatorVotesTableProps) => { - const [answerFilter, setAnswerFilter] = useState( - ProposalValidatorVoteType.ALL + const [answerFilter, setAnswerFilter] = useState( + ProposalVoteType.ALL ); const [search, setSearch] = useState(""); const debouncedSearch = useDebounce(search); @@ -148,7 +148,7 @@ export const ValidatorVotesTable = ({ }, [data, setTotalData]); const isSearching = - debouncedSearch !== "" || answerFilter !== ProposalValidatorVoteType.ALL; + debouncedSearch !== "" || answerFilter !== ProposalVoteType.ALL; const totalValidators = answers?.totalValidators ?? 0; @@ -156,37 +156,37 @@ export const ValidatorVotesTable = ({ () => [ { label: `All votes (${totalValidators})`, - value: ProposalValidatorVoteType.ALL, + value: ProposalVoteType.ALL, disabled: false, }, { label: `Yes (${answers?.yes ?? 0})`, - value: ProposalValidatorVoteType.YES, + value: ProposalVoteType.YES, disabled: false, }, { label: `No (${answers?.no ?? 0})`, - value: ProposalValidatorVoteType.NO, + value: ProposalVoteType.NO, disabled: false, }, { label: `No with veto (${answers?.noWithVeto ?? 0})`, - value: ProposalValidatorVoteType.NO_WITH_VETO, + value: ProposalVoteType.NO_WITH_VETO, disabled: false, }, { label: `Abstain (${answers?.abstain ?? 0})`, - value: ProposalValidatorVoteType.ABSTAIN, + value: ProposalVoteType.ABSTAIN, disabled: false, }, { label: `Weighted (${answers?.weighted ?? 0})`, - value: ProposalValidatorVoteType.WEIGHTED, + value: ProposalVoteType.WEIGHTED, disabled: false, }, { label: `Did not vote (${answers?.didNotVote ?? 0})`, - value: ProposalValidatorVoteType.DID_NOT_VOTE, + value: ProposalVoteType.DID_NOT_VOTE, disabled: false, }, ], @@ -199,27 +199,17 @@ export const ValidatorVotesTable = ({ setSearch(e.target.value); }; - const handleOnAnswerFilterChange = (newAnswer: ProposalValidatorVoteType) => { + const handleOnAnswerFilterChange = (newAnswer: ProposalVoteType) => { setCurrentPage(1); setAnswerFilter(newAnswer); }; - const onPageChange = (nextPage: number) => { - setCurrentPage(nextPage); - }; - - const onPageSizeChange = (e: ChangeEvent) => { - const size = Number(e.target.value); - setPageSize(size); - setCurrentPage(1); - }; - return ( {fullVersion && ( - + formLabel="Filter by Answer" options={answerOptions} onChange={handleOnAnswerFilterChange} @@ -254,8 +244,12 @@ export const ValidatorVotesTable = ({ offset={offset} totalData={data?.total ?? 0} pageSize={pageSize} - onPageChange={onPageChange} - onPageSizeChange={onPageSizeChange} + onPageChange={setCurrentPage} + onPageSizeChange={(e) => { + const size = Number(e.target.value); + setPageSize(size); + setCurrentPage(1); + }} /> )} {onViewMore && !!totalValidators && totalValidators > 10 && ( diff --git a/src/lib/pages/proposal-details/components/vote-details/voting-period/votes-table/index.tsx b/src/lib/pages/proposal-details/components/vote-details/voting-period/votes-table/index.tsx index 4e8c94f3d..ee6ddba4f 100644 --- a/src/lib/pages/proposal-details/components/vote-details/voting-period/votes-table/index.tsx +++ b/src/lib/pages/proposal-details/components/vote-details/voting-period/votes-table/index.tsx @@ -20,6 +20,7 @@ import { EmptyState, ErrorFetching } from "lib/components/state"; import { useDebounce } from "lib/hooks"; import type { ProposalAnswerCountsResponse } from "lib/services/proposal"; import { useProposalVotes } from "lib/services/proposalService"; +import { ProposalVoteType } from "lib/types"; import type { Option, ProposalVote } from "lib/types"; import { ProposalVotesTableHeader } from "./ProposalVotesTableHeader"; @@ -87,16 +88,6 @@ interface ProposalVotesTableProps { onViewMore?: () => void; } -// pass it to api -enum AnswerType { - ALL = "all", - YES = "yes", - NO = "no", - NO_WITH_VETO = "no_with_veto", - ABSTAIN = "abstain", - WEIGHTED = "weighted", -} - const tableHeaderId = "proposalVotesTable"; export const ProposalVotesTable = ({ @@ -105,7 +96,9 @@ export const ProposalVotesTable = ({ fullVersion, onViewMore, }: ProposalVotesTableProps) => { - const [answerFilter, setAnswerFilter] = useState(AnswerType.ALL); + const [answerFilter, setAnswerFilter] = useState( + ProposalVoteType.ALL + ); const [search, setSearch] = useState(""); const debouncedSearch = useDebounce(search); @@ -134,7 +127,8 @@ export const ProposalVotesTable = ({ { onSuccess: ({ total }) => setTotalData(total) } ); - const isSearching = debouncedSearch !== "" || answerFilter !== AnswerType.ALL; + const isSearching = + debouncedSearch !== "" || answerFilter !== ProposalVoteType.ALL; const total = answers?.total ?? 0; @@ -142,32 +136,32 @@ export const ProposalVotesTable = ({ () => [ { label: `All votes (${total})`, - value: AnswerType.ALL, + value: ProposalVoteType.ALL, disabled: false, }, { label: `Yes (${answers?.yes ?? 0})`, - value: AnswerType.YES, + value: ProposalVoteType.YES, disabled: false, }, { label: `No (${answers?.no ?? 0})`, - value: AnswerType.NO, + value: ProposalVoteType.NO, disabled: false, }, { label: `No with veto (${answers?.noWithVeto ?? 0})`, - value: AnswerType.NO_WITH_VETO, + value: ProposalVoteType.NO_WITH_VETO, disabled: false, }, { label: `Abstain (${answers?.abstain ?? 0})`, - value: AnswerType.ABSTAIN, + value: ProposalVoteType.ABSTAIN, disabled: false, }, { label: `Weighted (${answers?.weighted ?? 0})`, - value: AnswerType.WEIGHTED, + value: ProposalVoteType.WEIGHTED, disabled: false, }, ], @@ -180,27 +174,17 @@ export const ProposalVotesTable = ({ setSearch(e.target.value); }; - const handleOnAnswerFilterChange = (newAnswer: AnswerType) => { + const handleOnAnswerFilterChange = (newAnswer: ProposalVoteType) => { setCurrentPage(1); setAnswerFilter(newAnswer); }; - const onPageChange = (nextPage: number) => { - setCurrentPage(nextPage); - }; - - const onPageSizeChange = (e: ChangeEvent) => { - const size = Number(e.target.value); - setPageSize(size); - setCurrentPage(1); - }; - return ( {fullVersion && ( - + formLabel="Filter by Answer" options={answerOptions} onChange={handleOnAnswerFilterChange} @@ -234,8 +218,12 @@ export const ProposalVotesTable = ({ offset={offset} totalData={data?.total ?? 0} pageSize={pageSize} - onPageChange={onPageChange} - onPageSizeChange={onPageSizeChange} + onPageChange={setCurrentPage} + onPageSizeChange={(e) => { + const size = Number(e.target.value); + setPageSize(size); + setCurrentPage(1); + }} /> )} {onViewMore && !!total && total > 10 && ( diff --git a/src/lib/pages/validator-details/components/tables/voted-proposals/index.tsx b/src/lib/pages/validator-details/components/tables/voted-proposals/index.tsx index 38aa9542e..f568c8dc1 100644 --- a/src/lib/pages/validator-details/components/tables/voted-proposals/index.tsx +++ b/src/lib/pages/validator-details/components/tables/voted-proposals/index.tsx @@ -1,11 +1,17 @@ -import { Alert, Flex, Text } from "@chakra-ui/react"; +import { Alert, Flex, Grid, GridItem, Text } from "@chakra-ui/react"; +import type { ChangeEvent } from "react"; +import { useMemo, useState } from "react"; import { useMobile } from "lib/app-provider"; +import { SelectInput } from "lib/components/forms"; import { CustomIcon } from "lib/components/icon"; +import InputWithIcon from "lib/components/InputWithIcon"; import { Pagination } from "lib/components/pagination"; import { usePaginator } from "lib/components/pagination/usePaginator"; import { TableTitle, ViewMore } from "lib/components/table"; +import { useDebounce } from "lib/hooks"; import { useValidatorVotedProposals } from "lib/services/validatorService"; +import { ProposalVoteType } from "lib/types"; import type { ValidatorAddr } from "lib/types"; import { VotedProposalsTableBody } from "./VotedProposalsTableBody"; @@ -23,6 +29,11 @@ export const VotedProposalsTable = ({ }: VotedProposalsTableProps) => { const isMobile = useMobile(); const isMobileOverview = isMobile && !!onViewMore; + const [answerFilter, setAnswerFilter] = useState( + ProposalVoteType.ALL + ); + const [search, setSearch] = useState(""); + const debouncedSearch = useDebounce(search); const { pagesQuantity, @@ -44,11 +55,62 @@ export const VotedProposalsTable = ({ validatorAddress, onViewMore ? 5 : pageSize, offset, - { - onSuccess: ({ total }) => setTotalData(total), - } + answerFilter, + debouncedSearch, + { onSuccess: ({ total }) => setTotalData(total) } + ); + + const answerOptions = useMemo( + () => [ + { + label: `All votes`, + value: ProposalVoteType.ALL, + disabled: false, + }, + { + label: `Yes`, + value: ProposalVoteType.YES, + disabled: false, + }, + { + label: `No`, + value: ProposalVoteType.NO, + disabled: false, + }, + { + label: `No with veto`, + value: ProposalVoteType.NO_WITH_VETO, + disabled: false, + }, + { + label: `Abstain`, + value: ProposalVoteType.ABSTAIN, + disabled: false, + }, + { + label: `Weighted`, + value: ProposalVoteType.WEIGHTED, + disabled: false, + }, + { + label: `Did not vote`, + value: ProposalVoteType.DID_NOT_VOTE, + disabled: false, + }, + ], + [] ); + const handleOnSearchChange = (e: ChangeEvent) => { + setCurrentPage(1); + setSearch(e.target.value); + }; + + const handleOnAnswerFilterChange = (newAnswer: ProposalVoteType) => { + setCurrentPage(1); + setAnswerFilter(newAnswer); + }; + return isMobileOverview ? ( {!onViewMore && ( - - - - Kindly note that the validator may not have voted on the proposal - due to ineligibility, such as being recently added to the network. - - + <> + + + + Kindly note that the validator may not have voted on the proposal + due to ineligibility, such as being recently added to the network. + + + + + + formLabel="Filter by vote answer" + options={answerOptions} + onChange={handleOnAnswerFilterChange} + labelBgColor="background.main" + initialSelected={answerFilter} + popoverBgColor="gray.800" + disableMaxH + /> + + + + + + )} { const endpoint = useBaseApiRoute("proposals"); @@ -375,17 +375,17 @@ export const useProposalValidatorVotes = ( const filteredItemsByAnswer = data.items.filter((vote) => { switch (answer) { - case ProposalValidatorVoteType.YES: + case ProposalVoteType.YES: return vote.yes === 1; - case ProposalValidatorVoteType.NO: + case ProposalVoteType.NO: return vote.no === 1; - case ProposalValidatorVoteType.NO_WITH_VETO: + case ProposalVoteType.NO_WITH_VETO: return vote.noWithVeto === 1; - case ProposalValidatorVoteType.ABSTAIN: + case ProposalVoteType.ABSTAIN: return vote.abstain === 1; - case ProposalValidatorVoteType.WEIGHTED: + case ProposalVoteType.WEIGHTED: return vote.isVoteWeighted; - case ProposalValidatorVoteType.DID_NOT_VOTE: + case ProposalVoteType.DID_NOT_VOTE: return ( vote.yes === 0 && vote.no === 0 && @@ -393,7 +393,7 @@ export const useProposalValidatorVotes = ( vote.abstain === 0 && !vote.isVoteWeighted ); - case ProposalValidatorVoteType.ALL: + case ProposalVoteType.ALL: default: return true; } @@ -413,7 +413,6 @@ export const useProposalValidatorVotes = ( items: filteredItemsBySearch.slice(offset, offset + limit), total: filteredItemsBySearch.length, }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, [data?.items, limit, offset, answer, search]); return { data: filteredData, ...rest }; @@ -423,8 +422,8 @@ export const useProposalVotes = ( id: number, limit: number, offset: number, - answer?: string, - search?: string, + answer: ProposalVoteType, + search: string, options: Pick, "onSuccess"> = {} ): UseQueryResult => { const endpoint = useBaseApiRoute("proposals"); diff --git a/src/lib/services/validatorService.ts b/src/lib/services/validatorService.ts index 31e4794f2..fc4b324c6 100644 --- a/src/lib/services/validatorService.ts +++ b/src/lib/services/validatorService.ts @@ -10,7 +10,13 @@ import { useBaseApiRoute, useCurrentChain, } from "lib/app-provider"; -import type { Nullable, Option, Validator, ValidatorAddr } from "lib/types"; +import type { + Nullable, + Option, + ProposalVoteType, + Validator, + ValidatorAddr, +} from "lib/types"; import type { BlocksResponse } from "./block"; import type { @@ -235,12 +241,12 @@ export const useValidatorVotedProposals = ( validatorAddress: ValidatorAddr, limit: number, offset: number, + answer: ProposalVoteType, + search: string, options: Pick< UseQueryOptions, "onSuccess" - > = {}, - answer?: string, - search?: string + > = {} ) => { const endpoint = useBaseApiRoute("validators"); diff --git a/src/lib/types/proposal.ts b/src/lib/types/proposal.ts index ff8376e37..556e57319 100644 --- a/src/lib/types/proposal.ts +++ b/src/lib/types/proposal.ts @@ -155,12 +155,12 @@ export interface ProposalValidatorVote extends ProposalVote { rank: number; } -export enum ProposalValidatorVoteType { +export enum ProposalVoteType { ALL = "all", YES = "yes", NO = "no", NO_WITH_VETO = "no_with_veto", ABSTAIN = "abstain", WEIGHTED = "weighted", - DID_NOT_VOTE = "did_not_vote", + DID_NOT_VOTE = "did_not_vote", // only for validator votes }