diff --git a/src/components/phase-summary/BasePhaseSummary.tsx b/src/components/phase-summary/BasePhaseSummary.tsx index f927ded..2704993 100644 --- a/src/components/phase-summary/BasePhaseSummary.tsx +++ b/src/components/phase-summary/BasePhaseSummary.tsx @@ -11,6 +11,7 @@ interface Props { leftColumn: ReactNode rightColumn: ReactNode proposalList: ReactNode + votesList: ReactNode stats?: ReactNode } @@ -21,6 +22,7 @@ export const BasePhaseSummary: FC = ({ leftColumn, rightColumn, proposalList, + votesList, stats, }) => { return ( @@ -53,6 +55,7 @@ export const BasePhaseSummary: FC = ({ {proposalList} + {votesList} ) } diff --git a/src/components/phase-summary/VotesTable.tsx b/src/components/phase-summary/VotesTable.tsx new file mode 100644 index 0000000..b59e4b5 --- /dev/null +++ b/src/components/phase-summary/VotesTable.tsx @@ -0,0 +1,127 @@ +import type { ForwardRefExoticComponent, RefAttributes } from 'react' +import { Badge } from '@/components/ui/badge' +import { cn } from '@/lib/utils' +import { + CheckCircledIcon, + CheckIcon, + CountdownTimerIcon, + Cross2Icon, + CrossCircledIcon, +} from '@radix-ui/react-icons' +import type { IconProps } from '@radix-ui/react-icons/dist/types' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '../ui/table' +import { Vote, VoteStatus } from '@/types/phase-summary' +import { Card, CardContent, CardHeader, CardTitle } from '../ui/card' + +interface Props { + votes: Vote[] + title: string +} + +interface VotesTableStatus { + value: VoteStatus + icon: ForwardRefExoticComponent> +} + +interface VotesTableDirection { + value: 'FOR' | 'AGAINST' + icon: ForwardRefExoticComponent> +} + +export const votesTableStatuses = [ + { + value: 'Pending', + icon: CountdownTimerIcon, + }, + { + value: 'Orphaned', + icon: CrossCircledIcon, + }, + { + value: 'Canonical', + icon: CheckCircledIcon, + }, +] satisfies VotesTableStatus[] + +export const votesTableDirections = [ + { + value: 'FOR', + icon: CheckIcon, + }, + { + value: 'AGAINST', + icon: Cross2Icon, + }, +] satisfies VotesTableDirection[] + +export const VotesTable = ({ votes, title }: Props) => { + return ( + + + {title} + + + + + + Height + Timestap + Account + Hash + Status + + + + {votes.map(vote => ( + + +
+ {vote.height} +
+
+ +
+ {vote.timestamp} +
+ {/* {topic.reviewerGroups.map(rg => ( + + {rg.reviewerGroup.name} + + ))} */} +
+ +
+ + {vote.account.slice(0, 12) + + '...' + + vote.account.slice(-6)} + +
+
+ +
+ + {vote.hash.slice(0, 12) + '...' + vote.hash.slice(-6)} + +
+
+ +
+ {vote.status} +
+
+
+ ))} +
+
+
+
+ ) +} diff --git a/src/components/phase-summary/VotingPhaseRankedSummary.tsx b/src/components/phase-summary/VotingPhaseRankedSummary.tsx index e0e2d7a..892fef1 100644 --- a/src/components/phase-summary/VotingPhaseRankedSummary.tsx +++ b/src/components/phase-summary/VotingPhaseRankedSummary.tsx @@ -12,6 +12,7 @@ import { PhaseTimeCard } from './PhaseTimeCard' import { StatsCard } from './StatsCard' import { ProposalList } from './ProposalList' import { BudgetDistributionChart } from '../funding-rounds/BudgetDistributionChart' +import { VotesTable } from './VotesTable' interface Props { summary: VotingPhaseRankedSummaryType @@ -92,6 +93,7 @@ export const VotingPhaseRankedSummary: FC = ({ showCommunityVotes={false} /> } + votesList={} /> ) diff --git a/src/services/VotingService.ts b/src/services/VotingService.ts index e14f342..346fd95 100644 --- a/src/services/VotingService.ts +++ b/src/services/VotingService.ts @@ -1,6 +1,7 @@ import { PrismaClient } from '@prisma/client' import { OCVApiService } from './OCVApiService' import { + VoteStatus, type VotingPhaseFundsDistributionSummary, type VotingPhaseRankedSummary, } from '@/types/phase-summary' @@ -132,6 +133,15 @@ export class VotingService { remainingBudget, budgetBreakdown, proposalVotes, + votes: voteData.votes.map(ocvVote => ({ + account: ocvVote.account, + hash: ocvVote.hash, + memo: ocvVote.memo, + height: ocvVote.height, + status: ocvVote.status as VoteStatus, // if necessary, cast status to VoteStatus type + timestamp: ocvVote.timestamp, + nonce: ocvVote.nonce, + })), } } @@ -165,6 +175,7 @@ export class VotingService { // Get ranked votes from OCV API const voteData = await this.ocvService.getRankedVotes(fundingRound.mefId) + console.log('votes', voteData.votes, fundingRound.mefId) // Calculate budget breakdown const budgetBreakdown = fundingRound.proposals.reduce( @@ -247,6 +258,15 @@ export class VotingService { totalVotes: voteData.total_votes, budgetBreakdown, proposalVotes, + votes: voteData.votes.map(ocvVote => ({ + account: ocvVote.account, + hash: ocvVote.hash, + memo: ocvVote.memo, + height: ocvVote.height, + status: ocvVote.status as VoteStatus, + timestamp: ocvVote.timestamp, + nonce: ocvVote.nonce, + })), } } diff --git a/src/types/phase-summary.ts b/src/types/phase-summary.ts index 792ee19..fd23299 100644 --- a/src/types/phase-summary.ts +++ b/src/types/phase-summary.ts @@ -89,6 +89,7 @@ export interface VotingPhaseFundsDistributionSummary extends BasePhaseSummary { notFundedProposals: number totalBudget: number remainingBudget: number + votes: Vote[] } export type RankedVotingProposalVote = ProposalVoteBase & { @@ -101,6 +102,17 @@ export type RankedVotingProposalVote = ProposalVoteBase & { } hasVotes: boolean } +export type VoteStatus = 'Pending' | 'Canonical' | 'Orphaned' + +export interface Vote { + account: string + hash: string + memo: string + height: number + status: VoteStatus + timestamp: number + nonce: number +} export interface VotingPhaseRankedSummary { fundingRoundName: string @@ -113,6 +125,7 @@ export interface VotingPhaseRankedSummary { large: number } proposalVotes: RankedVotingProposalVote[] + votes: Vote[] } export type PhaseStatus = 'not-started' | 'ongoing' | 'ended'