Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added view poll votes modal in web app #839

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RavenPoll } from "@/types/RavenMessaging/RavenPoll"
import { ErrorBanner } from "@/components/layout/AlertBanner"
import { RavenPollOption } from "@/types/RavenMessaging/RavenPollOption"
import { useToast } from "@/hooks/useToast"
import { ViewPollVotes } from "@/components/feature/polls/ViewPollVotes"

interface PollMessageBlockProps extends BoxProps {
message: PollMessage,
Expand Down Expand Up @@ -69,6 +70,7 @@ const PollMessageBox = ({ data, messageID }: { data: Poll, messageID: string })
</>
}
{data.poll.is_disabled ? <Badge color="gray" className={'w-fit'}>Poll is now closed</Badge> : null}
{data.poll.is_anonymous ? null : <ViewPollVotes poll={data} />}
</Flex>
</Flex>
)
Expand Down
123 changes: 123 additions & 0 deletions raven-app/src/components/feature/polls/ViewPollVotes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { useFrappeGetCall } from 'frappe-react-sdk'
import { Poll } from '../chat/ChatMessage/Renderers/PollMessage'
import { useState } from 'react'
import { Button, Dialog, Flex, Separator, Text } from '@radix-ui/themes'
import { DIALOG_CONTENT_CLASS } from '@/utils/layout/dialog'
import { ErrorBanner } from '@/components/layout/AlertBanner'
import { UserAvatar } from '@/components/common/UserAvatar'
import { useGetUser } from '@/hooks/useGetUser'
import clsx from 'clsx'

type VoteData = {
users: string[]
count: number
percentage: number
}

type PollVotesResponse = Record<string, VoteData>

interface ViewPollVotesProps {
poll: Poll
}

export const ViewPollVotes = ({ poll }: ViewPollVotesProps) => {

const [open, setOpen] = useState(false)
const onClose = () => {
setOpen(false)
}

return (
<Dialog.Root open={open} onOpenChange={setOpen}>

<Separator className='w-full' />
<Dialog.Trigger>
<Button variant='ghost' size={'1'} className='-mb-2.5 bg-transparent hover:text-accent-10 w-full'>View Votes</Button>
</Dialog.Trigger>

<Dialog.Content className={clsx(DIALOG_CONTENT_CLASS, 'max-h-[80vh]')}>
<ViewPollVotesModalContent
onClose={onClose}
poll={poll} />
</Dialog.Content>

</Dialog.Root>
)
}

interface ViewPollVotesModalContentProps {
onClose: () => void,
poll: Poll
}

const ViewPollVotesModalContent = ({ onClose, poll }: ViewPollVotesModalContentProps) => {

// fetch poll votes using poll_id
const { data, error } = useFrappeGetCall<{ message: PollVotesResponse }>('raven.api.raven_poll.get_all_votes', {
'poll_id': poll.poll.name,
}, `poll_votes_${poll.poll.name}`, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
})

return (
<Flex direction={'column'} gap={'2'}>
<ErrorBanner error={error} />
{data && data.message && <VotesBlock votesData={data.message} poll={poll} />}
</Flex>
)
}

const VotesBlock = ({ votesData, poll }: { votesData: PollVotesResponse, poll: Poll }) => {
return (
<Flex direction={'column'}>
<Dialog.Title>
<Flex justify={'between'} align={'baseline'}>
<Text>Poll Votes</Text>
<Text size={'2'} color='gray' className='not-cal'>{poll.poll.total_votes} votes</Text>
</Flex>
</Dialog.Title>

<Separator className='w-full' />

<Flex direction={'column'} className='py-4' gap={'2'}>
<Text size={'3'} weight={'bold'}>{poll.poll.question}</Text>
{votesData && Object.keys(votesData).map((opt) => {
const option = votesData[opt]
const optionName = poll.poll.options.find(o => o.name === opt)?.option
return (
<div>
<div key={opt} className='flex items-center justify-between py-2'>
<Flex>
<Text size='1' color='gray'>{optionName}</Text>
<Text size='1' color='gray' className='ml-1'>- {option.percentage.toFixed(2)}%</Text>
</Flex>
<Text size='1' color='gray'>{option.count} vote{option.count > 1 ? 's' : ''}</Text>
</div>
<Flex direction={'column'} gap={'2'} className='bg-gray-100 dark:bg-gray-3 rounded-md py-2 px-2'>
{option.users.map((user) => {
return <div key={user} className='group'>
<UserVote user_id={user} />
</div>
})}
</Flex>
</div>
)
})}
</Flex>
</Flex>
)
}

const UserVote = ({ user_id }: { user_id: string }) => {

const user = useGetUser(user_id)

return <Flex gap={'2'}>
<UserAvatar alt={user?.full_name} src={user?.user_image} size='2' />
<div className='flex flex-col text-ellipsis justify-center border-b border-gray-6 w-full pb-2 group-last:pb-0 group-last:border-b-0'>
<Text as='span' className='block' weight='medium' size='2'>{user?.full_name}</Text>
<Text as='span' size='1' color='gray'>{user?.name}</Text>
</div>
</Flex>
}
Loading