diff --git a/src/hooks/governance/useGovernanceCache.ts b/src/hooks/governance/useGovernanceCache.ts index 51f5f565b1..8ca3d34e4c 100644 --- a/src/hooks/governance/useGovernanceCache.ts +++ b/src/hooks/governance/useGovernanceCache.ts @@ -4,7 +4,7 @@ * proposals / votes / payloads; user & protocol state is read on-chain in * separate hooks. */ -import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; +import { keepPreviousData, useInfiniteQuery, useQuery } from '@tanstack/react-query'; import { Contract } from 'ethers'; import { adaptCacheProposalToDetail, @@ -96,12 +96,19 @@ const useEnsNames = (proposalId: number, addresses: string[]) => { // Proposals — list / search / detail // ============================================ -/** Paginated proposals list, adapted to the canonical list item type. */ -export const useGovernanceProposals = () => +/** + * Paginated proposals list, adapted to the canonical list item type. `stateFilter` is a cache + * state (`executed`, `active`, …) applied server-side, so every page holds matching proposals. + */ +export const useGovernanceProposals = (stateFilter?: string) => useInfiniteQuery({ - queryKey: queryKeysFactory.governanceCacheProposals(), + queryKey: queryKeysFactory.governanceCacheProposals(stateFilter), queryFn: async ({ pageParam = 0 }) => { - const proposals = await getProposals(PROPOSALS_PAGE_SIZE, pageParam * PROPOSALS_PAGE_SIZE); + const proposals = await getProposals( + PROPOSALS_PAGE_SIZE, + pageParam * PROPOSALS_PAGE_SIZE, + stateFilter + ); return { proposals: proposals.map(adaptCacheProposalToListItem) }; }, initialPageParam: 0, @@ -121,6 +128,7 @@ export const useGovernanceProposalsSearch = (query: string) => { return results.map(adaptCacheProposalToListItem); }, enabled: query.trim() !== '', + placeholderData: keepPreviousData, ...CACHE_QUERY_OPTIONS, }); return { results: (data ?? []) as ProposalListItem[], loading: isFetching }; diff --git a/src/modules/governance/ProposalsV3List.tsx b/src/modules/governance/ProposalsV3List.tsx index 6eaaf31b7d..106657d25c 100644 --- a/src/modules/governance/ProposalsV3List.tsx +++ b/src/modules/governance/ProposalsV3List.tsx @@ -13,8 +13,9 @@ import { GOVERNANCE_PAGE } from 'src/utils/events'; import { figVars } from 'src/utils/figmaColors'; import { motion } from 'src/utils/motion'; +import { badgeToCacheState } from './adapters'; import { ProposalListHeader } from './ProposalListHeader'; -import { StateBadge, stringToState } from './StateBadge'; +import { ProposalBadgeState, StateBadge } from './StateBadge'; import { ProposalListItem } from './types'; import { VoteBar } from './VoteBar'; @@ -162,7 +163,8 @@ const ProposalListSkeleton = () => { export const ProposalsV3List = () => { const [proposalFilter, setProposalFilter] = useState('all'); - const filterState = stringToState(proposalFilter); + // The dropdown's values are the badge labels; the backend filters on cache states. + const filterBadge = proposalFilter === 'all' ? undefined : (proposalFilter as ProposalBadgeState); const [searchTerm, setSearchTerm] = useState(''); @@ -174,7 +176,7 @@ export const ProposalsV3List = () => { isFetching: loadingProposals, fetchNextPage, hasNextPage, - } = useGovernanceProposals(); + } = useGovernanceProposals(filterBadge && badgeToCacheState(filterBadge)); let listItems: ProposalListItem[] = []; @@ -184,10 +186,6 @@ export const ProposalsV3List = () => { data.pages.forEach((page) => listItems.push(...page.proposals)); } - if (proposalFilter !== 'all') { - listItems = listItems.filter((proposal) => proposal.badgeState === filterState); - } - return ( cache state mapping (the inverse, for the server-side state filter) +// ============================================ + +export function badgeToCacheState(badge: ProposalBadgeState): string { + switch (badge) { + case ProposalBadgeState.Created: + return 'created'; + case ProposalBadgeState.OpenForVoting: + return 'active'; + case ProposalBadgeState.Passed: + return 'queued'; + case ProposalBadgeState.Executed: + return 'executed'; + case ProposalBadgeState.Failed: + return 'failed'; + case ProposalBadgeState.Cancelled: + return 'cancelled'; + case ProposalBadgeState.Expired: + return 'expired'; + case ProposalBadgeState.PartiallyExecuted: + return 'partially_executed'; + } +} + // ============================================ // Cache state -> badge mapping (replaces mapStateToBadge duplicated in 3 cache files) // ============================================