From 803c25aae8ab316f121af041dde26330212dd7db Mon Sep 17 00:00:00 2001 From: Joanna Dyczka Date: Wed, 27 Mar 2024 00:53:59 +0100 Subject: [PATCH] [#221] add search, filter and sort to drep list --- .../src/components/atoms/StatusPill.tsx | 19 +++---- .../src/components/organisms/DRepCard.tsx | 7 +++ .../src/consts/dRepDirectory/filters.ts | 6 ++ .../src/consts/dRepDirectory/index.ts | 2 + .../src/consts/dRepDirectory/sorting.ts | 14 +++++ govtool/frontend/src/consts/index.ts | 1 + .../src/hooks/queries/useGetDRepListQuery.ts | 22 ++++++-- govtool/frontend/src/i18n/locales/en.ts | 3 + govtool/frontend/src/models/api.ts | 8 ++- govtool/frontend/src/pages/DRepDetails.tsx | 2 +- .../src/pages/DRepDirectoryContent.tsx | 56 +++++++++++++++++-- .../src/services/requests/getDRepList.ts | 10 +++- .../src/stories/StatusPill.stories.ts | 7 ++- 13 files changed, 129 insertions(+), 28 deletions(-) create mode 100644 govtool/frontend/src/consts/dRepDirectory/filters.ts create mode 100644 govtool/frontend/src/consts/dRepDirectory/index.ts create mode 100644 govtool/frontend/src/consts/dRepDirectory/sorting.ts diff --git a/govtool/frontend/src/components/atoms/StatusPill.tsx b/govtool/frontend/src/components/atoms/StatusPill.tsx index b09fcebf9..e8fbf0377 100644 --- a/govtool/frontend/src/components/atoms/StatusPill.tsx +++ b/govtool/frontend/src/components/atoms/StatusPill.tsx @@ -1,10 +1,9 @@ import { Chip, ChipProps, styled } from "@mui/material"; import { cyan, errorRed, successGreen } from "@/consts"; - -type Status = 'Active' | 'Inactive' | 'Retired'; +import { DRepStatus } from "@/models"; interface StatusPillProps { - status: Status; + status: DRepStatus; label?: string; size?: 'small' | 'medium'; sx?: ChipProps['sx']; @@ -25,18 +24,18 @@ export const StatusPill = ({ ); const bgColor = { - Active: successGreen.c200, - Inactive: cyan.c100, - Retired: errorRed.c100, + [DRepStatus.Active]: successGreen.c200, + [DRepStatus.Inactive]: cyan.c100, + [DRepStatus.Retired]: errorRed.c100, }; const textColor = { - Active: successGreen.c700, - Inactive: cyan.c500, - Retired: errorRed.c500, + [DRepStatus.Active]: successGreen.c700, + [DRepStatus.Inactive]: cyan.c500, + [DRepStatus.Retired]: errorRed.c500, }; -const StyledChip = styled(Chip)<{ status: Status }>(({ theme, status }) => ({ +const StyledChip = styled(Chip)<{ status: DRepStatus }>(({ theme, status }) => ({ backgroundColor: bgColor[status], color: textColor[status], border: `2px solid ${theme.palette.neutralWhite}`, diff --git a/govtool/frontend/src/components/organisms/DRepCard.tsx b/govtool/frontend/src/components/organisms/DRepCard.tsx index 25faa77c1..786969754 100644 --- a/govtool/frontend/src/components/organisms/DRepCard.tsx +++ b/govtool/frontend/src/components/organisms/DRepCard.tsx @@ -7,6 +7,7 @@ import { Card } from "@molecules"; import { correctAdaFormat } from "@/utils"; import { ICONS, PATHS } from "@/consts"; import { DRepData } from "@/models"; +import { useSnackbar } from "@/context"; type DRepCardProps = { dRep: DRepData; @@ -30,6 +31,7 @@ export const DRepCard = ({ }: DRepCardProps) => { const navigate = useNavigate(); const { t } = useTranslation(); + const { addSuccessAlert } = useSnackbar(); return ( {type} { + navigator.clipboard.writeText(view); + addSuccessAlert(t("alerts.copiedToClipboard")); + e.stopPropagation(); + }} sx={{ gap: 1, maxWidth: "100%", diff --git a/govtool/frontend/src/consts/dRepDirectory/filters.ts b/govtool/frontend/src/consts/dRepDirectory/filters.ts new file mode 100644 index 000000000..f6a089f8e --- /dev/null +++ b/govtool/frontend/src/consts/dRepDirectory/filters.ts @@ -0,0 +1,6 @@ +import { DRepStatus } from "@/models"; + +export const DREP_DIRECTORY_FILTERS = Object.values(DRepStatus).map((status) => ({ + key: status, + label: status, +})); diff --git a/govtool/frontend/src/consts/dRepDirectory/index.ts b/govtool/frontend/src/consts/dRepDirectory/index.ts new file mode 100644 index 000000000..93e21800d --- /dev/null +++ b/govtool/frontend/src/consts/dRepDirectory/index.ts @@ -0,0 +1,2 @@ +export * from "./filters"; +export * from "./sorting"; diff --git a/govtool/frontend/src/consts/dRepDirectory/sorting.ts b/govtool/frontend/src/consts/dRepDirectory/sorting.ts new file mode 100644 index 000000000..ab5b531bb --- /dev/null +++ b/govtool/frontend/src/consts/dRepDirectory/sorting.ts @@ -0,0 +1,14 @@ +export const DREP_DIRECTORY_SORTING = [ + { + key: "NewestRegistered", + label: "Newest registered", + }, + { + key: "VotingPower", + label: "Voting power", + }, + { + key: "Status", + label: "Status", + }, +]; diff --git a/govtool/frontend/src/consts/index.ts b/govtool/frontend/src/consts/index.ts index 22a9c3bbc..87b876b2e 100644 --- a/govtool/frontend/src/consts/index.ts +++ b/govtool/frontend/src/consts/index.ts @@ -1,6 +1,7 @@ export * from "./externalDataModalConfig"; export * from "./colors"; export * from "./dRepActions"; +export * from "./dRepDirectory"; export * from "./governanceAction"; export * from "./icons"; export * from "./images"; diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts index b00932f1d..2d5882fe8 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts @@ -2,24 +2,34 @@ import { UseQueryOptions, useQuery } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; -import { getDRepList } from "@services"; +import { GetDRepListParams, getDRepList } from "@services"; import { DRepData } from "@/models"; -export const useGetDRepListQuery = (dRepView?: string, options?: UseQueryOptions) => { +export const useGetDRepListQuery = ( + params?: GetDRepListParams, + options?: UseQueryOptions +) => { + const { drepView, sort, status } = params || {}; const { pendingTransaction } = useCardano(); - const { data, isLoading } = useQuery({ + const { data, isLoading, isPreviousData } = useQuery({ queryKey: [ QUERY_KEYS.useGetDRepListKey, (pendingTransaction.registerAsSoleVoter || pendingTransaction.registerAsDrep || pendingTransaction.retireAsSoleVoter || pendingTransaction.retireAsDrep)?.transactionHash, - dRepView + drepView, + sort, + status, ], - queryFn: () => getDRepList(dRepView), + queryFn: () => getDRepList({ + ...(drepView && { drepView }), + ...(sort && { sort }), + ...(status && { status }), + }), ...options }); - return { data, isLoading }; + return { data, isLoading, isPreviousData }; }; diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts index c48cdae90..0971344e4 100644 --- a/govtool/frontend/src/i18n/locales/en.ts +++ b/govtool/frontend/src/i18n/locales/en.ts @@ -240,6 +240,7 @@ export const en = { automatedVotingOptions: "Automated Voting Options", editBtn: "Edit DRep data", delegationOptions: "Delegation Options", + filterTitle: "DRep Status", meAsDRep: "This DRep ID is connected to your wallet", myDelegation: "You have delegated ₳ {{ada}} to:", myDRep: "This is your DRep", @@ -247,6 +248,8 @@ export const en = { noConfidenceDescription: "Select this to signal no confidence in the current constitutional committee by voting NO on every proposal and voting YES to no confidence proposals", noConfidenceTitle: "Signal No Confidence on Every Vote", + noResultsForTheSearchTitle: "No DReps found", + noResultsForTheSearchDescription: "Please try a different search", title: "DRep Directory", votingPower: "Voting Power", }, diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index 6cc2df604..141cb1472 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -6,6 +6,12 @@ export interface VoterInfo { deposit: number; } +export enum DRepStatus { + Active = "Active", + Inactive = "Inactive", + Retired = "Retired", +} + export interface DRepData { drepId: string; view: string; @@ -13,7 +19,7 @@ export interface DRepData { metadataHash: string; deposit: number; votingPower: number; - status: 'Active' | 'Inactive' | 'Retired'; + status: DRepStatus; type: 'DRep' | 'SoleVoter'; } diff --git a/govtool/frontend/src/pages/DRepDetails.tsx b/govtool/frontend/src/pages/DRepDetails.tsx index 1ab2d2d1a..9b8284cdc 100644 --- a/govtool/frontend/src/pages/DRepDetails.tsx +++ b/govtool/frontend/src/pages/DRepDetails.tsx @@ -47,7 +47,7 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => { const { delegate, isDelegating } = useDelegateTodRep(); const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey); - const { data, isLoading } = useGetDRepListQuery(dRepParam); + const { data, isLoading } = useGetDRepListQuery({ drepView: dRepParam }); const dRep = data?.[0]; if (!dRep && isLoading) return ; diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx index 250457df3..08c14283d 100644 --- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -3,14 +3,17 @@ import { FC } from "react"; import { AutomatedVotingOptions, DRepCard } from "@organisms"; import { Typography } from "@atoms"; import { Trans, useTranslation } from "react-i18next"; +import { Card, DataActionsBar } from "@molecules"; import { useCardano } from "@/context"; import { + useDataActionsBar, useDelegateTodRep, useGetAdaHolderCurrentDelegationQuery, useGetAdaHolderVotingPowerQuery, useGetDRepListQuery } from "@/hooks"; import { correctAdaFormat, formHexToBech32, isSameDRep } from "@/utils"; +import { DREP_DIRECTORY_FILTERS, DREP_DIRECTORY_SORTING } from "@/consts"; interface DRepDirectoryContentProps { isConnected?: boolean; @@ -25,6 +28,8 @@ export const DRepDirectoryContent: FC = ({ stakeKey, } = useCardano(); const { t } = useTranslation(); + const { debouncedSearchText, ...dataActionsBarProps } = useDataActionsBar(); + const { chosenFilters, chosenSorting } = dataActionsBarProps; const { delegate } = useDelegateTodRep(); @@ -33,13 +38,21 @@ export const DRepDirectoryContent: FC = ({ const inProgressDelegation = pendingTransaction.delegate?.resourceId; const { data: myDRepList } = useGetDRepListQuery( - currentDelegation?.startsWith('drep') ? currentDelegation : formHexToBech32(currentDelegation), + { drepView: currentDelegation?.startsWith('drep') + ? currentDelegation + : formHexToBech32(currentDelegation) }, { enabled: !!inProgressDelegation || !!currentDelegation } ); const myDrep = myDRepList?.[0]; - const { data: dRepList } = useGetDRepListQuery(); + const { data: dRepList, isPreviousData } = useGetDRepListQuery({ + drepView: debouncedSearchText, + sort: chosenSorting, + status: chosenFilters, + }, { + keepPreviousData: true, + }); - if ((stakeKey && votingPower === undefined) || !dRepList) { + if (stakeKey && votingPower === undefined) { return ; } @@ -47,6 +60,7 @@ export const DRepDirectoryContent: FC = ({ return ( + {/* My delegation */} {myDrep && (
@@ -60,6 +74,7 @@ export const DRepDirectoryContent: FC = ({
)} + {/* Automated voting options */} {isConnected && (
@@ -83,11 +98,42 @@ export const DRepDirectoryContent: FC = ({
)} + {/* DRep list */}
- + {t('dRepDirectory.listTitle')} - + + + {dRepList?.length === 0 && ( + + {t('dRepDirectory.noResultsForTheSearchTitle')} + {t('dRepDirectory.noResultsForTheSearchDescription')} + + )} {dRepList?.map((dRep) => (isSameDRep(dRep, myDrep?.view) ? null : ( diff --git a/govtool/frontend/src/services/requests/getDRepList.ts b/govtool/frontend/src/services/requests/getDRepList.ts index 9452638af..c4c0af028 100644 --- a/govtool/frontend/src/services/requests/getDRepList.ts +++ b/govtool/frontend/src/services/requests/getDRepList.ts @@ -1,7 +1,13 @@ import type { DRepData } from "@models"; import { API } from "../API"; -export const getDRepList = async (drepView?: string) => { - const response = await API.get("/drep/list", drepView ? { params: { drepView } } : undefined); +export type GetDRepListParams = { + drepView?: string; + sort?: string; + status?: string[]; +}; + +export const getDRepList = async (params: GetDRepListParams) => { + const response = await API.get("/drep/list", { params }); return response.data; }; diff --git a/govtool/frontend/src/stories/StatusPill.stories.ts b/govtool/frontend/src/stories/StatusPill.stories.ts index 0ca27c631..1a8467cad 100644 --- a/govtool/frontend/src/stories/StatusPill.stories.ts +++ b/govtool/frontend/src/stories/StatusPill.stories.ts @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { StatusPill } from "@atoms"; +import { DRepStatus } from "@/models"; const meta = { title: "Example/StatusPill", @@ -16,18 +17,18 @@ type Story = StoryObj; export const StatusPillActive: Story = { args: { - status: "Active", + status: DRepStatus.Active, }, }; export const StatusPillInactive: Story = { args: { - status: "Inactive", + status: DRepStatus.Inactive, }, }; export const StatusPillRetired: Story = { args: { - status: "Retired", + status: DRepStatus.Retired, }, };