Skip to content
Open
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
62 changes: 42 additions & 20 deletions packages/chakra-components/src/components/Election/Results.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, chakra, ChakraProps, Flex, Progress, Text, useMultiStyleConfig } from '@chakra-ui/react'
import { Box, chakra, ChakraProps, Flex, Image, Progress, Text, useMultiStyleConfig } from '@chakra-ui/react'
import { useClient, useDatesLocale, useElection } from '@vocdoni/react-providers'
import {
ElectionResultsTypeNames,
Expand All @@ -9,6 +9,7 @@ import {
PublishedElection,
} from '@vocdoni/sdk'
import { format } from 'date-fns'
import { Markdown } from '../layout'

const percent = (result: number, total: number) => ((Number(result) / total) * 100 || 0).toFixed(1) + '%'
export const results = (result: number, decimals?: number) =>
Expand Down Expand Up @@ -53,25 +54,46 @@ export const ElectionResults = (props: ElectionResultsProps) => {
<Text sx={styles.title}>{localize('results.title', { title: q.title.default })}</Text>
</chakra.div>
<chakra.div sx={styles.body}>
{choices.map((c: any, i: number) => (
<Box key={i}>
{totals && (
<>
<Text sx={styles.choiceTitle}>{c.title.default}</Text>
<Text sx={styles.choiceVotes}>
{localize('results.votes', {
votes: results(c.results, decimals) || 0,
percent: percent(results(c.results, decimals), totals[idx]),
})}
</Text>
<Progress
sx={styles.progress}
value={((Number(c.results) / totals[idx]) * 100) / 10 ** decimals || 0}
/>
</>
)}
</Box>
))}
{choices.map((c: any, i: number) => {
if (!totals) return null

const meta = c.meta ?? {}
const description = meta.description as string | undefined
const imageSrc = meta.image?.default as string | undefined
Comment on lines +60 to +62
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The metadata extraction logic is repeated for each choice in the map function. Consider extracting this into a helper function or defining a proper type for the metadata structure to improve type safety and maintainability.

Copilot uses AI. Check for mistakes.
Copy link
Member

@elboletaire elboletaire Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, @AlexanderArce, you can use a helper method in the SDK named dotobject() which allows you to extract parts of an object using dot notation. We actually have it as a helper for the election class (via election.get()), but I think it does not work for the fields you're trying out here... I think it was implemented only for the custom meta field (which is actually metadata.meta, what you're searching lies under metadata too, so probably won't work the election helper method, but you can still use the underlying dotobject() one for these things).

const hasDescription = !!description
const hasImage = !!imageSrc

return (
<Box key={i}>
<Progress
value={((Number(c.results) / totals[idx]) * 100) / 10 ** decimals || 0}
sx={styles.progress}
/>

<Flex sx={styles.choiceBody}>
<Box flex='1'>
<Flex justify='space-between' mb={hasDescription ? 1 : 0}>
<Text sx={styles.choiceTitle}>{c.title.default}</Text>
<Text sx={styles.choiceVotes}>
{localize('results.votes', {
votes: results(c.results, decimals) || 0,
percent: percent(results(c.results, decimals), totals[idx]),
})}
</Text>
</Flex>

{hasDescription && <Markdown sx={styles.choiceDescription}>{description}</Markdown>}
</Box>

{hasImage && (
<Box sx={styles.choiceImageWrapper}>
<Image src={imageSrc} alt={c.title.default} sx={styles.choiceImage} loading='lazy' />
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alt text uses the choice title directly. Consider providing more descriptive alt text that explains what the image represents, such as 'Image for {title}' or extracting alt text from the metadata if available.

Suggested change
<Image src={imageSrc} alt={c.title.default} sx={styles.choiceImage} loading='lazy' />
<Image src={imageSrc} alt={`Image for ${c.title.default}`} sx={styles.choiceImage} loading='lazy' />

Copilot uses AI. Check for mistakes.
Copy link
Member

@elboletaire elboletaire Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

we should ideally use translations here

</Box>
)}
</Flex>
</Box>
)
})}
</chakra.div>
</chakra.div>
)
Expand Down
32 changes: 32 additions & 0 deletions packages/chakra-components/src/theme/results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export const resultsAnatomy = [
'choiceTitle',
// choice number of votes
'choiceVotes',
// choice body
'choiceBody',
// choice description
'choiceDescription',
// choice image wrapper
'choiceImageWrapper',
// choice image
'choiceImage',
] as const

const { defineMultiStyleConfig, definePartsStyle } = createMultiStyleConfigHelpers(resultsAnatomy)
Expand All @@ -39,6 +47,30 @@ const baseStyle = definePartsStyle({
textAlign: 'center',
fontWeight: 'bold',
},
choiceBody: {
position: 'relative',
px: 3,
py: 2,
align: 'center',
justify: 'space-between',
},
choiceDescription: {
color: 'black',
pl: 2,
},
choiceImageWrapper: {
ml: 4,
flexShrink: 0,
w: ['90px', '130px'],
h: ['60px', '80px'],
borderRadius: 'md',
overflow: 'hidden',
},
choiceImage: {
w: '100%',
h: '100%',
objectFit: 'cover',
},
})

export const ResultsTheme = defineMultiStyleConfig({
Expand Down