From 1ad9f99aa22123cc48e01720bf696224aea32102 Mon Sep 17 00:00:00 2001 From: Bruno Date: Thu, 29 Jan 2026 19:55:15 -0300 Subject: [PATCH 01/11] feat: add new changes to holder and delegates sections --- .../components/HoldersAndDelegatesDrawer.tsx | 4 +- .../delegate/Delegates.tsx | 94 +++++- .../DelegateDelegationHistoryTable.tsx | 17 +- .../DelegateDelegationsHistory.tsx | 40 ++- .../votes/DelegateProposalsActivity.tsx | 33 +-- .../delegate/drawer/votes/ProposalsTable.tsx | 59 ++-- .../drawer/voting-power/VotingPowerTable.tsx | 280 ++++++++++-------- .../hooks/useBalanceHistory.ts | 10 + .../hooks/useDelegateDelegationHistory.ts | 8 + .../hooks/useDelegates.ts | 2 + .../drawer/balance-history/BalanceHistory.tsx | 38 ++- .../balance-history/BalanceHistoryTable.tsx | 6 + .../top-interactions/TopInteractions.tsx | 44 ++- .../hooks/useAccountInteractionsData.ts | 1 - .../holders-and-delegates/utils/index.ts | 1 + .../utils/proposalsTableUtils.tsx | 49 ++- .../transactions/utils/transactionsAdapter.ts | 2 + .../documents/balanceHistory.graphql | 4 + .../delegatorsVotingPower.graphql | 2 +- .../delegationsAndHistoricalVoting.graphql | 2 + .../historicalVotingPowerByAccountId.graphql | 4 + .../drawer/accountInteractions.graphql | 2 - packages/graphql-client/generated.ts | 27 +- packages/graphql-client/types.ts | 9 +- 24 files changed, 509 insertions(+), 229 deletions(-) diff --git a/apps/dashboard/features/holders-and-delegates/components/HoldersAndDelegatesDrawer.tsx b/apps/dashboard/features/holders-and-delegates/components/HoldersAndDelegatesDrawer.tsx index b5d655e16..9f73a78a0 100644 --- a/apps/dashboard/features/holders-and-delegates/components/HoldersAndDelegatesDrawer.tsx +++ b/apps/dashboard/features/holders-and-delegates/components/HoldersAndDelegatesDrawer.tsx @@ -47,12 +47,12 @@ export const HoldersAndDelegatesDrawer = ({ }, { id: "votingPower", - label: "Voting Power", + label: "Vote Composition", content: , }, { id: "delegationHistory", - label: "Delegation History", + label: "Voting Power History", content: ( ), diff --git a/apps/dashboard/features/holders-and-delegates/delegate/Delegates.tsx b/apps/dashboard/features/holders-and-delegates/delegate/Delegates.tsx index f1859eca9..090bbe161 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/Delegates.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/Delegates.tsx @@ -7,15 +7,16 @@ import { useDelegates, HoldersAndDelegatesDrawer, } from "@/features/holders-and-delegates"; +import { getAvgVoteTimingData } from "@/features/holders-and-delegates/utils"; import { TimeInterval } from "@/shared/types/enums"; -import { SkeletonRow, Button } from "@/shared/components"; +import { SkeletonRow, Button, SimpleProgressBar } from "@/shared/components"; import { EnsAvatar } from "@/shared/components/design-system/avatars/ens-avatar/EnsAvatar"; import { ArrowUpDown, ArrowState } from "@/shared/components/icons"; -import { formatNumberUserReadable } from "@/shared/utils"; +import { cn, formatNumberUserReadable } from "@/shared/utils"; import { Plus } from "lucide-react"; import { ProgressCircle } from "@/features/holders-and-delegates/components/ProgressCircle"; import { DaoIdEnum } from "@/shared/types/daos"; -import { useScreenSize } from "@/shared/hooks"; +import { useScreenSize, useDaoData } from "@/shared/hooks"; import { Address, formatUnits } from "viem"; import { Table } from "@/shared/components/design-system/table/Table"; import { Percentage } from "@/shared/components/design-system/table/Percentage"; @@ -27,13 +28,20 @@ import { QueryInput_VotingPowers_OrderBy, QueryInput_VotingPowers_OrderDirection, } from "@anticapture/graphql-client"; +import { Tooltip } from "@/shared/components/design-system/tooltips/Tooltip"; +import { BadgeStatus } from "@/shared/components/design-system/badges/BadgeStatus"; interface DelegateTableData { address: string; votingPower: string; - variation?: { percentageChange: number; absoluteChange: number }; + variation?: { + percentageChange: number; + absoluteChange: number; + isNewDelegate: boolean; + }; activity?: string | null; activityPercentage?: number | null; delegators: number; + avgVoteTiming?: { text: string; percentage: number } | null; } interface DelegatesProps { @@ -87,6 +95,13 @@ export const Delegates = ({ ), ); const { decimals } = daoConfig[daoId]; + const { data: daoData } = useDaoData(daoId); + + const votingPeriodSeconds = useMemo(() => { + if (!daoData?.votingPeriod) return 0; + const blockTime = daoConfig[daoId].daoOverview.chain.blockTime; + return (Number(daoData.votingPeriod) * blockTime) / 1000; + }, [daoData?.votingPeriod, daoId]); const handleAddressFilterApply = (address: string | undefined) => { setCurrentAddressFilter(address || ""); @@ -150,6 +165,16 @@ export const Delegates = ({ 100 : null; + const isNewDelegate = + delegate.previousVotingPower === "0" && + BigInt(delegate.votingPower || "0") > BigInt(0); + + const avgVoteTiming = getAvgVoteTimingData( + delegate.proposalsActivity?.avgTimeBeforeEnd, + votingPeriodSeconds, + delegate.proposalsActivity?.votedProposals, + ); + return { address: delegate.accountId, votingPower: formatNumberUserReadable(votingPowerFormatted), @@ -161,13 +186,15 @@ export const Delegates = ({ absoluteChange: Number( formatUnits(BigInt(delegate.absoluteChange), decimals), ), + isNewDelegate, }, activity, activityPercentage, delegators: delegate.delegationsCount, + avgVoteTiming, }; }); - }, [data, decimals]); + }, [data, decimals, votingPeriodSeconds]); const delegateColumns: ColumnDef[] = [ { @@ -281,7 +308,7 @@ export const Delegates = ({ ), meta: { - columnClassName: "w-72", + columnClassName: "w-40", }, }, { @@ -293,6 +320,7 @@ export const Delegates = ({ | { percentageChange: number; absoluteChange: number; + isNewDelegate: boolean; } | undefined; @@ -311,7 +339,11 @@ export const Delegates = ({
{(variation?.percentageChange || 0) < 0 ? "-" : ""} {formatNumberUserReadable(Math.abs(variation?.absoluteChange || 0))} - + {variation?.isNewDelegate ? ( + New + ) : ( + + )}
); }, @@ -351,6 +383,54 @@ export const Delegates = ({ ), }, + { + accessorKey: "avgVoteTiming", + cell: ({ row }) => { + const avgVoteTiming = row.getValue("avgVoteTiming") as { + text: string; + percentage: number; + } | null; + + if (loading) { + return ( +
+ +
+ ); + } + + if (!avgVoteTiming) { + return
-
; + } + + return ( +
+
+ {avgVoteTiming.text} +
+ {avgVoteTiming.text !== "-" && ( + + )} +
+ ); + }, + header: () => ( +
+ +

+ Avg Vote Timing +

+
+
+ ), + meta: { + columnClassName: "w-40", + }, + }, { accessorKey: "delegators", cell: ({ row }) => { diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx index 9c1c1d113..2d7fcaa4e 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx @@ -32,11 +32,15 @@ import { interface DelegateDelegationHistoryTableProps { accountId: string; daoId: DaoIdEnum; + fromTimestamp?: number; + toTimestamp?: number; } export const DelegateDelegationHistoryTable = ({ accountId, daoId, + fromTimestamp, + toTimestamp, }: DelegateDelegationHistoryTableProps) => { const { decimals } = daoConfig[daoId]; @@ -70,6 +74,8 @@ export const DelegateDelegationHistoryTable = ({ orderBy: sortBy, orderDirection: sortDirection, filterVariables, + fromTimestamp, + toTimestamp, }); const isInitialLoading = @@ -85,7 +91,7 @@ export const DelegateDelegationHistoryTable = ({ } }; - // Format timestamp to relative time + // Format timestamp to relative time in days const formatRelativeTime = (timestamp: string) => { const date = new Date(parseInt(timestamp) * 1000); const now = new Date(); @@ -94,13 +100,10 @@ export const DelegateDelegationHistoryTable = ({ const diffInMinutes = Math.floor(diffInSeconds / 60); const diffInHours = Math.floor(diffInMinutes / 60); const diffInDays = Math.floor(diffInHours / 24); - const diffInWeeks = Math.floor(diffInDays / 7); - const diffInMonths = Math.floor(diffInDays / 30); + const diffInYears = Math.floor(diffInDays / 365); - if (diffInMonths > 0) { - return `${diffInMonths} month${diffInMonths > 1 ? "s" : ""} ago`; - } else if (diffInWeeks > 0) { - return `${diffInWeeks} week${diffInWeeks > 1 ? "s" : ""} ago`; + if (diffInYears > 0) { + return `${diffInYears} year${diffInYears > 1 ? "s" : ""} ago`; } else if (diffInDays > 0) { return `${diffInDays} day${diffInDays > 1 ? "s" : ""} ago`; } else if (diffInHours > 0) { diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory.tsx index b1b4d796f..128d6dba8 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory.tsx @@ -1,6 +1,9 @@ import { DaoIdEnum } from "@/shared/types/daos"; import { DelegateDelegationHistoryTable } from "@/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable"; import { VotingPowerVariationGraph } from "@/features/holders-and-delegates/delegate/drawer/delegation-history/VotingPowerVariationGraph"; +import { parseAsStringEnum, useQueryState } from "nuqs"; +import { useMemo } from "react"; +import { SECONDS_PER_DAY } from "@/shared/constants/time-related"; interface DelegateDelegationsHistoryProps { accountId: string; @@ -11,16 +14,49 @@ export const DelegateDelegationsHistory = ({ accountId, daoId, }: DelegateDelegationsHistoryProps) => { + const [selectedPeriod] = useQueryState( + "selectedPeriod", + parseAsStringEnum(["30d", "90d", "all"]).withDefault("all"), + ); + + const { fromTimestamp, toTimestamp } = useMemo(() => { + const nowInSeconds = Date.now() / 1000; + + if (selectedPeriod === "all") { + return { fromTimestamp: undefined, toTimestamp: undefined }; + } + + let daysInSeconds: number; + switch (selectedPeriod) { + case "90d": + daysInSeconds = 90 * SECONDS_PER_DAY; + break; + default: + daysInSeconds = 30 * SECONDS_PER_DAY; + break; + } + + return { + fromTimestamp: Math.floor(nowInSeconds - daysInSeconds), + toTimestamp: Math.floor(nowInSeconds), + }; + }, [selectedPeriod]); + return (
{/* Graph Section */} -
+
{/* Table Section */}
- +
); diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/DelegateProposalsActivity.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/DelegateProposalsActivity.tsx index 155462dc1..ff5779d92 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/DelegateProposalsActivity.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/DelegateProposalsActivity.tsx @@ -20,6 +20,22 @@ interface DelegateProposalsActivityProps { daoId: DaoIdEnum; } +export const formatAvgTime = ( + avgTimeBeforeEndSeconds: number, + votedProposals: number, +): string => { + const avgTimeBeforeEndDays = avgTimeBeforeEndSeconds / SECONDS_PER_DAY; + + if (!votedProposals) { + return "-"; + } + + if (avgTimeBeforeEndDays < 1) { + return "< 1d before the end"; + } + return `${Math.round(avgTimeBeforeEndDays)}d before the end`; +}; + export const DelegateProposalsActivity = ({ address, daoId, @@ -65,23 +81,6 @@ export const DelegateProposalsActivity = ({ itemsPerPage, }); - // Helper function to format average time (convert seconds to days) - const formatAvgTime = ( - avgTimeBeforeEndSeconds: number, - votedProposals: number, - ): string => { - const avgTimeBeforeEndDays = avgTimeBeforeEndSeconds / SECONDS_PER_DAY; - - if (!votedProposals) { - return "-"; - } - - if (avgTimeBeforeEndDays < 1) { - return "< 1d before the end"; - } - return `${Math.round(avgTimeBeforeEndDays)}d before the end`; - }; - // Prepare values - undefined when loading/error, actual values when data is available const votedProposalsValue = loading || error || !data diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/ProposalsTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/ProposalsTable.tsx index 6c3c8e71e..ed1ac8528 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/ProposalsTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/ProposalsTable.tsx @@ -29,6 +29,7 @@ import { } from "@/features/holders-and-delegates/utils/proposalsTableUtils"; import { Table } from "@/shared/components/design-system/table/Table"; import daoConfig from "@/shared/dao-config"; +import { Tooltip } from "@/shared/components/design-system/tooltips/Tooltip"; interface ProposalTableData { proposalId: string; @@ -328,32 +329,38 @@ export const ProposalsTable = ({ ); }, header: () => ( - +
+ + + +
), }, { diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/VotingPowerTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/VotingPowerTable.tsx index 0c16d6197..8c5ee9b92 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/VotingPowerTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/VotingPowerTable.tsx @@ -26,7 +26,7 @@ export const VotingPowerTable = ({ const [isMounted, setIsMounted] = useState(false); const [sortBy, setSortBy] = useQueryState( "orderBy", - parseAsStringEnum(["balance"]).withDefault("balance"), + parseAsStringEnum(["balance", "timestamp"]).withDefault("balance"), ); const [sortOrder, setSortOrder] = useQueryState( "orderDirection", @@ -40,7 +40,7 @@ export const VotingPowerTable = ({ useVotingPower({ daoId: daoId as DaoIdEnum, address: address, - orderBy: sortBy, + orderBy: sortBy as "balance" | "timestamp", orderDirection: sortOrder as QueryInput_AccountBalances_OrderDirection, }); @@ -61,152 +61,174 @@ export const VotingPowerTable = ({ amount: number; date: string; }>[] = [ - { - accessorKey: "address", - header: () => ( -
- Address -
- ), - cell: ({ row }) => { - if (!isMounted || loading) { - return ( -
- - -
- ); - } - const addressValue: string = row.getValue("address"); + { + accessorKey: "address", + header: () => ( +
+ Address +
+ ), + cell: ({ row }) => { + if (!isMounted || loading) { return ( -
- + + -
- -
); - }, - meta: { - columnClassName: "w-72", - }, - }, - { - accessorKey: "amount", - header: ({ column }) => { - const handleSortToggle = () => { - const newSortOrder = sortOrder === "desc" ? "asc" : "desc"; - setSortBy("balance"); - setSortOrder(newSortOrder); - column.toggleSorting(newSortOrder === "desc"); - }; - return ( -
- Amount ({daoId}) - + } + const addressValue: string = row.getValue("address"); + return ( +
+ +
+
- ); - }, - cell: ({ row }) => { - if (!isMounted || loading) { - return ( -
- -
- ); - } - const amount: number = row.getValue("amount"); +
+ ); + }, + meta: { + columnClassName: "w-72", + }, + }, + { + accessorKey: "amount", + header: ({ column }) => { + const handleSortToggle = () => { + const newSortOrder = sortOrder === "desc" ? "asc" : "desc"; + setSortBy("balance"); + setSortOrder(newSortOrder); + column.toggleSorting(newSortOrder === "desc"); + }; + return ( +
+ Amount ({daoId}) + +
+ ); + }, + cell: ({ row }) => { + if (!isMounted || loading) { return (
- {formatNumberUserReadable( - token === "ERC20" - ? Number(BigInt(amount)) / Number(BigInt(10 ** 18)) || 0 - : Number(amount) || 0, - )} +
); - }, - meta: { - columnClassName: "w-72", - }, + } + const amount: number = row.getValue("amount"); + return ( +
+ {formatNumberUserReadable( + token === "ERC20" + ? Number(BigInt(amount)) / Number(BigInt(10 ** 18)) || 0 + : Number(amount) || 0, + )} +
+ ); + }, + meta: { + columnClassName: "w-72", }, - { - accessorKey: "date", - header: () => { + }, + { + accessorKey: "date", + header: () => { + const handleSortToggle = () => { + const newSortOrder = sortOrder === "desc" ? "asc" : "desc"; + setSortBy("timestamp"); + setSortOrder(newSortOrder); + }; + return ( +
+ Date + +
+ ); + }, + cell: ({ row }) => { + const date: string = row.getValue("date"); + + if (!isMounted || loading) { return ( -
- Date +
+
); - }, - cell: ({ row }) => { - const date: string = row.getValue("date"); + } - if (!isMounted || loading) { - return ( -
- -
- ); - } - - return ( -
- {date - ? new Date(Number(date) * 1000).toLocaleDateString("en-US", { + return ( +
+ {date + ? new Date(Number(date) * 1000).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }) - : "N/A"} -
- ); - }, - meta: { - columnClassName: "w-72", - }, + : "N/A"} +
+ ); + }, + meta: { + columnClassName: "w-72", }, - ]; + }, + ]; return (
diff --git a/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistory.ts b/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistory.ts index 050427bf4..f4946c0cf 100644 --- a/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistory.ts +++ b/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistory.ts @@ -26,6 +26,8 @@ export function useBalanceHistory({ filterVariables, itemsPerPage = 10, decimals, + fromTimestamp, + toTimestamp, }: { accountId: string; daoId: DaoIdEnum; @@ -37,6 +39,8 @@ export function useBalanceHistory({ transactionType?: "all" | "buy" | "sell"; filterVariables?: AmountFilterVariables; itemsPerPage?: number; + fromTimestamp?: number; + toTimestamp?: number; }) { const [currentPage, setCurrentPage] = useState(1); const [isPaginationLoading, setIsPaginationLoading] = useState(false); @@ -51,6 +55,8 @@ export function useBalanceHistory({ customFromFilter, customToFilter, filterVariables, + fromTimestamp, + toTimestamp, ]); const variables = useMemo(() => { @@ -64,6 +70,8 @@ export function useBalanceHistory({ to: customToFilter, offset: 0, limit: itemsPerPage, + fromDate: fromTimestamp, + toDate: toTimestamp, }; switch (transactionType) { @@ -86,6 +94,8 @@ export function useBalanceHistory({ orderBy, orderDirection, itemsPerPage, + fromTimestamp, + toTimestamp, ]); const { data, error, loading, fetchMore } = useBalanceHistoryQuery({ diff --git a/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistory.ts b/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistory.ts index 2fcc0ecf5..e04ad6683 100644 --- a/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistory.ts +++ b/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistory.ts @@ -66,6 +66,8 @@ export function useDelegateDelegationHistory({ customFromFilter, customToFilter, itemsPerPage = 10, + fromTimestamp, + toTimestamp, }: { accountId: string; daoId: DaoIdEnum; @@ -76,6 +78,8 @@ export function useDelegateDelegationHistory({ customToFilter?: string; filterVariables?: AmountFilterVariables; itemsPerPage?: number; + fromTimestamp?: number; + toTimestamp?: number; }): UseDelegateDelegationHistoryResult { const [currentPage, setCurrentPage] = useState(1); const [isPaginationLoading, setIsPaginationLoading] = @@ -123,6 +127,8 @@ export function useDelegateDelegationHistory({ }), ...(fromFilter && { delegator: fromFilter }), ...(toFilter && { delegate: toFilter }), + ...(fromTimestamp && { fromDate: fromTimestamp.toString() }), + ...(toTimestamp && { toDate: toTimestamp.toString() }), }), [ accountId, @@ -132,6 +138,8 @@ export function useDelegateDelegationHistory({ filterVariables, fromFilter, toFilter, + fromTimestamp, + toTimestamp, ], ); diff --git a/apps/dashboard/features/holders-and-delegates/hooks/useDelegates.ts b/apps/dashboard/features/holders-and-delegates/hooks/useDelegates.ts index bd144a036..60c0a9fd2 100644 --- a/apps/dashboard/features/holders-and-delegates/hooks/useDelegates.ts +++ b/apps/dashboard/features/holders-and-delegates/hooks/useDelegates.ts @@ -19,6 +19,7 @@ interface ProposalsActivity { totalProposals: number; votedProposals: number; neverVoted: boolean; + avgTimeBeforeEnd?: number; } interface Delegate { @@ -242,6 +243,7 @@ export const useDelegates = ({ }; const proposalsActivity = delegateActivities.get(delegate.accountId); + return { ...delegate, ...votingPowerVariation, diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistory.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistory.tsx index b85721967..a395ac45b 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistory.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistory.tsx @@ -3,6 +3,9 @@ import { DaoIdEnum } from "@/shared/types/daos"; import { BalanceHistoryVariationGraph } from "@/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryVariationGraph"; import { BalanceHistoryTable } from "@/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable"; +import { parseAsStringEnum, useQueryState } from "nuqs"; +import { useMemo } from "react"; +import { SECONDS_PER_DAY } from "@/shared/constants/time-related"; interface BalanceHistoryProps { accountId: string; @@ -10,6 +13,34 @@ interface BalanceHistoryProps { } export const BalanceHistory = ({ accountId, daoId }: BalanceHistoryProps) => { + const [selectedPeriod] = useQueryState( + "selectedPeriod", + parseAsStringEnum(["30d", "90d", "all"]).withDefault("all"), + ); + + const { fromTimestamp, toTimestamp } = useMemo(() => { + const nowInSeconds = Date.now() / 1000; + + if (selectedPeriod === "all") { + return { fromTimestamp: undefined, toTimestamp: undefined }; + } + + let daysInSeconds: number; + switch (selectedPeriod) { + case "90d": + daysInSeconds = 90 * SECONDS_PER_DAY; + break; + default: + daysInSeconds = 30 * SECONDS_PER_DAY; + break; + } + + return { + fromTimestamp: Math.floor(nowInSeconds - daysInSeconds), + toTimestamp: Math.floor(nowInSeconds), + }; + }, [selectedPeriod]); + return (
{/* Graph Section */} @@ -19,7 +50,12 @@ export const BalanceHistory = ({ accountId, daoId }: BalanceHistoryProps) => { {/* Table Section */}
- +
); diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx index 84fa2b8fe..c65cf4ae9 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx @@ -46,9 +46,13 @@ interface BalanceHistoryData { export const BalanceHistoryTable = ({ accountId, daoId, + fromTimestamp, + toTimestamp, }: { accountId: string; daoId: DaoIdEnum; + fromTimestamp?: number; + toTimestamp?: number; }) => { const { decimals } = daoConfig[daoId]; @@ -102,6 +106,8 @@ export const BalanceHistoryTable = ({ customFromFilter, customToFilter, filterVariables, + fromTimestamp, + toTimestamp, }); const isInitialLoading = loading && (!transfers || transfers.length === 0); diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractions.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractions.tsx index fe96c7661..c5f429534 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractions.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractions.tsx @@ -19,7 +19,7 @@ const ChartLegend = ({ if (loading) { return (
- {Array.from({ length: 10 }, (_, i) => ( + {Array.from({ length: 6 }, (_, i) => (
))} @@ -72,20 +72,50 @@ export const TopInteractions = ({ const { topFive, totalCount, - netBalanceChange, legendItems, pieData, chartConfig, + netBalanceChange, loading: loadingVotingPowerData, } = useAccountInteractionsData({ daoId, address }); - if (!topFive || (topFive.length === 0 && !loadingVotingPowerData)) { + if (!topFive || topFive.length === 0) { + if (loadingVotingPowerData) { + // Show loading skeleton instead of "no interactions found" + return ( +
+
+
+
+
+ +
+
+
+

+ Net Tokens In/Out (90D) +

+ +
+
+
+
+
+
+ ); + } return (
); @@ -109,12 +139,12 @@ export const TopInteractions = ({

- Net Tokens In/Out (90D) + Net Tokens In/Out

{!netBalanceChange ? ( ) : ( diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/hooks/useAccountInteractionsData.ts b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/hooks/useAccountInteractionsData.ts index 72174c4fd..9008ec109 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/hooks/useAccountInteractionsData.ts +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/hooks/useAccountInteractionsData.ts @@ -79,7 +79,6 @@ export const useAccountInteractionsData = ({ const { data, loading, error } = useGetAccountInteractionsQuery({ variables: { address, - fromDate, orderDirection: sortDirection as QueryInput_AccountInteractions_OrderDirection, minAmount: filterVariables?.minAmount, diff --git a/apps/dashboard/features/holders-and-delegates/utils/index.ts b/apps/dashboard/features/holders-and-delegates/utils/index.ts index b04bfcf75..1dba6f210 100644 --- a/apps/dashboard/features/holders-and-delegates/utils/index.ts +++ b/apps/dashboard/features/holders-and-delegates/utils/index.ts @@ -1 +1,2 @@ export * from "./constants"; +export { getAvgVoteTimingData } from "./proposalsTableUtils"; diff --git a/apps/dashboard/features/holders-and-delegates/utils/proposalsTableUtils.tsx b/apps/dashboard/features/holders-and-delegates/utils/proposalsTableUtils.tsx index 101d4adca..b5cc4a9e4 100644 --- a/apps/dashboard/features/holders-and-delegates/utils/proposalsTableUtils.tsx +++ b/apps/dashboard/features/holders-and-delegates/utils/proposalsTableUtils.tsx @@ -133,6 +133,27 @@ export const isProposalFinished = (finalResultStatus: string): boolean => { return status !== "ongoing" && status !== "pending"; }; +const formatVoteTiming = ( + timeBeforeEnd: number, + votingPeriod: number, + suffix: "left" | "avg", +): { text: string; percentage: number } => { + const timeElapsed = votingPeriod - timeBeforeEnd; + const percentage = Math.max( + 0, + Math.min(100, (timeElapsed / votingPeriod) * 100), + ); + + const daysLeft = Math.floor(timeBeforeEnd / (24 * 60 * 60)); + + if (daysLeft >= 4) { + return { text: `Early (${daysLeft}d ${suffix})`, percentage }; + } else if (daysLeft < 1) { + return { text: `Late (<1d ${suffix})`, percentage }; + } + return { text: `Late (${daysLeft}d ${suffix})`, percentage }; +}; + // Helper function to format vote timing and calculate percentage export const getVoteTimingData = ( userVote: Query_ProposalsActivity_Proposals_Items_UserVote | null | undefined, @@ -159,22 +180,18 @@ export const getVoteTimingData = ( return { text: "Expired", percentage: 100 }; } - // Calculate how much time has passed as a percentage - const timeElapsed = voteTime - startTime; - const percentage = Math.max( - 0, - Math.min(100, (timeElapsed / daoVotingPeriod) * 100), - ); - - const timeDiff = endTime - voteTime; - const daysLeft = Math.floor(timeDiff / (24 * 60 * 60)); + const timeBeforeEnd = endTime - voteTime; + return formatVoteTiming(timeBeforeEnd, daoVotingPeriod, "left"); +}; - if (daysLeft >= 4) { - return { text: `Early (${daysLeft}d left)`, percentage }; - } else { - if (daysLeft == 0) { - return { text: `Late (<1d left)`, percentage }; - } - return { text: `Late (${daysLeft}d left)`, percentage }; +export const getAvgVoteTimingData = ( + avgTimeBeforeEnd: number | undefined | null, + votingPeriodSeconds: number, + votedProposals: number = 0, +): { text: string; percentage: number } => { + if (!avgTimeBeforeEnd || votedProposals === 0) { + return { text: "-", percentage: 0 }; } + + return formatVoteTiming(avgTimeBeforeEnd, votingPeriodSeconds, "avg"); }; diff --git a/apps/dashboard/features/transactions/utils/transactionsAdapter.ts b/apps/dashboard/features/transactions/utils/transactionsAdapter.ts index 87d7a07d5..c1deb9a60 100644 --- a/apps/dashboard/features/transactions/utils/transactionsAdapter.ts +++ b/apps/dashboard/features/transactions/utils/transactionsAdapter.ts @@ -60,6 +60,8 @@ const formatRelativeTime = (timestampSec: string): string => { const minutes = Math.floor(diff / 60); const hours = Math.floor(diff / 3600); const days = Math.floor(diff / 86400); + const years = Math.floor(days / 365); + if (years > 0) return `${years} year${years > 1 ? "s" : ""} ago`; if (days > 0) return `${days} day${days > 1 ? "s" : ""} ago`; if (hours > 0) return `${hours} hour${hours > 1 ? "s" : ""} ago`; if (minutes > 0) return `${minutes} min ago`; diff --git a/packages/graphql-client/documents/balanceHistory.graphql b/packages/graphql-client/documents/balanceHistory.graphql index 5aa2d243e..08faf5c45 100644 --- a/packages/graphql-client/documents/balanceHistory.graphql +++ b/packages/graphql-client/documents/balanceHistory.graphql @@ -8,6 +8,8 @@ query BalanceHistory( $to: String $fromValue: String $toValue: String + $fromDate: Float + $toDate: Float ) { transfers( address: $address @@ -19,6 +21,8 @@ query BalanceHistory( to: $to fromValue: $fromValue toValue: $toValue + fromDate: $fromDate + toDate: $toDate ) { items { timestamp diff --git a/packages/graphql-client/documents/delegate/drawer/voting-power/delegatorsVotingPower.graphql b/packages/graphql-client/documents/delegate/drawer/voting-power/delegatorsVotingPower.graphql index 0075629cf..7b77c3949 100644 --- a/packages/graphql-client/documents/delegate/drawer/voting-power/delegatorsVotingPower.graphql +++ b/packages/graphql-client/documents/delegate/drawer/voting-power/delegatorsVotingPower.graphql @@ -6,7 +6,7 @@ query GetDelegatorVotingPowerDetails( $skip: NonNegativeInt ) { votingPowerByAccountId(accountId: $address) { - accountId + accountId votingPower } accountBalances( diff --git a/packages/graphql-client/documents/delegationsAndHistoricalVoting.graphql b/packages/graphql-client/documents/delegationsAndHistoricalVoting.graphql index 441f2aa46..9e288f6e9 100644 --- a/packages/graphql-client/documents/delegationsAndHistoricalVoting.graphql +++ b/packages/graphql-client/documents/delegationsAndHistoricalVoting.graphql @@ -22,6 +22,7 @@ query GetHistoricalVotingAndActivity( totalProposals votedProposals neverVoted + avgTimeBeforeEnd } } @@ -32,5 +33,6 @@ query GetDelegateProposalsActivity($address: String!, $fromDate: String) { totalProposals votedProposals neverVoted + avgTimeBeforeEnd } } diff --git a/packages/graphql-client/documents/historicalVotingPowerByAccountId.graphql b/packages/graphql-client/documents/historicalVotingPowerByAccountId.graphql index fcfca4059..076eba0a1 100644 --- a/packages/graphql-client/documents/historicalVotingPowerByAccountId.graphql +++ b/packages/graphql-client/documents/historicalVotingPowerByAccountId.graphql @@ -6,6 +6,8 @@ query HistoricalVotingPowerByAccount( $orderDirection: queryInput_historicalVotingPowerByAccountId_orderDirection = desc $toValue: String $fromValue: String + $fromDate: String + $toDate: String ) { historicalVotingPowerByAccountId( address: $account @@ -15,6 +17,8 @@ query HistoricalVotingPowerByAccount( limit: $limit toValue: $toValue fromValue: $fromValue + fromDate: $fromDate + toDate: $toDate ) { items { accountId diff --git a/packages/graphql-client/documents/token-holder/drawer/accountInteractions.graphql b/packages/graphql-client/documents/token-holder/drawer/accountInteractions.graphql index 245c0e15e..50749dae0 100644 --- a/packages/graphql-client/documents/token-holder/drawer/accountInteractions.graphql +++ b/packages/graphql-client/documents/token-holder/drawer/accountInteractions.graphql @@ -1,6 +1,5 @@ query getAccountInteractions( $address: String! - $fromDate: String $limit: PositiveInt $maxAmount: String $minAmount: String @@ -10,7 +9,6 @@ query getAccountInteractions( ) { accountInteractions( address: $address - fromDate: $fromDate limit: $limit maxAmount: $maxAmount minAmount: $minAmount diff --git a/packages/graphql-client/generated.ts b/packages/graphql-client/generated.ts index 2c1674aee..06e26b576 100644 --- a/packages/graphql-client/generated.ts +++ b/packages/graphql-client/generated.ts @@ -2883,6 +2883,8 @@ export type BalanceHistoryQueryVariables = Exact<{ to?: InputMaybe; fromValue?: InputMaybe; toValue?: InputMaybe; + fromDate?: InputMaybe; + toDate?: InputMaybe; }>; @@ -3005,7 +3007,7 @@ export type GetHistoricalVotingAndActivityQueryVariables = Exact<{ }>; -export type GetHistoricalVotingAndActivityQuery = { __typename?: 'Query', votingPowerVariations?: { __typename?: 'votingPowerVariations_200_response', items: Array<{ __typename?: 'query_votingPowerVariations_items_items', accountId: string, previousVotingPower: string, currentVotingPower: string, percentageChange: string, absoluteChange: string } | null> } | null, proposalsActivity?: { __typename?: 'proposalsActivity_200_response', totalProposals: number, votedProposals: number, neverVoted: boolean } | null }; +export type GetHistoricalVotingAndActivityQuery = { __typename?: 'Query', votingPowerVariations?: { __typename?: 'votingPowerVariations_200_response', items: Array<{ __typename?: 'query_votingPowerVariations_items_items', accountId: string, previousVotingPower: string, currentVotingPower: string, percentageChange: string, absoluteChange: string } | null> } | null, proposalsActivity?: { __typename?: 'proposalsActivity_200_response', totalProposals: number, votedProposals: number, neverVoted: boolean, avgTimeBeforeEnd: number } | null }; export type GetDelegateProposalsActivityQueryVariables = Exact<{ address: Scalars['String']['input']; @@ -3013,7 +3015,7 @@ export type GetDelegateProposalsActivityQueryVariables = Exact<{ }>; -export type GetDelegateProposalsActivityQuery = { __typename?: 'Query', proposalsActivity?: { __typename?: 'proposalsActivity_200_response', address: string, totalProposals: number, votedProposals: number, neverVoted: boolean } | null }; +export type GetDelegateProposalsActivityQuery = { __typename?: 'Query', proposalsActivity?: { __typename?: 'proposalsActivity_200_response', address: string, totalProposals: number, votedProposals: number, neverVoted: boolean, avgTimeBeforeEnd: number } | null }; export type GetProposalsFromDaoQueryVariables = Exact<{ skip?: InputMaybe; @@ -3095,6 +3097,8 @@ export type HistoricalVotingPowerByAccountQueryVariables = Exact<{ orderDirection?: InputMaybe; toValue?: InputMaybe; fromValue?: InputMaybe; + fromDate?: InputMaybe; + toDate?: InputMaybe; }>; @@ -3128,7 +3132,6 @@ export type GetDaoAddressesAccountBalancesQuery = { __typename?: 'Query', accoun export type GetAccountInteractionsQueryVariables = Exact<{ address: Scalars['String']['input']; - fromDate?: InputMaybe; limit?: InputMaybe; maxAmount?: InputMaybe; minAmount?: InputMaybe; @@ -3189,7 +3192,7 @@ export type TransactionsQuery = { __typename?: 'Query', transactions?: { __typen export const BalanceHistoryDocument = gql` - query BalanceHistory($address: String!, $offset: Float = 0, $limit: Float = 10, $sortBy: queryInput_transfers_sortBy, $sortOrder: queryInput_transfers_sortOrder, $from: String, $to: String, $fromValue: String, $toValue: String) { + query BalanceHistory($address: String!, $offset: Float = 0, $limit: Float = 10, $sortBy: queryInput_transfers_sortBy, $sortOrder: queryInput_transfers_sortOrder, $from: String, $to: String, $fromValue: String, $toValue: String, $fromDate: Float, $toDate: Float) { transfers( address: $address sortBy: $sortBy @@ -3200,6 +3203,8 @@ export const BalanceHistoryDocument = gql` to: $to fromValue: $fromValue toValue: $toValue + fromDate: $fromDate + toDate: $toDate ) { items { timestamp @@ -3234,6 +3239,8 @@ export const BalanceHistoryDocument = gql` * to: // value for 'to' * fromValue: // value for 'fromValue' * toValue: // value for 'toValue' + * fromDate: // value for 'fromDate' + * toDate: // value for 'toDate' * }, * }); */ @@ -3922,6 +3929,7 @@ export const GetHistoricalVotingAndActivityDocument = gql` totalProposals votedProposals neverVoted + avgTimeBeforeEnd } } `; @@ -3968,6 +3976,7 @@ export const GetDelegateProposalsActivityDocument = gql` totalProposals votedProposals neverVoted + avgTimeBeforeEnd } } `; @@ -4423,7 +4432,7 @@ export type HistoricalVotingPowerLazyQueryHookResult = ReturnType; export type HistoricalVotingPowerQueryResult = Apollo.QueryResult; export const HistoricalVotingPowerByAccountDocument = gql` - query HistoricalVotingPowerByAccount($account: String!, $skip: NonNegativeInt, $limit: PositiveInt = 10, $orderBy: queryInput_historicalVotingPowerByAccountId_orderBy = timestamp, $orderDirection: queryInput_historicalVotingPowerByAccountId_orderDirection = desc, $toValue: String, $fromValue: String) { + query HistoricalVotingPowerByAccount($account: String!, $skip: NonNegativeInt, $limit: PositiveInt = 10, $orderBy: queryInput_historicalVotingPowerByAccountId_orderBy = timestamp, $orderDirection: queryInput_historicalVotingPowerByAccountId_orderDirection = desc, $toValue: String, $fromValue: String, $fromDate: String, $toDate: String) { historicalVotingPowerByAccountId( address: $account skip: $skip @@ -4432,6 +4441,8 @@ export const HistoricalVotingPowerByAccountDocument = gql` limit: $limit toValue: $toValue fromValue: $fromValue + fromDate: $fromDate + toDate: $toDate ) { items { accountId @@ -4475,6 +4486,8 @@ export const HistoricalVotingPowerByAccountDocument = gql` * orderDirection: // value for 'orderDirection' * toValue: // value for 'toValue' * fromValue: // value for 'fromValue' + * fromDate: // value for 'fromDate' + * toDate: // value for 'toDate' * }, * }); */ @@ -4665,10 +4678,9 @@ export type GetDaoAddressesAccountBalancesLazyQueryHookResult = ReturnType; export type GetDaoAddressesAccountBalancesQueryResult = Apollo.QueryResult; export const GetAccountInteractionsDocument = gql` - query getAccountInteractions($address: String!, $fromDate: String, $limit: PositiveInt, $maxAmount: String, $minAmount: String, $orderDirection: queryInput_accountInteractions_orderDirection, $skip: NonNegativeInt, $filterAddress: String) { + query getAccountInteractions($address: String!, $limit: PositiveInt, $maxAmount: String, $minAmount: String, $orderDirection: queryInput_accountInteractions_orderDirection, $skip: NonNegativeInt, $filterAddress: String) { accountInteractions( address: $address - fromDate: $fromDate limit: $limit maxAmount: $maxAmount minAmount: $minAmount @@ -4700,7 +4712,6 @@ export const GetAccountInteractionsDocument = gql` * const { data, loading, error } = useGetAccountInteractionsQuery({ * variables: { * address: // value for 'address' - * fromDate: // value for 'fromDate' * limit: // value for 'limit' * maxAmount: // value for 'maxAmount' * minAmount: // value for 'minAmount' diff --git a/packages/graphql-client/types.ts b/packages/graphql-client/types.ts index 18033898d..82700e2e2 100644 --- a/packages/graphql-client/types.ts +++ b/packages/graphql-client/types.ts @@ -2880,6 +2880,8 @@ export type BalanceHistoryQueryVariables = Exact<{ to?: InputMaybe; fromValue?: InputMaybe; toValue?: InputMaybe; + fromDate?: InputMaybe; + toDate?: InputMaybe; }>; @@ -3002,7 +3004,7 @@ export type GetHistoricalVotingAndActivityQueryVariables = Exact<{ }>; -export type GetHistoricalVotingAndActivityQuery = { __typename?: 'Query', votingPowerVariations?: { __typename?: 'votingPowerVariations_200_response', items: Array<{ __typename?: 'query_votingPowerVariations_items_items', accountId: string, previousVotingPower: string, currentVotingPower: string, percentageChange: string, absoluteChange: string } | null> } | null, proposalsActivity?: { __typename?: 'proposalsActivity_200_response', totalProposals: number, votedProposals: number, neverVoted: boolean } | null }; +export type GetHistoricalVotingAndActivityQuery = { __typename?: 'Query', votingPowerVariations?: { __typename?: 'votingPowerVariations_200_response', items: Array<{ __typename?: 'query_votingPowerVariations_items_items', accountId: string, previousVotingPower: string, currentVotingPower: string, percentageChange: string, absoluteChange: string } | null> } | null, proposalsActivity?: { __typename?: 'proposalsActivity_200_response', totalProposals: number, votedProposals: number, neverVoted: boolean, avgTimeBeforeEnd: number } | null }; export type GetDelegateProposalsActivityQueryVariables = Exact<{ address: Scalars['String']['input']; @@ -3010,7 +3012,7 @@ export type GetDelegateProposalsActivityQueryVariables = Exact<{ }>; -export type GetDelegateProposalsActivityQuery = { __typename?: 'Query', proposalsActivity?: { __typename?: 'proposalsActivity_200_response', address: string, totalProposals: number, votedProposals: number, neverVoted: boolean } | null }; +export type GetDelegateProposalsActivityQuery = { __typename?: 'Query', proposalsActivity?: { __typename?: 'proposalsActivity_200_response', address: string, totalProposals: number, votedProposals: number, neverVoted: boolean, avgTimeBeforeEnd: number } | null }; export type GetProposalsFromDaoQueryVariables = Exact<{ skip?: InputMaybe; @@ -3092,6 +3094,8 @@ export type HistoricalVotingPowerByAccountQueryVariables = Exact<{ orderDirection?: InputMaybe; toValue?: InputMaybe; fromValue?: InputMaybe; + fromDate?: InputMaybe; + toDate?: InputMaybe; }>; @@ -3125,7 +3129,6 @@ export type GetDaoAddressesAccountBalancesQuery = { __typename?: 'Query', accoun export type GetAccountInteractionsQueryVariables = Exact<{ address: Scalars['String']['input']; - fromDate?: InputMaybe; limit?: InputMaybe; maxAmount?: InputMaybe; minAmount?: InputMaybe; From 3e654a696b12d8b5804a4627dcadf72d8b720c2a Mon Sep 17 00:00:00 2001 From: Bruno Date: Thu, 29 Jan 2026 20:22:36 -0300 Subject: [PATCH 02/11] feat: refactor holders and delegates components for improved timestamp handling and UI consistency --- .../HoldersAndDelegatesSection.tsx | 53 +++++++++---------- .../delegate/Delegates.tsx | 33 ++---------- .../DelegateDelegationHistoryTable.tsx | 32 ++--------- .../DelegateDelegationsHistory.tsx | 31 +++-------- .../VotingPowerVariationGraph.tsx | 36 ++++--------- .../holders-and-delegates/hooks/index.ts | 21 +++++--- .../holders-and-delegates/hooks/types.ts | 25 +++++++++ .../hooks/useBalanceHistory.ts | 4 +- .../hooks/useBalanceHistoryGraph.ts | 2 + .../hooks/useDelegateDelegationHistory.ts | 31 +++++------ .../useDelegateDelegationHistoryGraph.ts | 2 + .../hooks/useDelegationHistory.ts | 14 +---- .../hooks/useProposalsActivity.ts | 5 +- .../drawer/balance-history/BalanceHistory.tsx | 31 +++-------- .../balance-history/BalanceHistoryTable.tsx | 30 ++--------- .../BalanceHistoryVariationGraph.tsx | 34 ++++-------- .../utils/formatRelativeTime.ts | 37 +++++++++++++ .../holders-and-delegates/utils/index.ts | 2 + .../utils/timestampUtils.ts | 24 +++++++++ 19 files changed, 198 insertions(+), 249 deletions(-) create mode 100644 apps/dashboard/features/holders-and-delegates/hooks/types.ts create mode 100644 apps/dashboard/features/holders-and-delegates/utils/formatRelativeTime.ts create mode 100644 apps/dashboard/features/holders-and-delegates/utils/timestampUtils.ts diff --git a/apps/dashboard/features/holders-and-delegates/HoldersAndDelegatesSection.tsx b/apps/dashboard/features/holders-and-delegates/HoldersAndDelegatesSection.tsx index d37e34909..b168bcfc9 100644 --- a/apps/dashboard/features/holders-and-delegates/HoldersAndDelegatesSection.tsx +++ b/apps/dashboard/features/holders-and-delegates/HoldersAndDelegatesSection.tsx @@ -15,6 +15,16 @@ import { parseAsString, parseAsStringEnum, useQueryState } from "nuqs"; type TabId = "tokenHolders" | "delegates"; +interface TabConfig { + id: TabId; + label: string; +} + +const TABS: TabConfig[] = [ + { id: "tokenHolders", label: "TOKEN HOLDERS" }, + { id: "delegates", label: "DELEGATES" }, +] as const; + export const HoldersAndDelegatesSection = ({ daoId }: { daoId: DaoIdEnum }) => { const defaultDays = TimeInterval.NINETY_DAYS; const [days, setDays] = useQueryState( @@ -50,34 +60,21 @@ export const HoldersAndDelegatesSection = ({ daoId }: { daoId: DaoIdEnum }) => { delegates: , }; - const HoldersAndDelegatesLeftComponent = () => { - const tabs: Array<{ id: TabId; label: string }> = [ - { - id: "tokenHolders", - label: "TOKEN HOLDERS", - }, - { - id: "delegates", - label: "DELEGATES", - }, - ]; - - return ( -
-
- {tabs.map((tab) => ( - - ))} -
+ const TabsHeader = () => ( +
+
+ {TABS.map((tab) => ( + + ))}
- ); - }; +
+ ); return (
@@ -89,7 +86,7 @@ export const HoldersAndDelegatesSection = ({ daoId }: { daoId: DaoIdEnum }) => { >
- + { - const now = Date.now(); - const msPerDay = 24 * 60 * 60 * 1000; - - let daysBack: number; - switch (period) { - case TimeInterval.SEVEN_DAYS: - daysBack = 7; - break; - case TimeInterval.THIRTY_DAYS: - daysBack = 30; - break; - case TimeInterval.NINETY_DAYS: - daysBack = 90; - break; - case TimeInterval.ONE_YEAR: - daysBack = 365; - break; - default: - daysBack = 30; - } - - return Math.floor((now - daysBack * msPerDay) / 1000); +// Converts a TimeInterval to a timestamp (in seconds) representing the start date. +const getFromTimestamp = (period: TimeInterval): number => { + return Math.floor(Date.now() / 1000) - DAYS_IN_SECONDS[period]; }; export const Delegates = ({ @@ -108,10 +88,7 @@ export const Delegates = ({ }; // Calculate time-based parameters - const fromDate = useMemo( - () => getTimeDataFromPeriod(timePeriod), - [timePeriod], - ); + const fromDate = useMemo(() => getFromTimestamp(timePeriod), [timePeriod]); const { data, diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx index 2d7fcaa4e..9571ceb5e 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx @@ -15,6 +15,7 @@ import { useDelegateDelegationHistory, DelegationHistoryItem, } from "@/features/holders-and-delegates/hooks/useDelegateDelegationHistory"; +import { formatRelativeTime } from "@/features/holders-and-delegates/utils"; import daoConfigByDaoId from "@/shared/dao-config"; import { Table } from "@/shared/components/design-system/table/Table"; import { AmountFilter } from "@/shared/components/design-system/table/filters/amount-filter/AmountFilter"; @@ -60,8 +61,7 @@ export const DelegateDelegationHistoryTable = ({ "active", parseAsBoolean.withDefault(false), ); - // const [fromFilter, setFromFilter] = useQueryState("from", parseAsAddress); - // const [toFilter, setToFilter] = useQueryState("to", parseAsAddress); + const sortOptions: SortOption[] = [ { value: "largest-first", label: "Largest first" }, { value: "smallest-first", label: "Smallest first" }, @@ -81,41 +81,15 @@ export const DelegateDelegationHistoryTable = ({ const isInitialLoading = loading && (!delegationHistory || delegationHistory.length === 0); - // Handle sorting const handleSort = (field: "timestamp" | "delta") => { if (sortBy === field) { setSortDirection(sortDirection === "asc" ? "desc" : "asc"); } else { setSortBy(field); - setSortDirection("desc"); // Always start with desc for new sort field - } - }; - - // Format timestamp to relative time in days - const formatRelativeTime = (timestamp: string) => { - const date = new Date(parseInt(timestamp) * 1000); - const now = new Date(); - const diffInMs = now.getTime() - date.getTime(); - const diffInSeconds = Math.floor(diffInMs / 1000); - const diffInMinutes = Math.floor(diffInSeconds / 60); - const diffInHours = Math.floor(diffInMinutes / 60); - const diffInDays = Math.floor(diffInHours / 24); - const diffInYears = Math.floor(diffInDays / 365); - - if (diffInYears > 0) { - return `${diffInYears} year${diffInYears > 1 ? "s" : ""} ago`; - } else if (diffInDays > 0) { - return `${diffInDays} day${diffInDays > 1 ? "s" : ""} ago`; - } else if (diffInHours > 0) { - return `${diffInHours} hour${diffInHours > 1 ? "s" : ""} ago`; - } else if (diffInMinutes > 0) { - return `${diffInMinutes} minute${diffInMinutes > 1 ? "s" : ""} ago`; - } else { - return "Just now"; + setSortDirection("desc"); } }; - // Determine delegation type and color based on gain/loss const getDelegationType = (item: DelegationHistoryItem) => { let statusText = ""; diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory.tsx index 128d6dba8..8dfdbd87a 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory.tsx @@ -3,7 +3,8 @@ import { DelegateDelegationHistoryTable } from "@/features/holders-and-delegates import { VotingPowerVariationGraph } from "@/features/holders-and-delegates/delegate/drawer/delegation-history/VotingPowerVariationGraph"; import { parseAsStringEnum, useQueryState } from "nuqs"; import { useMemo } from "react"; -import { SECONDS_PER_DAY } from "@/shared/constants/time-related"; +import { getTimestampRangeFromPeriod } from "@/features/holders-and-delegates/utils"; +import { TimePeriod } from "@/features/holders-and-delegates/components/TimePeriodSwitcher"; interface DelegateDelegationsHistoryProps { accountId: string; @@ -16,31 +17,13 @@ export const DelegateDelegationsHistory = ({ }: DelegateDelegationsHistoryProps) => { const [selectedPeriod] = useQueryState( "selectedPeriod", - parseAsStringEnum(["30d", "90d", "all"]).withDefault("all"), + parseAsStringEnum(["30d", "90d", "all"]).withDefault("all"), ); - const { fromTimestamp, toTimestamp } = useMemo(() => { - const nowInSeconds = Date.now() / 1000; - - if (selectedPeriod === "all") { - return { fromTimestamp: undefined, toTimestamp: undefined }; - } - - let daysInSeconds: number; - switch (selectedPeriod) { - case "90d": - daysInSeconds = 90 * SECONDS_PER_DAY; - break; - default: - daysInSeconds = 30 * SECONDS_PER_DAY; - break; - } - - return { - fromTimestamp: Math.floor(nowInSeconds - daysInSeconds), - toTimestamp: Math.floor(nowInSeconds), - }; - }, [selectedPeriod]); + const { fromTimestamp, toTimestamp } = useMemo( + () => getTimestampRangeFromPeriod(selectedPeriod), + [selectedPeriod], + ); return (
diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/VotingPowerVariationGraph.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/VotingPowerVariationGraph.tsx index b4360e97e..d99d85e96 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/VotingPowerVariationGraph.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/VotingPowerVariationGraph.tsx @@ -16,12 +16,15 @@ import { formatNumberUserReadable } from "@/shared/utils"; import { DaoIdEnum } from "@/shared/types/daos"; import { DelegationHistoryGraphItem } from "@/features/holders-and-delegates/hooks"; import { useDelegateDelegationHistoryGraph } from "@/features/holders-and-delegates/hooks/useDelegateDelegationHistoryGraph"; -import { TimePeriodSwitcher } from "@/features/holders-and-delegates/components/TimePeriodSwitcher"; +import { + TimePeriod, + TimePeriodSwitcher, +} from "@/features/holders-and-delegates/components/TimePeriodSwitcher"; import { ChartExceptionState } from "@/shared/components"; import { EnsAvatar } from "@/shared/components/design-system/avatars/ens-avatar/EnsAvatar"; import { AnticaptureWatermark } from "@/shared/components/icons/AnticaptureWatermark"; import { parseAsStringEnum, useQueryState } from "nuqs"; -import { SECONDS_PER_DAY } from "@/shared/constants/time-related"; +import { getTimestampRangeFromPeriod } from "@/features/holders-and-delegates/utils"; interface VotingPowerVariationGraphProps { accountId: string; @@ -84,32 +87,13 @@ export const VotingPowerVariationGraph = ({ }: VotingPowerVariationGraphProps) => { const [selectedPeriod, setSelectedPeriod] = useQueryState( "selectedPeriod", - parseAsStringEnum(["30d", "90d", "all"]).withDefault("all"), + parseAsStringEnum(["30d", "90d", "all"]).withDefault("all"), ); - // Calculate timestamp range based on time period - const { fromTimestamp, toTimestamp } = useMemo(() => { - // For "all", treat as all time by not setting limits - if (selectedPeriod === "all") { - return { fromTimestamp: undefined, toTimestamp: undefined }; - } - - const nowInSeconds = Date.now() / 1000; - let daysInSeconds: number; - switch (selectedPeriod) { - case "90d": - daysInSeconds = 90 * SECONDS_PER_DAY; - break; - default: - daysInSeconds = 30 * SECONDS_PER_DAY; - break; - } - - return { - fromTimestamp: Math.floor(nowInSeconds - daysInSeconds), - toTimestamp: Math.floor(nowInSeconds), - }; - }, [selectedPeriod]); + const { fromTimestamp, toTimestamp } = useMemo( + () => getTimestampRangeFromPeriod(selectedPeriod), + [selectedPeriod], + ); const { delegationHistory, loading, error } = useDelegateDelegationHistoryGraph( diff --git a/apps/dashboard/features/holders-and-delegates/hooks/index.ts b/apps/dashboard/features/holders-and-delegates/hooks/index.ts index 71651e15c..32319f3bc 100644 --- a/apps/dashboard/features/holders-and-delegates/hooks/index.ts +++ b/apps/dashboard/features/holders-and-delegates/hooks/index.ts @@ -1,12 +1,21 @@ -export * from "./useDelegates"; -export * from "./useTokenHolders"; -export * from "./useBalanceHistory"; +export type { + PaginationInfo, + SimplePaginationInfo, + AmountFilterVariables, + LoadingState, +} from "./types"; +export { useDelegates } from "./useDelegates"; +export { useTokenHolders } from "./useTokenHolders"; +export type { TokenHolder } from "./useTokenHolders"; +export { useBalanceHistory } from "./useBalanceHistory"; export { useDelegateDelegationHistory, type DelegationHistoryItem, type UseDelegateDelegationHistoryResult, } from "./useDelegateDelegationHistory"; -export * from "./useDelegateDelegationHistoryGraph"; +export { useDelegateDelegationHistoryGraph } from "./useDelegateDelegationHistoryGraph"; +export type { DelegationHistoryGraphItem } from "./useDelegateDelegationHistoryGraph"; export { useDelegationHistory } from "./useDelegationHistory"; -export { useTokenHolders } from "./useTokenHolders"; -export { useDelegates } from "./useDelegates"; +export { useBalanceHistoryGraph } from "./useBalanceHistoryGraph"; +export type { BalanceHistoryGraphItem } from "./useBalanceHistoryGraph"; +export { useProposalsActivity } from "./useProposalsActivity"; diff --git a/apps/dashboard/features/holders-and-delegates/hooks/types.ts b/apps/dashboard/features/holders-and-delegates/hooks/types.ts new file mode 100644 index 000000000..60656ae42 --- /dev/null +++ b/apps/dashboard/features/holders-and-delegates/hooks/types.ts @@ -0,0 +1,25 @@ +export interface PaginationInfo { + hasNextPage: boolean; + hasPreviousPage: boolean; + currentPage: number; + totalPages: number; + totalCount: number; + itemsPerPage: number; + currentItemsCount: number; +} + +export interface SimplePaginationInfo { + hasNextPage: boolean; + hasPreviousPage: boolean; + currentPage: number; +} + +export interface AmountFilterVariables { + fromValue?: string | null; + toValue?: string | null; +} + +export interface LoadingState { + loading: boolean; + fetchingMore: boolean; +} diff --git a/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistory.ts b/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistory.ts index f4946c0cf..3ae0f9b23 100644 --- a/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistory.ts +++ b/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistory.ts @@ -1,3 +1,5 @@ +"use client"; + import { formatUnits } from "viem"; import { useMemo, useState, useEffect, useCallback } from "react"; @@ -9,7 +11,7 @@ import { } from "@anticapture/graphql-client/hooks"; import { DaoIdEnum } from "@/shared/types/daos"; -import { AmountFilterVariables } from "@/features/holders-and-delegates/hooks/useDelegateDelegationHistory"; +import { AmountFilterVariables } from "./types"; import { QueryInput_Transfers_SortBy, QueryInput_Transfers_SortOrder, diff --git a/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistoryGraph.ts b/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistoryGraph.ts index 9f65ce99b..70290f400 100644 --- a/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistoryGraph.ts +++ b/apps/dashboard/features/holders-and-delegates/hooks/useBalanceHistoryGraph.ts @@ -1,3 +1,5 @@ +"use client"; + import { useMemo } from "react"; import { formatUnits } from "viem"; diff --git a/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistory.ts b/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistory.ts index e04ad6683..cf39d1e95 100644 --- a/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistory.ts +++ b/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistory.ts @@ -12,6 +12,7 @@ import { HistoricalVotingPowerByAccountQueryVariables, QueryInput_HistoricalVotingPowerByAccountId_OrderDirection, } from "@anticapture/graphql-client"; +import { AmountFilterVariables } from "./types"; // Interface for a single delegation history item export interface DelegationHistoryItem { @@ -52,10 +53,18 @@ export interface UseDelegateDelegationHistoryResult { hasPreviousPage: boolean; } -export type AmountFilterVariables = Pick< - HistoricalVotingPowerByAccountQueryVariables, - "fromValue" | "toValue" ->; +interface UseDelegateDelegationHistoryParams { + accountId: string; + daoId: DaoIdEnum; + orderBy?: string; + orderDirection?: "asc" | "desc"; + customFromFilter?: string; + customToFilter?: string; + filterVariables?: AmountFilterVariables; + itemsPerPage?: number; + fromTimestamp?: number; + toTimestamp?: number; +} export function useDelegateDelegationHistory({ accountId, @@ -68,19 +77,7 @@ export function useDelegateDelegationHistory({ itemsPerPage = 10, fromTimestamp, toTimestamp, -}: { - accountId: string; - daoId: DaoIdEnum; - orderBy?: string; - orderDirection?: "asc" | "desc"; - transactionType?: "all" | "buy" | "sell"; - customFromFilter?: string; - customToFilter?: string; - filterVariables?: AmountFilterVariables; - itemsPerPage?: number; - fromTimestamp?: number; - toTimestamp?: number; -}): UseDelegateDelegationHistoryResult { +}: UseDelegateDelegationHistoryParams): UseDelegateDelegationHistoryResult { const [currentPage, setCurrentPage] = useState(1); const [isPaginationLoading, setIsPaginationLoading] = useState(false); diff --git a/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistoryGraph.ts b/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistoryGraph.ts index 35eead289..a5ac00437 100644 --- a/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistoryGraph.ts +++ b/apps/dashboard/features/holders-and-delegates/hooks/useDelegateDelegationHistoryGraph.ts @@ -1,3 +1,5 @@ +"use client"; + import { useMemo } from "react"; import { QueryInput_HistoricalVotingPowerByAccountId_OrderDirection, diff --git a/apps/dashboard/features/holders-and-delegates/hooks/useDelegationHistory.ts b/apps/dashboard/features/holders-and-delegates/hooks/useDelegationHistory.ts index 54b75cbe3..2b488ce79 100644 --- a/apps/dashboard/features/holders-and-delegates/hooks/useDelegationHistory.ts +++ b/apps/dashboard/features/holders-and-delegates/hooks/useDelegationHistory.ts @@ -8,19 +8,7 @@ import { import { useMemo, useCallback, useState, useEffect } from "react"; import { NetworkStatus } from "@apollo/client"; import { DaoIdEnum } from "@/shared/types/daos"; -import { AmountFilterVariables } from "@/features/holders-and-delegates/hooks/useDelegateDelegationHistory"; - -interface PaginationInfo { - hasNextPage: boolean; - hasPreviousPage: boolean; - endCursor?: string | null; - startCursor?: string | null; - totalCount: number; - currentPage: number; - totalPages: number; - itemsPerPage: number; - currentItemsCount: number; -} +import { AmountFilterVariables, PaginationInfo } from "./types"; interface UseDelegationHistoryResult { data: diff --git a/apps/dashboard/features/holders-and-delegates/hooks/useProposalsActivity.ts b/apps/dashboard/features/holders-and-delegates/hooks/useProposalsActivity.ts index 41387a072..6b2719e84 100644 --- a/apps/dashboard/features/holders-and-delegates/hooks/useProposalsActivity.ts +++ b/apps/dashboard/features/holders-and-delegates/hooks/useProposalsActivity.ts @@ -1,3 +1,5 @@ +"use client"; + import { useCallback, useEffect, useMemo, useState } from "react"; import { useGetProposalsActivityQuery } from "@anticapture/graphql-client/hooks"; @@ -8,8 +10,7 @@ import { import { DaoIdEnum } from "@/shared/types/daos"; import { NetworkStatus } from "@apollo/client"; -interface UseProposalsActivityParams - extends GetProposalsActivityQueryVariables { +interface UseProposalsActivityParams extends GetProposalsActivityQueryVariables { itemsPerPage: number; daoId: DaoIdEnum; } diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistory.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistory.tsx index a395ac45b..b9b27bc32 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistory.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistory.tsx @@ -5,7 +5,8 @@ import { BalanceHistoryVariationGraph } from "@/features/holders-and-delegates/t import { BalanceHistoryTable } from "@/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable"; import { parseAsStringEnum, useQueryState } from "nuqs"; import { useMemo } from "react"; -import { SECONDS_PER_DAY } from "@/shared/constants/time-related"; +import { getTimestampRangeFromPeriod } from "@/features/holders-and-delegates/utils"; +import { TimePeriod } from "@/features/holders-and-delegates/components/TimePeriodSwitcher"; interface BalanceHistoryProps { accountId: string; @@ -15,31 +16,13 @@ interface BalanceHistoryProps { export const BalanceHistory = ({ accountId, daoId }: BalanceHistoryProps) => { const [selectedPeriod] = useQueryState( "selectedPeriod", - parseAsStringEnum(["30d", "90d", "all"]).withDefault("all"), + parseAsStringEnum(["30d", "90d", "all"]).withDefault("all"), ); - const { fromTimestamp, toTimestamp } = useMemo(() => { - const nowInSeconds = Date.now() / 1000; - - if (selectedPeriod === "all") { - return { fromTimestamp: undefined, toTimestamp: undefined }; - } - - let daysInSeconds: number; - switch (selectedPeriod) { - case "90d": - daysInSeconds = 90 * SECONDS_PER_DAY; - break; - default: - daysInSeconds = 30 * SECONDS_PER_DAY; - break; - } - - return { - fromTimestamp: Math.floor(nowInSeconds - daysInSeconds), - toTimestamp: Math.floor(nowInSeconds), - }; - }, [selectedPeriod]); + const { fromTimestamp, toTimestamp } = useMemo( + () => getTimestampRangeFromPeriod(selectedPeriod), + [selectedPeriod], + ); return (
diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx index c65cf4ae9..aacda3ebe 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx @@ -31,6 +31,7 @@ import { useQueryState, useQueryStates, } from "nuqs"; +import { formatRelativeTime } from "@/features/holders-and-delegates/utils"; interface BalanceHistoryData { id: string; @@ -115,33 +116,8 @@ export const BalanceHistoryTable = ({ // Transform transfers to table data format const transformedData = useMemo(() => { return transfers.map((transfer) => { - const transferDate = new Date(parseInt(transfer.timestamp) * 1000); - const now = new Date(); - const diffInMs = now.getTime() - transferDate.getTime(); - const diffInSeconds = Math.floor(diffInMs / 1000); - const diffInMinutes = Math.floor(diffInSeconds / 60); - const diffInHours = Math.floor(diffInMinutes / 60); - const diffInDays = Math.floor(diffInHours / 24); - const diffInWeeks = Math.floor(diffInDays / 7); - const diffInMonths = Math.floor(diffInDays / 30); - const diffInYears = Math.floor(diffInDays / 365); - - let relativeTime; - if (diffInYears > 0) { - relativeTime = `${diffInYears} year${diffInYears > 1 ? "s" : ""} ago`; - } else if (diffInMonths > 0) { - relativeTime = `${diffInMonths} month${diffInMonths > 1 ? "s" : ""} ago`; - } else if (diffInWeeks > 0) { - relativeTime = `${diffInWeeks} week${diffInWeeks > 1 ? "s" : ""} ago`; - } else if (diffInDays > 0) { - relativeTime = `${diffInDays} day${diffInDays > 1 ? "s" : ""} ago`; - } else if (diffInHours > 0) { - relativeTime = `${diffInHours} hour${diffInHours > 1 ? "s" : ""} ago`; - } else if (diffInMinutes > 0) { - relativeTime = `${diffInMinutes} minute${diffInMinutes > 1 ? "s" : ""} ago`; - } else { - relativeTime = "Just now"; - } + const timestampSeconds = parseInt(transfer.timestamp); + const relativeTime = formatRelativeTime(timestampSeconds); return { id: transfer.transactionHash, diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryVariationGraph.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryVariationGraph.tsx index 08739a8bd..2772d3812 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryVariationGraph.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryVariationGraph.tsx @@ -17,13 +17,16 @@ import { BalanceHistoryGraphItem, useBalanceHistoryGraph, } from "@/features/holders-and-delegates/hooks/useBalanceHistoryGraph"; -import { TimePeriodSwitcher } from "@/features/holders-and-delegates/components/TimePeriodSwitcher"; +import { + TimePeriod, + TimePeriodSwitcher, +} from "@/features/holders-and-delegates/components/TimePeriodSwitcher"; import { ChartExceptionState } from "@/shared/components"; import { EnsAvatar } from "@/shared/components/design-system/avatars/ens-avatar/EnsAvatar"; import { AnticaptureWatermark } from "@/shared/components/icons/AnticaptureWatermark"; import { parseAsStringEnum, useQueryState } from "nuqs"; import { useMemo } from "react"; -import { SECONDS_PER_DAY } from "@/shared/constants/time-related"; +import { getTimestampRangeFromPeriod } from "@/features/holders-and-delegates/utils"; interface BalanceHistoryVariationGraphProps { accountId: string; @@ -85,30 +88,13 @@ export const BalanceHistoryVariationGraph = ({ }: BalanceHistoryVariationGraphProps) => { const [selectedPeriod, setSelectedPeriod] = useQueryState( "selectedPeriod", - parseAsStringEnum(["30d", "90d", "all"]).withDefault("all"), + parseAsStringEnum(["30d", "90d", "all"]).withDefault("all"), ); - const fromDate = useMemo(() => { - // For "all", treat as all time by not setting limits - if (selectedPeriod === "all") return undefined; - - // Use start of today for a stable reference that won't change on each render - const today = new Date(); - today.setHours(0, 0, 0, 0); - const todayInSeconds = today.getTime() / 1000; - - let daysInSeconds: number; - switch (selectedPeriod) { - case "90d": - daysInSeconds = 90 * SECONDS_PER_DAY; - break; - default: - daysInSeconds = 30 * SECONDS_PER_DAY; - break; - } - - return Math.floor(todayInSeconds - daysInSeconds); - }, [selectedPeriod]); + const { fromTimestamp: fromDate } = useMemo( + () => getTimestampRangeFromPeriod(selectedPeriod), + [selectedPeriod], + ); const { balanceHistory, loading, error } = useBalanceHistoryGraph( accountId, diff --git a/apps/dashboard/features/holders-and-delegates/utils/formatRelativeTime.ts b/apps/dashboard/features/holders-and-delegates/utils/formatRelativeTime.ts new file mode 100644 index 000000000..8579e7e17 --- /dev/null +++ b/apps/dashboard/features/holders-and-delegates/utils/formatRelativeTime.ts @@ -0,0 +1,37 @@ +export function formatRelativeTime(timestampSeconds: number | string): string { + const timestamp = + typeof timestampSeconds === "string" + ? parseInt(timestampSeconds, 10) + : timestampSeconds; + + const date = new Date(timestamp * 1000); + const now = new Date(); + const diffInMs = now.getTime() - date.getTime(); + const diffInSeconds = Math.floor(diffInMs / 1000); + const diffInMinutes = Math.floor(diffInSeconds / 60); + const diffInHours = Math.floor(diffInMinutes / 60); + const diffInDays = Math.floor(diffInHours / 24); + const diffInWeeks = Math.floor(diffInDays / 7); + const diffInMonths = Math.floor(diffInDays / 30); + const diffInYears = Math.floor(diffInDays / 365); + + if (diffInYears > 0) { + return `${diffInYears} year${diffInYears > 1 ? "s" : ""} ago`; + } + if (diffInMonths > 0) { + return `${diffInMonths} month${diffInMonths > 1 ? "s" : ""} ago`; + } + if (diffInWeeks > 0) { + return `${diffInWeeks} week${diffInWeeks > 1 ? "s" : ""} ago`; + } + if (diffInDays > 0) { + return `${diffInDays} day${diffInDays > 1 ? "s" : ""} ago`; + } + if (diffInHours > 0) { + return `${diffInHours} hour${diffInHours > 1 ? "s" : ""} ago`; + } + if (diffInMinutes > 0) { + return `${diffInMinutes} minute${diffInMinutes > 1 ? "s" : ""} ago`; + } + return "Just now"; +} diff --git a/apps/dashboard/features/holders-and-delegates/utils/index.ts b/apps/dashboard/features/holders-and-delegates/utils/index.ts index 1dba6f210..b78543e12 100644 --- a/apps/dashboard/features/holders-and-delegates/utils/index.ts +++ b/apps/dashboard/features/holders-and-delegates/utils/index.ts @@ -1,2 +1,4 @@ export * from "./constants"; export { getAvgVoteTimingData } from "./proposalsTableUtils"; +export { formatRelativeTime } from "./formatRelativeTime"; +export { getTimestampRangeFromPeriod } from "./timestampUtils"; diff --git a/apps/dashboard/features/holders-and-delegates/utils/timestampUtils.ts b/apps/dashboard/features/holders-and-delegates/utils/timestampUtils.ts new file mode 100644 index 000000000..defc25718 --- /dev/null +++ b/apps/dashboard/features/holders-and-delegates/utils/timestampUtils.ts @@ -0,0 +1,24 @@ +import { TimePeriod } from "@/features/holders-and-delegates/components/TimePeriodSwitcher"; +import { SECONDS_PER_DAY } from "@/shared/constants/time-related"; + +interface TimestampRange { + fromTimestamp: number | undefined; + toTimestamp: number | undefined; +} + +export function getTimestampRangeFromPeriod( + selectedPeriod: TimePeriod, +): TimestampRange { + if (selectedPeriod === "all") { + return { fromTimestamp: undefined, toTimestamp: undefined }; + } + + const nowInSeconds = Date.now() / 1000; + const daysInSeconds = + selectedPeriod === "90d" ? 90 * SECONDS_PER_DAY : 30 * SECONDS_PER_DAY; + + return { + fromTimestamp: Math.floor(nowInSeconds - daysInSeconds), + toTimestamp: Math.floor(nowInSeconds), + }; +} From 526229edd5d9bf3315b6fe567b7c1e8a28653c91 Mon Sep 17 00:00:00 2001 From: Bruno Date: Thu, 29 Jan 2026 20:31:35 -0300 Subject: [PATCH 03/11] feat: enhance ENS avatar loading behavior and display logic --- .../design-system/avatars/ens-avatar/EnsAvatar.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/shared/components/design-system/avatars/ens-avatar/EnsAvatar.tsx b/apps/dashboard/shared/components/design-system/avatars/ens-avatar/EnsAvatar.tsx index e49b48134..7a5e7781f 100644 --- a/apps/dashboard/shared/components/design-system/avatars/ens-avatar/EnsAvatar.tsx +++ b/apps/dashboard/shared/components/design-system/avatars/ens-avatar/EnsAvatar.tsx @@ -104,8 +104,9 @@ export const EnsAvatar = ({ }; const displayName = getDisplayName(); - const isLoadingName = loading || ensLoading; + const isLoadingName = loading || (ensLoading && !address); const isEnsName = Boolean(ensData?.ens); + const isResolvingEns = ensLoading && address; const baseClasses = cn( sizeClasses[size], @@ -115,7 +116,7 @@ export const EnsAvatar = ({ ); const avatarElement = () => { - if (isLoadingName) { + if (loading || (ensLoading && !address)) { return ( From 4d5b60d46ab786feae4ddfd98297c4e12fbc2ddc Mon Sep 17 00:00:00 2001 From: Bruno Date: Fri, 30 Jan 2026 10:25:00 -0300 Subject: [PATCH 04/11] feat: improve loading and no data handling in TopInteractions component --- .../top-interactions/TopInteractions.tsx | 190 ++++++++---------- 1 file changed, 80 insertions(+), 110 deletions(-) diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractions.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractions.tsx index c5f429534..0f2107524 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractions.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractions.tsx @@ -35,6 +35,10 @@ const ChartLegend = ({ ); } + if (items.length === 0) { + return
No interactions found
; + } + return (
{items.map((item) => { @@ -79,131 +83,97 @@ export const TopInteractions = ({ loading: loadingVotingPowerData, } = useAccountInteractionsData({ daoId, address }); - if (!topFive || topFive.length === 0) { - if (loadingVotingPowerData) { - // Show loading skeleton instead of "no interactions found" - return ( -
-
-
-
-
- -
-
-
-

- Net Tokens In/Out (90D) -

- -
-
-
-
-
-
- ); - } - return ( -
+ const variant = netBalanceChange >= 0 ? "positive" : "negative"; + + return ( +
+ {!topFive || (topFive.length === 0 && !loadingVotingPowerData) ? ( -
- ); - } - - const variant = netBalanceChange >= 0 ? "positive" : "negative"; - - return ( -
-
-
-
-
- -
+ ) : ( +
+
+
+
+ {loadingVotingPowerData ? ( + + ) : ( + + )} +
-
-
-

- Net Tokens In/Out -

-
- {!netBalanceChange ? ( - - ) : ( - // this is inverted because is relative to the drawer address - // thus a positive value on the row means the drawer address is sending tokens -

- {netBalanceChange < 0 ? ( - - ) : ( - - )} - {formatNumberUserReadable(Math.abs(netBalanceChange))} -

- )} +
+
+

+ Net Tokens In/Out +

+
+ {!netBalanceChange || loadingVotingPowerData ? ( + + ) : ( + // this is inverted because is relative to the drawer address + // thus a positive value on the row means the drawer address is sending tokens +

+ {netBalanceChange < 0 ? ( + + ) : ( + + )} + {formatNumberUserReadable(Math.abs(netBalanceChange))} +

+ )} +
-
-
+
-
-

- Top Interaction (by aggregated value) -

+
+

+ Top Interaction (by aggregated value) +

-
- {!legendItems || !topFive ? ( - - ) : !topFive ? ( -
- Loading Interactions... -
- ) : topFive && topFive.length > 0 ? ( - - ) : ( -
- No interactions found -
- )} +
+ +
-
+ )}
From 873e09940d0c065d2185bba623cc3ec559174085 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 2 Feb 2026 20:06:33 -0300 Subject: [PATCH 05/11] feat: migrate formatRelativeTime utility to shared utils and update imports --- .../DelegateDelegationHistoryTable.tsx | 2 +- .../balance-history/BalanceHistoryTable.tsx | 2 +- .../utils/formatRelativeTime.ts | 37 ------------- .../holders-and-delegates/utils/index.ts | 1 - .../transactions/utils/transactionsAdapter.ts | 17 +----- .../shared/utils/formatRelativeTime.ts | 53 +++++++++++++++++++ 6 files changed, 56 insertions(+), 56 deletions(-) delete mode 100644 apps/dashboard/features/holders-and-delegates/utils/formatRelativeTime.ts create mode 100644 apps/dashboard/shared/utils/formatRelativeTime.ts diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx index 9571ceb5e..0e9bc1d00 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable.tsx @@ -15,7 +15,6 @@ import { useDelegateDelegationHistory, DelegationHistoryItem, } from "@/features/holders-and-delegates/hooks/useDelegateDelegationHistory"; -import { formatRelativeTime } from "@/features/holders-and-delegates/utils"; import daoConfigByDaoId from "@/shared/dao-config"; import { Table } from "@/shared/components/design-system/table/Table"; import { AmountFilter } from "@/shared/components/design-system/table/filters/amount-filter/AmountFilter"; @@ -29,6 +28,7 @@ import { useQueryState, useQueryStates, } from "nuqs"; +import { formatRelativeTime } from "@/shared/utils/formatRelativeTime"; interface DelegateDelegationHistoryTableProps { accountId: string; diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx index aacda3ebe..8be23d1b1 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx @@ -31,7 +31,7 @@ import { useQueryState, useQueryStates, } from "nuqs"; -import { formatRelativeTime } from "@/features/holders-and-delegates/utils"; +import { formatRelativeTime } from "@/shared/utils/formatRelativeTime"; interface BalanceHistoryData { id: string; diff --git a/apps/dashboard/features/holders-and-delegates/utils/formatRelativeTime.ts b/apps/dashboard/features/holders-and-delegates/utils/formatRelativeTime.ts deleted file mode 100644 index 8579e7e17..000000000 --- a/apps/dashboard/features/holders-and-delegates/utils/formatRelativeTime.ts +++ /dev/null @@ -1,37 +0,0 @@ -export function formatRelativeTime(timestampSeconds: number | string): string { - const timestamp = - typeof timestampSeconds === "string" - ? parseInt(timestampSeconds, 10) - : timestampSeconds; - - const date = new Date(timestamp * 1000); - const now = new Date(); - const diffInMs = now.getTime() - date.getTime(); - const diffInSeconds = Math.floor(diffInMs / 1000); - const diffInMinutes = Math.floor(diffInSeconds / 60); - const diffInHours = Math.floor(diffInMinutes / 60); - const diffInDays = Math.floor(diffInHours / 24); - const diffInWeeks = Math.floor(diffInDays / 7); - const diffInMonths = Math.floor(diffInDays / 30); - const diffInYears = Math.floor(diffInDays / 365); - - if (diffInYears > 0) { - return `${diffInYears} year${diffInYears > 1 ? "s" : ""} ago`; - } - if (diffInMonths > 0) { - return `${diffInMonths} month${diffInMonths > 1 ? "s" : ""} ago`; - } - if (diffInWeeks > 0) { - return `${diffInWeeks} week${diffInWeeks > 1 ? "s" : ""} ago`; - } - if (diffInDays > 0) { - return `${diffInDays} day${diffInDays > 1 ? "s" : ""} ago`; - } - if (diffInHours > 0) { - return `${diffInHours} hour${diffInHours > 1 ? "s" : ""} ago`; - } - if (diffInMinutes > 0) { - return `${diffInMinutes} minute${diffInMinutes > 1 ? "s" : ""} ago`; - } - return "Just now"; -} diff --git a/apps/dashboard/features/holders-and-delegates/utils/index.ts b/apps/dashboard/features/holders-and-delegates/utils/index.ts index b78543e12..2ccdb628c 100644 --- a/apps/dashboard/features/holders-and-delegates/utils/index.ts +++ b/apps/dashboard/features/holders-and-delegates/utils/index.ts @@ -1,4 +1,3 @@ export * from "./constants"; export { getAvgVoteTimingData } from "./proposalsTableUtils"; -export { formatRelativeTime } from "./formatRelativeTime"; export { getTimestampRangeFromPeriod } from "./timestampUtils"; diff --git a/apps/dashboard/features/transactions/utils/transactionsAdapter.ts b/apps/dashboard/features/transactions/utils/transactionsAdapter.ts index c1deb9a60..86a5fead5 100644 --- a/apps/dashboard/features/transactions/utils/transactionsAdapter.ts +++ b/apps/dashboard/features/transactions/utils/transactionsAdapter.ts @@ -2,6 +2,7 @@ import { SupplyType } from "@/shared/components/badges/SupplyLabel"; import { formatNumberUserReadable } from "@/shared/utils"; import { formatUnits } from "viem"; import { TransactionData } from "@/features/transactions/hooks/useTransactionsTableData"; +import { formatRelativeTime } from "@/shared/utils/formatRelativeTime"; export type GraphTransaction = { from: string; @@ -52,22 +53,6 @@ const deduceSupplyTypes = (tx: GraphTransaction): SupplyType[] => { return types; }; -const formatRelativeTime = (timestampSec: string): string => { - const ts = Number(timestampSec); - if (!ts) return ""; - const now = Math.floor(Date.now() / 1000); - const diff = Math.max(0, now - ts); - const minutes = Math.floor(diff / 60); - const hours = Math.floor(diff / 3600); - const days = Math.floor(diff / 86400); - const years = Math.floor(days / 365); - if (years > 0) return `${years} year${years > 1 ? "s" : ""} ago`; - if (days > 0) return `${days} day${days > 1 ? "s" : ""} ago`; - if (hours > 0) return `${hours} hour${hours > 1 ? "s" : ""} ago`; - if (minutes > 0) return `${minutes} min ago`; - return "just now"; -}; - const toBigIntSafe = (val: string | number | undefined | null): bigint => { if (val == null || val === "") return 0n; if (typeof val === "bigint") return val; diff --git a/apps/dashboard/shared/utils/formatRelativeTime.ts b/apps/dashboard/shared/utils/formatRelativeTime.ts new file mode 100644 index 000000000..38bfc54c8 --- /dev/null +++ b/apps/dashboard/shared/utils/formatRelativeTime.ts @@ -0,0 +1,53 @@ +const MILLISECONDS_PER_SECOND = 1000; +const SECONDS_PER_MINUTE = 60; +const MINUTES_PER_HOUR = 60; +const HOURS_PER_DAY = 24; +const DAYS_PER_WEEK = 7; +const DAYS_PER_MONTH = 30; // Approximate - used for display purposes only +const DAYS_PER_YEAR = 365; + +// - Converts timestamp from seconds to milliseconds for Date object +// - Returns granularity from years down to minutes +// - Uses approximate month calculation (30 days) for simplicity +// - Returns "Just now" for timestamps less than a minute ago + +// Example: formatRelativeTime(1706889600) // "2 hours ago" + +export function formatRelativeTime(timestampSeconds: number | string): string { + const timestamp = + typeof timestampSeconds === "string" + ? parseInt(timestampSeconds, 10) + : timestampSeconds; + + const date = new Date(timestamp * MILLISECONDS_PER_SECOND); + const now = new Date(); + const diffInMs = now.getTime() - date.getTime(); + + const diffInSeconds = Math.floor(diffInMs / MILLISECONDS_PER_SECOND); + const diffInMinutes = Math.floor(diffInSeconds / SECONDS_PER_MINUTE); + const diffInHours = Math.floor(diffInMinutes / MINUTES_PER_HOUR); + const diffInDays = Math.floor(diffInHours / HOURS_PER_DAY); + const diffInWeeks = Math.floor(diffInDays / DAYS_PER_WEEK); + const diffInMonths = Math.floor(diffInDays / DAYS_PER_MONTH); + const diffInYears = Math.floor(diffInDays / DAYS_PER_YEAR); + + if (diffInYears > 0) { + return `${diffInYears} year${diffInYears > 1 ? "s" : ""} ago`; + } + if (diffInMonths > 0) { + return `${diffInMonths} month${diffInMonths > 1 ? "s" : ""} ago`; + } + if (diffInWeeks > 0) { + return `${diffInWeeks} week${diffInWeeks > 1 ? "s" : ""} ago`; + } + if (diffInDays > 0) { + return `${diffInDays} day${diffInDays > 1 ? "s" : ""} ago`; + } + if (diffInHours > 0) { + return `${diffInHours} hour${diffInHours > 1 ? "s" : ""} ago`; + } + if (diffInMinutes > 0) { + return `${diffInMinutes} minute${diffInMinutes > 1 ? "s" : ""} ago`; + } + return "Just now"; +} From 96edd28967066164c8b6f5c53eaa6a1717337277 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 2 Feb 2026 20:22:19 -0300 Subject: [PATCH 06/11] feat: implement vote composition table and voting power history features --- .../components/HoldersAndDelegatesDrawer.tsx | 14 ++++++------- .../holders-and-delegates/components/index.ts | 2 +- .../delegate/drawer/index.ts | 8 +++++--- .../ThePieChart.tsx | 2 +- .../VoteComposition.tsx} | 12 +++++------ .../VoteCompositionTable.tsx} | 16 +++++++-------- .../drawer/vote-composition/hooks/index.ts | 2 ++ .../hooks/useVoteCompositionData.ts} | 20 +++++++++---------- .../utils/renderCustomizedLabel.tsx | 0 .../VotingPowerHistory.tsx} | 12 +++++------ .../VotingPowerHistoryTable.tsx} | 6 +++--- .../VotingPowerVariationGraph.tsx | 0 .../drawer/voting-power/hooks/index.ts | 2 -- .../top-interactions/TopInteractionsChart.tsx | 2 +- 14 files changed, 49 insertions(+), 49 deletions(-) rename apps/dashboard/features/holders-and-delegates/delegate/drawer/{voting-power => vote-composition}/ThePieChart.tsx (97%) rename apps/dashboard/features/holders-and-delegates/delegate/drawer/{voting-power/VotingPower.tsx => vote-composition/VoteComposition.tsx} (91%) rename apps/dashboard/features/holders-and-delegates/delegate/drawer/{voting-power/VotingPowerTable.tsx => vote-composition/VoteCompositionTable.tsx} (96%) create mode 100644 apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/hooks/index.ts rename apps/dashboard/features/holders-and-delegates/delegate/drawer/{voting-power/hooks/useVotingPowerData.ts => vote-composition/hooks/useVoteCompositionData.ts} (93%) rename apps/dashboard/features/holders-and-delegates/delegate/drawer/{voting-power => vote-composition}/utils/renderCustomizedLabel.tsx (100%) rename apps/dashboard/features/holders-and-delegates/delegate/drawer/{delegation-history/DelegateDelegationsHistory.tsx => voting-power-history/VotingPowerHistory.tsx} (75%) rename apps/dashboard/features/holders-and-delegates/delegate/drawer/{delegation-history/DelegateDelegationHistoryTable.tsx => voting-power-history/VotingPowerHistoryTable.tsx} (99%) rename apps/dashboard/features/holders-and-delegates/delegate/drawer/{delegation-history => voting-power-history}/VotingPowerVariationGraph.tsx (100%) delete mode 100644 apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/hooks/index.ts diff --git a/apps/dashboard/features/holders-and-delegates/components/HoldersAndDelegatesDrawer.tsx b/apps/dashboard/features/holders-and-delegates/components/HoldersAndDelegatesDrawer.tsx index 9f73a78a0..e3b0936a7 100644 --- a/apps/dashboard/features/holders-and-delegates/components/HoldersAndDelegatesDrawer.tsx +++ b/apps/dashboard/features/holders-and-delegates/components/HoldersAndDelegatesDrawer.tsx @@ -7,9 +7,9 @@ import { cn } from "@/shared/utils"; import { Tabs, TabsList, TabsTrigger } from "@radix-ui/react-tabs"; import { useScreenSize } from "@/shared/hooks"; import { CopyAndPasteButton } from "@/shared/components/buttons/CopyAndPasteButton"; -import { DelegateDelegationsHistory } from "@/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory"; +import { VotingPowerHistory } from "@/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistory"; import { DaoIdEnum } from "@/shared/types/daos"; -import { VotingPower } from "@/features/holders-and-delegates/delegate/drawer/voting-power/VotingPower"; +import { VoteComposition } from "@/features/holders-and-delegates/delegate/drawer/vote-composition/VoteComposition"; import { BalanceHistory } from "@/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistory"; import { DelegationHistoryTable } from "@/features/holders-and-delegates/token-holder/drawer/delegation-history/DelegationHistoryTable"; import { DelegateProposalsActivity } from "@/features/holders-and-delegates/delegate/drawer/votes/DelegateProposalsActivity"; @@ -46,16 +46,14 @@ export const HoldersAndDelegatesDrawer = ({ ), }, { - id: "votingPower", + id: "voteComposition", label: "Vote Composition", - content: , + content: , }, { - id: "delegationHistory", + id: "votingPowerHistory", label: "Voting Power History", - content: ( - - ), + content: , }, ], }, diff --git a/apps/dashboard/features/holders-and-delegates/components/index.ts b/apps/dashboard/features/holders-and-delegates/components/index.ts index 220384cdc..05bff91ae 100644 --- a/apps/dashboard/features/holders-and-delegates/components/index.ts +++ b/apps/dashboard/features/holders-and-delegates/components/index.ts @@ -1,6 +1,6 @@ export * from "@/features/holders-and-delegates/delegate/Delegates"; export * from "@/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistory"; -export * from "@/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable"; +export * from "@/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistoryTable"; export * from "@/features/holders-and-delegates/components/HoldersAndDelegatesDrawer"; export * from "@/features/holders-and-delegates/components/ProgressCircle"; export * from "@/features/holders-and-delegates/components/TabButton"; diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/index.ts b/apps/dashboard/features/holders-and-delegates/delegate/drawer/index.ts index b0a615973..126953520 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/index.ts +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/index.ts @@ -1,3 +1,5 @@ -export * from "@/features/holders-and-delegates/delegate/drawer/voting-power/VotingPower"; -export * from "@/features/holders-and-delegates/delegate/drawer/voting-power/ThePieChart"; -export * from "@/features/holders-and-delegates/delegate/drawer/voting-power/VotingPowerTable"; +export * from "@/features/holders-and-delegates/delegate/drawer/vote-composition/VoteComposition"; +export * from "@/features/holders-and-delegates/delegate/drawer/vote-composition/ThePieChart"; +export * from "@/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable"; +export * from "@/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistory"; +export * from "@/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistoryTable"; diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/ThePieChart.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/ThePieChart.tsx similarity index 97% rename from apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/ThePieChart.tsx rename to apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/ThePieChart.tsx index 85e89e716..38810a52c 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/ThePieChart.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/ThePieChart.tsx @@ -9,7 +9,7 @@ import { TooltipProps, } from "recharts"; import { formatNumberUserReadable } from "@/shared/utils"; -import { renderCustomizedLabel } from "@/features/holders-and-delegates/delegate/drawer/voting-power/utils/renderCustomizedLabel"; +import { renderCustomizedLabel } from "@/features/holders-and-delegates/delegate/drawer/vote-composition/utils/renderCustomizedLabel"; import { SkeletonRow } from "@/shared/components/skeletons/SkeletonRow"; import { AnticaptureWatermark } from "@/shared/components/icons/AnticaptureWatermark"; diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/VotingPower.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteComposition.tsx similarity index 91% rename from apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/VotingPower.tsx rename to apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteComposition.tsx index 778cbbecb..1972af95f 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/VotingPower.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteComposition.tsx @@ -1,11 +1,11 @@ "use client"; -import { ThePieChart } from "@/features/holders-and-delegates/delegate/drawer/voting-power/ThePieChart"; -import { VotingPowerTable } from "@/features/holders-and-delegates/delegate/drawer/voting-power/VotingPowerTable"; +import { ThePieChart } from "@/features/holders-and-delegates/delegate/drawer/vote-composition/ThePieChart"; +import { VoteCompositionTable } from "@/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable"; import { DaoIdEnum } from "@/shared/types/daos"; import { formatNumberUserReadable } from "@/shared/utils"; import { SkeletonRow } from "@/shared/components/skeletons/SkeletonRow"; -import { useVotingPowerData } from "@/features/holders-and-delegates/delegate/drawer/voting-power/hooks/useVotingPowerData"; +import { useVoteCompositionData } from "@/features/holders-and-delegates/delegate/drawer/vote-composition/hooks/useVoteCompositionData"; import { BlankSlate } from "@/shared/components/design-system/blank-slate/BlankSlate"; import { Inbox } from "lucide-react"; @@ -62,7 +62,7 @@ const ChartLegend = ({ ); }; -export const VotingPower = ({ +export const VoteComposition = ({ address, daoId, }: { @@ -76,7 +76,7 @@ export const VotingPower = ({ pieData, chartConfig, loading: loadingVotingPowerData, - } = useVotingPowerData(daoId, address); + } = useVoteCompositionData(daoId, address); if ( !topFiveDelegators || @@ -149,7 +149,7 @@ export const VotingPower = ({
- +
); diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/VotingPowerTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx similarity index 96% rename from apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/VotingPowerTable.tsx rename to apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx index 8c5ee9b92..240a16d7b 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/VotingPowerTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx @@ -16,7 +16,7 @@ import { CopyAndPasteButton } from "@/shared/components/buttons/CopyAndPasteButt import { parseAsStringEnum, useQueryState } from "nuqs"; import { QueryInput_AccountBalances_OrderDirection } from "@anticapture/graphql-client"; -export const VotingPowerTable = ({ +export const VoteCompositionTable = ({ address, daoId, }: { @@ -170,15 +170,15 @@ export const VotingPowerTable = ({ { accessorKey: "date", header: () => { - const handleSortToggle = () => { - const newSortOrder = sortOrder === "desc" ? "asc" : "desc"; - setSortBy("timestamp"); - setSortOrder(newSortOrder); - }; + // const handleSortToggle = () => { + // const newSortOrder = sortOrder === "desc" ? "asc" : "desc"; + // setSortBy("timestamp"); + // setSortOrder(newSortOrder); + // }; return (
Date - + */}
); }, diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/hooks/index.ts b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/hooks/index.ts new file mode 100644 index 000000000..eccb5e005 --- /dev/null +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/hooks/index.ts @@ -0,0 +1,2 @@ +export { useVoteCompositionData } from "./useVoteCompositionData"; +export type { VoteCompositionData } from "./useVoteCompositionData"; diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/hooks/useVotingPowerData.ts b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/hooks/useVoteCompositionData.ts similarity index 93% rename from apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/hooks/useVotingPowerData.ts rename to apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/hooks/useVoteCompositionData.ts index 9c95de230..13a41ee7b 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/hooks/useVotingPowerData.ts +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/hooks/useVoteCompositionData.ts @@ -6,7 +6,7 @@ import { Address, formatUnits } from "viem"; import { formatAddress } from "@/shared/utils/formatAddress"; import daoConfig from "@/shared/dao-config"; -export interface VotingPowerData { +export interface VoteCompositionData { // Dados básicos topFiveDelegators: unknown[]; currentVotingPower: number; @@ -27,15 +27,15 @@ export interface VotingPowerData { } /** - * Hook to get the voting power data for a delegate and pass to VotingPower component and ThePieChart component + * Hook to get the vote composition data for a delegate * @param daoId - The ID of the DAO * @param address - The address of the delegate - * @returns The voting power data + * @returns The vote composition data */ -export const useVotingPowerData = ( +export const useVoteCompositionData = ( daoId: DaoIdEnum, address: string, -): VotingPowerData => { +): VoteCompositionData => { const { decimals, daoOverview: { token }, @@ -56,7 +56,7 @@ export const useVotingPowerData = ( const { data: ensData } = useMultipleEnsData(delegatorAddresses); // default Value when there is no data - const defaultData: VotingPowerData = { + const defaultData: VoteCompositionData = { topFiveDelegators: [], currentVotingPower: 0, loading, @@ -82,9 +82,9 @@ export const useVotingPowerData = ( const delegateCurrentVotingPower = accountPowerVotingPower ? BigInt(accountPowerVotingPower) : topFiveDelegators.reduce( - (acc, item) => acc + BigInt(item.rawBalance), - BigInt(0), - ); + (acc, item) => acc + BigInt(item.rawBalance), + BigInt(0), + ); const currentVotingPowerNumber = Number( token === "ERC20" @@ -119,7 +119,7 @@ export const useVotingPowerData = ( const percentage = Number( (Number(BigInt(delegator.rawBalance)) / Number(delegateCurrentVotingPower)) * - 100, + 100, ); const ensName = ensData?.[delegator.address as Address]?.ens; diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/utils/renderCustomizedLabel.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/utils/renderCustomizedLabel.tsx similarity index 100% rename from apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/utils/renderCustomizedLabel.tsx rename to apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/utils/renderCustomizedLabel.tsx diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistory.tsx similarity index 75% rename from apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory.tsx rename to apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistory.tsx index 8dfdbd87a..6bc01d160 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationsHistory.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistory.tsx @@ -1,20 +1,20 @@ import { DaoIdEnum } from "@/shared/types/daos"; -import { DelegateDelegationHistoryTable } from "@/features/holders-and-delegates/delegate/drawer/delegation-history/DelegateDelegationHistoryTable"; -import { VotingPowerVariationGraph } from "@/features/holders-and-delegates/delegate/drawer/delegation-history/VotingPowerVariationGraph"; +import { VotingPowerHistoryTable } from "@/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistoryTable"; +import { VotingPowerVariationGraph } from "@/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerVariationGraph"; import { parseAsStringEnum, useQueryState } from "nuqs"; import { useMemo } from "react"; import { getTimestampRangeFromPeriod } from "@/features/holders-and-delegates/utils"; import { TimePeriod } from "@/features/holders-and-delegates/components/TimePeriodSwitcher"; -interface DelegateDelegationsHistoryProps { +interface VotingPowerHistoryProps { accountId: string; daoId: DaoIdEnum; } -export const DelegateDelegationsHistory = ({ +export const VotingPowerHistory = ({ accountId, daoId, -}: DelegateDelegationsHistoryProps) => { +}: VotingPowerHistoryProps) => { const [selectedPeriod] = useQueryState( "selectedPeriod", parseAsStringEnum(["30d", "90d", "all"]).withDefault("all"), @@ -34,7 +34,7 @@ export const DelegateDelegationsHistory = ({ {/* Table Section */}
- { +}: VotingPowerHistoryTableProps) => { const { decimals } = daoConfig[daoId]; const [sortBy, setSortBy] = useQueryState( diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/VotingPowerVariationGraph.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerVariationGraph.tsx similarity index 100% rename from apps/dashboard/features/holders-and-delegates/delegate/drawer/delegation-history/VotingPowerVariationGraph.tsx rename to apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerVariationGraph.tsx diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/hooks/index.ts b/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/hooks/index.ts deleted file mode 100644 index 44588c5a0..000000000 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power/hooks/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { useVotingPowerData } from "./useVotingPowerData"; -export type { VotingPowerData } from "./useVotingPowerData"; diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractionsChart.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractionsChart.tsx index 32583f313..15f320922 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractionsChart.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/top-interactions/TopInteractionsChart.tsx @@ -9,7 +9,7 @@ import { TooltipProps, } from "recharts"; import { formatNumberUserReadable } from "@/shared/utils"; -import { renderCustomizedLabel } from "@/features/holders-and-delegates/delegate/drawer/voting-power/utils/renderCustomizedLabel"; +import { renderCustomizedLabel } from "@/features/holders-and-delegates/delegate/drawer/vote-composition/utils/renderCustomizedLabel"; import { SkeletonRow } from "@/shared/components/skeletons/SkeletonRow"; import { AnticaptureWatermark } from "@/shared/components/icons/AnticaptureWatermark"; From 3b24f8fd8706943b1c7dcc4df882d5d52adb32b0 Mon Sep 17 00:00:00 2001 From: Bruno Date: Tue, 3 Feb 2026 12:10:08 -0300 Subject: [PATCH 07/11] feat: enhance formatRelativeTime utility to support skipping --- .../vote-composition/VoteCompositionTable.tsx | 22 ---------- .../VotingPowerHistoryTable.tsx | 4 +- .../balance-history/BalanceHistoryTable.tsx | 4 +- .../design-system/SimpleProgressBar.tsx | 2 +- .../shared/utils/formatRelativeTime.ts | 41 ++++++++++++++----- 5 files changed, 38 insertions(+), 35 deletions(-) diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx index 240a16d7b..e0bee85b0 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx @@ -170,31 +170,9 @@ export const VoteCompositionTable = ({ { accessorKey: "date", header: () => { - // const handleSortToggle = () => { - // const newSortOrder = sortOrder === "desc" ? "asc" : "desc"; - // setSortBy("timestamp"); - // setSortOrder(newSortOrder); - // }; return (
Date - {/* */}
); }, diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistoryTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistoryTable.tsx index 32da8a4d0..797c3af21 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistoryTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistoryTable.tsx @@ -159,7 +159,9 @@ export const VotingPowerHistoryTable = ({ return (
- {formatRelativeTime(timestamp)} + {formatRelativeTime(timestamp, { + skipMonthsAndWeeks: true, + })}
); diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx index 8be23d1b1..230edf919 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx @@ -117,7 +117,9 @@ export const BalanceHistoryTable = ({ const transformedData = useMemo(() => { return transfers.map((transfer) => { const timestampSeconds = parseInt(transfer.timestamp); - const relativeTime = formatRelativeTime(timestampSeconds); + const relativeTime = formatRelativeTime(timestampSeconds, { + skipMonthsAndWeeks: true, + }); return { id: transfer.transactionHash, diff --git a/apps/dashboard/shared/components/design-system/SimpleProgressBar.tsx b/apps/dashboard/shared/components/design-system/SimpleProgressBar.tsx index db92f2363..bbcf5daae 100644 --- a/apps/dashboard/shared/components/design-system/SimpleProgressBar.tsx +++ b/apps/dashboard/shared/components/design-system/SimpleProgressBar.tsx @@ -16,7 +16,7 @@ export const SimpleProgressBar = ({ return (
diff --git a/apps/dashboard/shared/utils/formatRelativeTime.ts b/apps/dashboard/shared/utils/formatRelativeTime.ts index 38bfc54c8..4d2f4bf60 100644 --- a/apps/dashboard/shared/utils/formatRelativeTime.ts +++ b/apps/dashboard/shared/utils/formatRelativeTime.ts @@ -6,14 +6,25 @@ const DAYS_PER_WEEK = 7; const DAYS_PER_MONTH = 30; // Approximate - used for display purposes only const DAYS_PER_YEAR = 365; +interface FormatRelativeTimeOptions { + // If true, skips months and weeks, showing days until 1 year (e.g., "40 days ago", "200 days ago") + skipMonthsAndWeeks?: boolean; +} + // - Converts timestamp from seconds to milliseconds for Date object // - Returns granularity from years down to minutes // - Uses approximate month calculation (30 days) for simplicity // - Returns "Just now" for timestamps less than a minute ago -// Example: formatRelativeTime(1706889600) // "2 hours ago" +// Example: formatRelativeTime(1706889600) // "1 month ago" +// Example: formatRelativeTime(1706889600, { skipMonthsAndWeeks: true }) // "40 days ago" instead of "1 month ago" + +export function formatRelativeTime( + timestampSeconds: number | string, + options: FormatRelativeTimeOptions = {}, +): string { + const { skipMonthsAndWeeks = false } = options; -export function formatRelativeTime(timestampSeconds: number | string): string { const timestamp = typeof timestampSeconds === "string" ? parseInt(timestampSeconds, 10) @@ -34,15 +45,25 @@ export function formatRelativeTime(timestampSeconds: number | string): string { if (diffInYears > 0) { return `${diffInYears} year${diffInYears > 1 ? "s" : ""} ago`; } - if (diffInMonths > 0) { - return `${diffInMonths} month${diffInMonths > 1 ? "s" : ""} ago`; - } - if (diffInWeeks > 0) { - return `${diffInWeeks} week${diffInWeeks > 1 ? "s" : ""} ago`; - } - if (diffInDays > 0) { - return `${diffInDays} day${diffInDays > 1 ? "s" : ""} ago`; + + if (skipMonthsAndWeeks) { + // Skip months and weeks, show days directly + if (diffInDays > 0) { + return `${diffInDays} day${diffInDays > 1 ? "s" : ""} ago`; + } + } else { + // Original behavior with months and weeks + if (diffInMonths > 0) { + return `${diffInMonths} month${diffInMonths > 1 ? "s" : ""} ago`; + } + if (diffInWeeks > 0) { + return `${diffInWeeks} week${diffInWeeks > 1 ? "s" : ""} ago`; + } + if (diffInDays > 0) { + return `${diffInDays} day${diffInDays > 1 ? "s" : ""} ago`; + } } + if (diffInHours > 0) { return `${diffInHours} hour${diffInHours > 1 ? "s" : ""} ago`; } From 25dc4fc4369374c77d8676f91e9a2eaa00b59cf8 Mon Sep 17 00:00:00 2001 From: Bruno Date: Tue, 3 Feb 2026 12:13:06 -0300 Subject: [PATCH 08/11] refactor: remove isNewDelegate property from DelegateTableData and related logic --- .../holders-and-delegates/delegate/Delegates.tsx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/apps/dashboard/features/holders-and-delegates/delegate/Delegates.tsx b/apps/dashboard/features/holders-and-delegates/delegate/Delegates.tsx index 4dc9d605b..746d05122 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/Delegates.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/Delegates.tsx @@ -37,7 +37,6 @@ interface DelegateTableData { variation?: { percentageChange: number; absoluteChange: number; - isNewDelegate: boolean; }; activity?: string | null; activityPercentage?: number | null; @@ -142,10 +141,6 @@ export const Delegates = ({ 100 : null; - const isNewDelegate = - delegate.previousVotingPower === "0" && - BigInt(delegate.votingPower || "0") > BigInt(0); - const avgVoteTiming = getAvgVoteTimingData( delegate.proposalsActivity?.avgTimeBeforeEnd, votingPeriodSeconds, @@ -163,7 +158,6 @@ export const Delegates = ({ absoluteChange: Number( formatUnits(BigInt(delegate.absoluteChange), decimals), ), - isNewDelegate, }, activity, activityPercentage, @@ -297,7 +291,6 @@ export const Delegates = ({ | { percentageChange: number; absoluteChange: number; - isNewDelegate: boolean; } | undefined; @@ -316,11 +309,7 @@ export const Delegates = ({
{(variation?.percentageChange || 0) < 0 ? "-" : ""} {formatNumberUserReadable(Math.abs(variation?.absoluteChange || 0))} - {variation?.isNewDelegate ? ( - New - ) : ( - - )} +
); }, From 63443f5418c4ecc5faa7c60abed28a5ed8417f92 Mon Sep 17 00:00:00 2001 From: Bruno Date: Tue, 3 Feb 2026 12:30:48 -0300 Subject: [PATCH 09/11] feat: add offset parameter to various query arguments and remove outdated comments --- packages/graphql-client/generated.ts | 23 ++++++++++++++++++++--- packages/graphql-client/types.ts | 23 ++++++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/graphql-client/generated.ts b/packages/graphql-client/generated.ts index 5faa3c978..29cc8a82d 100644 --- a/packages/graphql-client/generated.ts +++ b/packages/graphql-client/generated.ts @@ -16,12 +16,9 @@ export type Scalars = { Int: { input: number; output: number; } Float: { input: number; output: number; } BigInt: { input: any; output: any; } - /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ JSON: { input: any; output: any; } - /** Integers that will have a value of 0 or more. */ NonNegativeInt: { input: any; output: any; } ObjMap: { input: any; output: any; } - /** Integers that will have a value greater than 0. */ PositiveInt: { input: any; output: any; } }; @@ -498,6 +495,7 @@ export type QueryVotesOnchainsArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -513,6 +511,7 @@ export type QueryVotingPowerHistorysArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -546,6 +545,12 @@ export type QueryVotingPowersArgs = { toValue?: InputMaybe; }; +export type ViewPageInfo = { + __typename?: 'ViewPageInfo'; + hasNextPage: Scalars['Boolean']['output']; + hasPreviousPage: Scalars['Boolean']['output']; +}; + export type Account = { __typename?: 'account'; balances?: Maybe; @@ -565,6 +570,7 @@ export type AccountBalancesArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -575,6 +581,7 @@ export type AccountDelegatedFromBalancesArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -585,6 +592,7 @@ export type AccountDelegationsFromArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -595,6 +603,7 @@ export type AccountDelegationsToArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -605,6 +614,7 @@ export type AccountPowersArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -615,6 +625,7 @@ export type AccountProposalsArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -625,6 +636,7 @@ export type AccountReceivedTransfersArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -635,6 +647,7 @@ export type AccountSentTransfersArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -645,6 +658,7 @@ export type AccountVotesArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -1421,6 +1435,7 @@ export type ProposalsOnchainVotesArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -2444,6 +2459,7 @@ export type TransactionDelegationsArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -2454,6 +2470,7 @@ export type TransactionTransfersArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; diff --git a/packages/graphql-client/types.ts b/packages/graphql-client/types.ts index e7ad730f0..c9576254f 100644 --- a/packages/graphql-client/types.ts +++ b/packages/graphql-client/types.ts @@ -13,12 +13,9 @@ export type Scalars = { Int: { input: number; output: number; } Float: { input: number; output: number; } BigInt: { input: any; output: any; } - /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ JSON: { input: any; output: any; } - /** Integers that will have a value of 0 or more. */ NonNegativeInt: { input: any; output: any; } ObjMap: { input: any; output: any; } - /** Integers that will have a value greater than 0. */ PositiveInt: { input: any; output: any; } }; @@ -495,6 +492,7 @@ export type QueryVotesOnchainsArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -510,6 +508,7 @@ export type QueryVotingPowerHistorysArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -543,6 +542,12 @@ export type QueryVotingPowersArgs = { toValue?: InputMaybe; }; +export type ViewPageInfo = { + __typename?: 'ViewPageInfo'; + hasNextPage: Scalars['Boolean']['output']; + hasPreviousPage: Scalars['Boolean']['output']; +}; + export type Account = { __typename?: 'account'; balances?: Maybe; @@ -562,6 +567,7 @@ export type AccountBalancesArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -572,6 +578,7 @@ export type AccountDelegatedFromBalancesArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -582,6 +589,7 @@ export type AccountDelegationsFromArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -592,6 +600,7 @@ export type AccountDelegationsToArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -602,6 +611,7 @@ export type AccountPowersArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -612,6 +622,7 @@ export type AccountProposalsArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -622,6 +633,7 @@ export type AccountReceivedTransfersArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -632,6 +644,7 @@ export type AccountSentTransfersArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -642,6 +655,7 @@ export type AccountVotesArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -1418,6 +1432,7 @@ export type ProposalsOnchainVotesArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -2441,6 +2456,7 @@ export type TransactionDelegationsArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; @@ -2451,6 +2467,7 @@ export type TransactionTransfersArgs = { after?: InputMaybe; before?: InputMaybe; limit?: InputMaybe; + offset?: InputMaybe; orderBy?: InputMaybe; orderDirection?: InputMaybe; where?: InputMaybe; From af2ce05787f340957d89495b6df110304606e71d Mon Sep 17 00:00:00 2001 From: Bruno Date: Tue, 3 Feb 2026 21:47:28 -0300 Subject: [PATCH 10/11] feat: enhance VoteComposition and VoteCompositionTable --- .../vote-composition/VoteComposition.tsx | 72 +++++++------------ .../vote-composition/VoteCompositionTable.tsx | 10 +-- .../delegate/drawer/votes/ProposalsTable.tsx | 2 +- .../VotingPowerHistory.tsx | 4 +- .../VotingPowerHistoryTable.tsx | 3 +- 5 files changed, 37 insertions(+), 54 deletions(-) diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteComposition.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteComposition.tsx index f36c54193..a8ca88e16 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteComposition.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteComposition.tsx @@ -37,27 +37,31 @@ const ChartLegend = ({ return (
- {items.map((item) => { - return ( -
- - - {item.label} + {items.length === 0 ? ( +
No delegators found
+ ) : ( + items.map((item) => { + return ( +
- {item.percentage}% + className="rounded-xs size-2" + style={{ backgroundColor: item.color }} + /> + + {item.label} + + {item.percentage}% + - -
- ); - })} +
+ ); + }) + )}
); }; @@ -77,19 +81,6 @@ export const VoteComposition = ({ chartConfig, loading: loadingVotingPowerData, } = useVoteCompositionData(daoId, address); - - if ( - !topFiveDelegators || - (topFiveDelegators.length === 0 && !loadingVotingPowerData) - ) { - return ( - - ); - } return (
@@ -129,19 +120,10 @@ export const VoteComposition = ({

- {!legendItems || !topFiveDelegators ? ( - - ) : !topFiveDelegators ? ( -
- Loading delegators... -
- ) : topFiveDelegators && topFiveDelegators.length > 0 ? ( - - ) : ( -
- No delegators found -
- )} +
diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx index e0bee85b0..68eb4443b 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx @@ -15,6 +15,7 @@ import daoConfig from "@/shared/dao-config"; import { CopyAndPasteButton } from "@/shared/components/buttons/CopyAndPasteButton"; import { parseAsStringEnum, useQueryState } from "nuqs"; import { QueryInput_AccountBalances_OrderDirection } from "@anticapture/graphql-client"; +import { DEFAULT_ITEMS_PER_PAGE } from "@/features/holders-and-delegates/utils"; export const VoteCompositionTable = ({ address, @@ -23,6 +24,7 @@ export const VoteCompositionTable = ({ address: string; daoId: string; }) => { + const limit: number = DEFAULT_ITEMS_PER_PAGE; const [isMounted, setIsMounted] = useState(false); const [sortBy, setSortBy] = useQueryState( "orderBy", @@ -42,6 +44,7 @@ export const VoteCompositionTable = ({ address: address, orderBy: sortBy as "balance" | "timestamp", orderDirection: sortOrder as QueryInput_AccountBalances_OrderDirection, + limit, }); useEffect(() => { @@ -209,19 +212,18 @@ export const VoteCompositionTable = ({ ]; return ( -
+
); diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/ProposalsTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/ProposalsTable.tsx index dd57a0a12..b6ad98f83 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/ProposalsTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/votes/ProposalsTable.tsx @@ -228,7 +228,7 @@ export const ProposalsTable = ({ ); }, header: () => ( -
+
User Vote {userVoteFilterOptions && onUserVoteFilterChange && ( +
{/* Graph Section */}
{/* Table Section */} -
+
{ setIsFilterActive(false); setSortBy("timestamp"); - setSortDirection("desc"); setFilterVariables(() => ({ fromValue: "", toValue: "", @@ -479,7 +478,7 @@ export const VotingPowerHistoryTable = ({ ]; return ( -
+
Date: Wed, 4 Feb 2026 12:23:42 -0300 Subject: [PATCH 11/11] feat: implement DateCell component and update date handling in tables --- .../vote-composition/VoteCompositionTable.tsx | 19 +++--- .../VotingPowerHistoryTable.tsx | 8 +-- .../balance-history/BalanceHistoryTable.tsx | 17 ++---- .../DelegationHistoryTable.tsx | 24 +++----- .../transactions/TransactionsTable.tsx | 2 +- .../hooks/useTransactionsTableData.ts | 2 +- .../utils/getTransactionsColumns.tsx | 11 ++-- .../transactions/utils/transactionsAdapter.ts | 9 ++- .../design-system/table/cells/DateCell.tsx | 31 ++++++++++ .../shared/utils/formatRelativeTime.ts | 58 ++++++++----------- 10 files changed, 91 insertions(+), 90 deletions(-) create mode 100644 apps/dashboard/shared/components/design-system/table/cells/DateCell.tsx diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx index 68eb4443b..af25945c2 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/vote-composition/VoteCompositionTable.tsx @@ -16,6 +16,7 @@ import { CopyAndPasteButton } from "@/shared/components/buttons/CopyAndPasteButt import { parseAsStringEnum, useQueryState } from "nuqs"; import { QueryInput_AccountBalances_OrderDirection } from "@anticapture/graphql-client"; import { DEFAULT_ITEMS_PER_PAGE } from "@/features/holders-and-delegates/utils"; +import { DateCell } from "@/shared/components/design-system/table/cells/DateCell"; export const VoteCompositionTable = ({ address, @@ -55,14 +56,14 @@ export const VoteCompositionTable = ({ return { address: account.address, amount: Number(account.balance) || 0, - date: account.timestamp, + timestamp: account.timestamp, }; }); const columns: ColumnDef<{ address: string; amount: number; - date: string; + timestamp: string; }>[] = [ { accessorKey: "address", @@ -171,7 +172,7 @@ export const VoteCompositionTable = ({ }, }, { - accessorKey: "date", + accessorKey: "timestamp", header: () => { return (
@@ -180,7 +181,7 @@ export const VoteCompositionTable = ({ ); }, cell: ({ row }) => { - const date: string = row.getValue("date"); + const timestamp: string = row.getValue("timestamp"); if (!isMounted || loading) { return ( @@ -194,14 +195,8 @@ export const VoteCompositionTable = ({ } return ( -
- {date - ? new Date(Number(date) * 1000).toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - }) - : "N/A"} +
+ {timestamp ? : "N/A"}
); }, diff --git a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistoryTable.tsx b/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistoryTable.tsx index 5f8aea627..bf3629f0e 100644 --- a/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistoryTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/delegate/drawer/voting-power-history/VotingPowerHistoryTable.tsx @@ -28,8 +28,8 @@ import { useQueryState, useQueryStates, } from "nuqs"; -import { formatRelativeTime } from "@/shared/utils/formatRelativeTime"; import { DEFAULT_ITEMS_PER_PAGE } from "@/features/holders-and-delegates/utils"; +import { DateCell } from "@/shared/components/design-system/table/cells/DateCell"; interface VotingPowerHistoryTableProps { accountId: string; @@ -163,11 +163,7 @@ export const VotingPowerHistoryTable = ({ return (
- - {formatRelativeTime(timestamp, { - skipMonthsAndWeeks: true, - })} - +
); }, diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx index e90d10906..2967016fc 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/balance-history/BalanceHistoryTable.tsx @@ -31,13 +31,13 @@ import { useQueryState, useQueryStates, } from "nuqs"; -import { formatRelativeTime } from "@/shared/utils/formatRelativeTime"; import { DEFAULT_ITEMS_PER_PAGE } from "@/features/holders-and-delegates/utils"; import { useAmountFilterStore } from "@/shared/components/design-system/table/filters/amount-filter/store/amount-filter-store"; +import { DateCell } from "@/shared/components/design-system/table/cells/DateCell"; interface BalanceHistoryData { id: string; - date: string; + timestamp: string; amount: string; type: "Buy" | "Sell"; fromAddress: string; @@ -120,14 +120,9 @@ export const BalanceHistoryTable = ({ // Transform transfers to table data format const transformedData = useMemo(() => { return transfers.map((transfer) => { - const timestampSeconds = parseInt(transfer.timestamp); - const relativeTime = formatRelativeTime(timestampSeconds, { - skipMonthsAndWeeks: true, - }); - return { id: transfer.transactionHash, - date: relativeTime, + timestamp: transfer.timestamp, amount: formatNumberUserReadable(transfer.amount), type: transfer.direction === "in" ? "Buy" : ("Sell" as "Buy" | "Sell"), fromAddress: transfer.fromAccountId, @@ -138,12 +133,12 @@ export const BalanceHistoryTable = ({ const balanceHistoryColumns: ColumnDef[] = [ { - accessorKey: "date", + accessorKey: "timestamp", meta: { columnClassName: "w-32", }, cell: ({ row }) => { - const date = row.getValue("date") as string; + const timestamp = row.getValue("timestamp") as string; if (isInitialLoading) { return ( @@ -158,7 +153,7 @@ export const BalanceHistoryTable = ({ return (
- {date} +
); }, diff --git a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/delegation-history/DelegationHistoryTable.tsx b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/delegation-history/DelegationHistoryTable.tsx index ecab8dd5f..64e31728b 100644 --- a/apps/dashboard/features/holders-and-delegates/token-holder/drawer/delegation-history/DelegationHistoryTable.tsx +++ b/apps/dashboard/features/holders-and-delegates/token-holder/drawer/delegation-history/DelegationHistoryTable.tsx @@ -7,10 +7,7 @@ import { useEffect, useState } from "react"; import { Address, parseUnits } from "viem"; import { useDelegationHistory } from "@/features/holders-and-delegates/hooks/useDelegationHistory"; import { formatUnits } from "viem"; -import { - formatNumberUserReadable, - formatDateUserReadable, -} from "@/shared/utils/"; +import { formatNumberUserReadable } from "@/shared/utils/"; import { ExternalLink } from "lucide-react"; import { ArrowState, ArrowUpDown } from "@/shared/components/icons/ArrowUpDown"; import daoConfigByDaoId from "@/shared/dao-config"; @@ -33,12 +30,12 @@ import { } from "nuqs"; import { DEFAULT_ITEMS_PER_PAGE } from "@/features/holders-and-delegates/utils"; import { useAmountFilterStore } from "@/shared/components/design-system/table/filters/amount-filter/store/amount-filter-store"; +import { DateCell } from "@/shared/components/design-system/table/cells/DateCell"; interface DelegationData { address: string; amount: string; - date: string; - timestamp: number; + timestamp: string; } interface DelegationHistoryTableProps { @@ -107,22 +104,17 @@ export const DelegationHistoryTable = ({ .map((delegation) => { const delegateAddress = delegation.delegateAddress || ""; const delegatedValue = delegation.amount || "0"; - const timestamp = delegation.timestamp || 0; + const timestamp = delegation.timestamp || "0"; const formattedAmount = Number( formatUnits(BigInt(delegatedValue), decimals), ).toFixed(2); - const date = timestamp - ? formatDateUserReadable(new Date(Number(timestamp) * 1000)) - : "Unknown"; - return { address: delegateAddress, amount: formattedAmount, transactionHash: delegation.transactionHash, - date, - timestamp: Number(timestamp), + timestamp: String(timestamp), }; }) || []; @@ -264,7 +256,7 @@ export const DelegationHistoryTable = ({ }, }, { - accessorKey: "date", + accessorKey: "timestamp", header: ({ column }) => { const handleSortToggle = () => { const newSortOrder = sortOrder === "desc" ? "asc" : "desc"; @@ -308,11 +300,11 @@ export const DelegationHistoryTable = ({ ); } - const date: string = row.getValue("date"); + const timestamp: string = row.getValue("timestamp"); return (
- {date} +
); }, diff --git a/apps/dashboard/features/transactions/TransactionsTable.tsx b/apps/dashboard/features/transactions/TransactionsTable.tsx index 5194d55d8..7412aa05e 100644 --- a/apps/dashboard/features/transactions/TransactionsTable.tsx +++ b/apps/dashboard/features/transactions/TransactionsTable.tsx @@ -90,7 +90,7 @@ export const TransactionsTable = ({ id: "loading-row", affectedSupply: ["CEX", "DEX"] as SupplyType[], amount: "1000000", - date: "2 hours ago", + timestamp: "0", from: "0x1234567890abcdef", to: "0xabcdef1234567890", txHash: diff --git a/apps/dashboard/features/transactions/hooks/useTransactionsTableData.ts b/apps/dashboard/features/transactions/hooks/useTransactionsTableData.ts index 960ac987c..f0a144715 100644 --- a/apps/dashboard/features/transactions/hooks/useTransactionsTableData.ts +++ b/apps/dashboard/features/transactions/hooks/useTransactionsTableData.ts @@ -33,7 +33,7 @@ export interface TransactionsFilters extends TransactionsParamsType { export interface TransactionData { id: string; amount: string; - date: string; + timestamp: string; from: string; to: string; affectedSupply?: SupplyType[]; diff --git a/apps/dashboard/features/transactions/utils/getTransactionsColumns.tsx b/apps/dashboard/features/transactions/utils/getTransactionsColumns.tsx index a16a433e7..e7281ea38 100644 --- a/apps/dashboard/features/transactions/utils/getTransactionsColumns.tsx +++ b/apps/dashboard/features/transactions/utils/getTransactionsColumns.tsx @@ -17,6 +17,7 @@ import { Address, zeroAddress } from "viem"; import { EnsAvatar } from "@/shared/components/design-system/avatars/ens-avatar/EnsAvatar"; import { cn } from "@/shared/utils"; import { TransactionsParamsType } from "@/features/transactions/hooks/useTransactionParams"; +import { DateCell } from "@/shared/components/design-system/table/cells/DateCell"; export const getTransactionsColumns = ({ loading, @@ -136,7 +137,7 @@ export const getTransactionsColumns = ({ size: 162, }, { - accessorKey: "date", + accessorKey: "timestamp", header: () => (