Browse Source

feat: display names of voters in graph's tooltips

develop
Ezerous 4 years ago
parent
commit
0f1c1e3b98
  1. 1
      packages/concordia-app/public/locales/en/translation.json
  2. 17
      packages/concordia-app/src/components/PollView/PollGraph/PollChartBar/index.jsx
  3. 15
      packages/concordia-app/src/components/PollView/PollGraph/PollChartDonut/index.jsx
  4. 22
      packages/concordia-app/src/components/PollView/PollGraph/index.jsx
  5. 6
      packages/concordia-app/src/components/PollView/PollGraph/styles.css
  6. 9
      packages/concordia-app/src/components/PollView/index.jsx
  7. 13
      packages/concordia-contracts/contracts/Forum.sol
  8. 29
      packages/concordia-contracts/contracts/Voting.sol

1
packages/concordia-app/public/locales/en/translation.json

@ -90,6 +90,7 @@
"topic.poll.invalid.data.header": "This topic has a poll but the data are untrusted!", "topic.poll.invalid.data.header": "This topic has a poll but the data are untrusted!",
"topic.poll.invalid.data.sub.header": "The poll data downloaded from the poster have been tampered with.", "topic.poll.invalid.data.sub.header": "The poll data downloaded from the poster have been tampered with.",
"topic.poll.tab.graph.title": "Results", "topic.poll.tab.graph.title": "Results",
"topic.poll.tab.results.vote": "Vote",
"topic.poll.tab.results.votes": "Votes", "topic.poll.tab.results.votes": "Votes",
"topic.poll.tab.results.user.vote": "You voted: ", "topic.poll.tab.results.user.vote": "You voted: ",
"topic.poll.tab.vote.no.changes": "This poll does not allow vote changes.", "topic.poll.tab.vote.no.changes": "This poll does not allow vote changes.",

17
packages/concordia-app/src/components/PollView/PollGraph/PollChartBar/index.jsx

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { CHART_TYPE_BAR } from '../../../../constants/polls/PollGraph'; import { CHART_TYPE_BAR } from '../../../../constants/polls/PollGraph';
const PollChartBar = (props) => { const PollChartBar = (props) => {
const { pollOptions, voteCounts } = props; const { pollOptions, voteCounts, voterNames } = props;
const chartOptions = useMemo(() => ({ const chartOptions = useMemo(() => ({
chart: { chart: {
@ -30,9 +30,20 @@ const PollChartBar = (props) => {
}, },
}, },
tooltip: { tooltip: {
enabled: false, enabled: true,
x: {
show: false,
},
y: {
formatter(value, { seriesIndex }) {
return `<div>${voterNames[seriesIndex].join('</div><div>')}</div>`;
},
title: {
formatter: () => null,
},
},
}, },
}), [pollOptions]); }), [pollOptions, voterNames]);
const chartSeries = useMemo(() => [{ const chartSeries = useMemo(() => [{
name: 'Votes', name: 'Votes',

15
packages/concordia-app/src/components/PollView/PollGraph/PollChartDonut/index.jsx

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { CHART_TYPE_DONUT } from '../../../../constants/polls/PollGraph'; import { CHART_TYPE_DONUT } from '../../../../constants/polls/PollGraph';
const PollChartDonut = (props) => { const PollChartDonut = (props) => {
const { pollOptions, voteCounts } = props; const { pollOptions, voteCounts, voterNames } = props;
const chartOptions = useMemo(() => ({ const chartOptions = useMemo(() => ({
chart: { chart: {
@ -30,12 +30,21 @@ const PollChartDonut = (props) => {
}, },
labels: pollOptions, labels: pollOptions,
tooltip: { tooltip: {
enabled: false, enabled: true,
fillSeriesColor: false,
y: {
formatter(value, { seriesIndex }) {
return `<div>${voterNames[seriesIndex].join('</div><div>')}</div>`;
},
title: {
formatter: () => null,
},
},
}, },
legend: { legend: {
position: 'bottom', position: 'bottom',
}, },
}), [pollOptions]); }), [pollOptions, voterNames]);
return ( return (
<Chart <Chart

22
packages/concordia-app/src/components/PollView/PollGraph/index.jsx

@ -1,4 +1,4 @@
import React, { useMemo } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { Grid, Statistic, Tab } from 'semantic-ui-react'; import { Grid, Statistic, Tab } from 'semantic-ui-react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -7,16 +7,23 @@ import PollChartDonut from './PollChartDonut';
import './styles.css'; import './styles.css';
const PollGraph = (props) => { const PollGraph = (props) => {
const { pollOptions, voteCounts, userVoteIndex } = props; const { pollOptions, userVoteIndex, voteCounts, voterNames } = props;
const [totalVotes, setTotalVotes] = useState(voteCounts.reduce((accumulator, voteCount) => accumulator + voteCount, 0));
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => {
setTotalVotes(voteCounts.reduce((accumulator, voteCount) => accumulator + voteCount, 0));
}, [voteCounts]);
const footer = useMemo(() => ( const footer = useMemo(() => (
<> <>
<Statistic size="mini"> <Statistic size="mini">
<Statistic.Value> <Statistic.Value>
{voteCounts.reduce((accumulator, voteCount) => accumulator + voteCount, 0)} { totalVotes }
</Statistic.Value> </Statistic.Value>
<Statistic.Label>{t('topic.poll.tab.results.votes')}</Statistic.Label> <Statistic.Label>
{ totalVotes !== 1 ? t('topic.poll.tab.results.votes') : t('topic.poll.tab.results.vote') }
</Statistic.Label>
</Statistic> </Statistic>
{userVoteIndex !== -1 {userVoteIndex !== -1
&& ( && (
@ -26,7 +33,7 @@ const PollGraph = (props) => {
</div> </div>
)} )}
</> </>
), [pollOptions, t, userVoteIndex, voteCounts]); ), [pollOptions, t, totalVotes, userVoteIndex]);
const panes = useMemo(() => { const panes = useMemo(() => {
const chartBarPane = ( const chartBarPane = (
@ -38,6 +45,7 @@ const PollGraph = (props) => {
<PollChartBar <PollChartBar
pollOptions={pollOptions} pollOptions={pollOptions}
voteCounts={voteCounts} voteCounts={voteCounts}
voterNames={voterNames}
/> />
</Grid.Column> </Grid.Column>
<Grid.Column /> <Grid.Column />
@ -59,6 +67,7 @@ const PollGraph = (props) => {
<PollChartDonut <PollChartDonut
pollOptions={pollOptions} pollOptions={pollOptions}
voteCounts={voteCounts} voteCounts={voteCounts}
voterNames={voterNames}
/> />
</Grid.Column> </Grid.Column>
<Grid.Column /> <Grid.Column />
@ -76,7 +85,7 @@ const PollGraph = (props) => {
{ menuItem: { key: 'chart-bar', icon: 'chart bar' }, render: () => chartBarPane }, { menuItem: { key: 'chart-bar', icon: 'chart bar' }, render: () => chartBarPane },
{ menuItem: { key: 'chart-donut', icon: 'chart pie' }, render: () => chartDonutPane }, { menuItem: { key: 'chart-donut', icon: 'chart pie' }, render: () => chartDonutPane },
]); ]);
}, [footer, pollOptions, voteCounts]); }, [footer, pollOptions, voteCounts, voterNames]);
return ( return (
<Tab <Tab
@ -93,6 +102,7 @@ PollGraph.defaultProps = {
PollGraph.propTypes = { PollGraph.propTypes = {
pollOptions: PropTypes.arrayOf(PropTypes.string).isRequired, pollOptions: PropTypes.arrayOf(PropTypes.string).isRequired,
voteCounts: PropTypes.arrayOf(PropTypes.number).isRequired, voteCounts: PropTypes.arrayOf(PropTypes.number).isRequired,
voterNames: PropTypes.arrayOf(PropTypes.array).isRequired,
userVoteIndex: PropTypes.number, userVoteIndex: PropTypes.number,
}; };

6
packages/concordia-app/src/components/PollView/PollGraph/styles.css

@ -2,3 +2,9 @@
box-shadow: none; box-shadow: none;
border: none; border: none;
} }
.apexcharts-tooltip {
border: 1px solid #ddd !important;
background-color: white !important;
color: black !important;
}

9
packages/concordia-app/src/components/PollView/index.jsx

@ -44,6 +44,7 @@ const PollView = (props) => {
const [pollOptions, setPollOptions] = useState([]); const [pollOptions, setPollOptions] = useState([]);
const [voteCounts, setVoteCounts] = useState([]); const [voteCounts, setVoteCounts] = useState([]);
const [voters, setVoters] = useState([]); const [voters, setVoters] = useState([]);
const [voterNames, setVoterNames] = useState([]);
const [pollHashValid, setPollHashValid] = useState(true); const [pollHashValid, setPollHashValid] = useState(true);
const [pollQuestion, setPollQuestion] = useState(''); const [pollQuestion, setPollQuestion] = useState('');
const [chainDataLoading, setChainDataLoading] = useState(true); const [chainDataLoading, setChainDataLoading] = useState(true);
@ -84,6 +85,11 @@ const PollView = (props) => {
.slice(index > 0 ? cumulativeSum[index - 1] : 0, .slice(index > 0 ? cumulativeSum[index - 1] : 0,
subArrayEnd))); subArrayEnd)));
setVoterNames(cumulativeSum
.map((subArrayEnd, index) => pollResults.value[6]
.slice(index > 0 ? cumulativeSum[index - 1] : 0,
subArrayEnd)));
setChainDataLoading(false); setChainDataLoading(false);
} }
}, [getPollCallHash, getPollResults]); }, [getPollCallHash, getPollResults]);
@ -148,10 +154,11 @@ const PollView = (props) => {
pollOptions={pollOptions} pollOptions={pollOptions}
voteCounts={voteCounts} voteCounts={voteCounts}
userVoteIndex={userVoteIndex} userVoteIndex={userVoteIndex}
voterNames={voterNames}
/> />
) )
: null : null
), [chainDataLoading, orbitDataLoading, pollOptions, userVoteIndex, voteCounts]); ), [chainDataLoading, orbitDataLoading, pollOptions, userVoteIndex, voteCounts, voterNames]);
const panes = useMemo(() => { const panes = useMemo(() => {
const pollVotePane = ( const pollVotePane = (

13
packages/concordia-contracts/contracts/Forum.sol

@ -3,6 +3,7 @@ pragma solidity 0.8.1;
contract Forum { contract Forum {
// Error messages for require() // Error messages for require()
string public constant USER_HAS_SIGNED_UP = "User has already signed up.";
string public constant USER_HAS_NOT_SIGNED_UP = "User hasn't signed up yet."; string public constant USER_HAS_NOT_SIGNED_UP = "User hasn't signed up yet.";
string public constant USERNAME_TAKEN = "Username is already taken."; string public constant USERNAME_TAKEN = "Username is already taken.";
string public constant TOPIC_DOES_NOT_EXIST = "Topic doesn't exist."; string public constant TOPIC_DOES_NOT_EXIST = "Topic doesn't exist.";
@ -28,7 +29,7 @@ contract Forum {
event UsernameUpdated(string newName, string oldName, address userAddress); event UsernameUpdated(string newName, string oldName, address userAddress);
function signUp(string memory username) public returns (bool) { function signUp(string memory username) public returns (bool) {
require(!hasUserSignedUp(msg.sender), USER_HAS_NOT_SIGNED_UP); require(!hasUserSignedUp(msg.sender), USER_HAS_SIGNED_UP);
require(!isUserNameTaken(username), USERNAME_TAKEN); require(!isUserNameTaken(username), USERNAME_TAKEN);
users[msg.sender] = User(username, new uint[](0), new uint[](0), block.timestamp, true); users[msg.sender] = User(username, new uint[](0), new uint[](0), block.timestamp, true);
usernameAddresses[username] = msg.sender; usernameAddresses[username] = msg.sender;
@ -118,6 +119,16 @@ contract Forum {
return userAddresses; return userAddresses;
} }
function getUsernames(address[] memory userAddressesArray) public view returns (string[] memory) {
string[] memory usernamesArray = new string[](userAddressesArray.length);
for (uint i = 0; i < userAddressesArray.length; i++) {
usernamesArray[i] = getUsername(userAddressesArray[i]);
}
return usernamesArray;
}
//----------------------------------------POSTING---------------------------------------- //----------------------------------------POSTING----------------------------------------
struct Topic { struct Topic {
uint topicID; uint topicID;

29
packages/concordia-contracts/contracts/Voting.sol

@ -68,26 +68,29 @@ contract Voting {
); );
} }
function getPoll(uint topicID) public view returns (uint, string memory, bool, uint, uint[] memory, address[] memory, uint) { function getPoll(uint topicID) public view
returns (uint, string memory, bool, uint, uint[] memory, address[] memory, string[] memory) {
require(pollExists(topicID), POLL_DOES_NOT_EXIST); require(pollExists(topicID), POLL_DOES_NOT_EXIST);
uint totalVotes = getTotalVotes(topicID);
uint[] memory voteCounts = getVoteCounts(topicID); uint[] memory voteCounts = getVoteCounts(topicID);
address[] memory voters = getSerializedVoters(topicID, voteCounts, totalVotes); address[] memory voters = getSerializedVoters(topicID, voteCounts);
string[] memory voterNames = forum.getUsernames(voters);
Poll storage poll = polls[topicID];
return ( return (
polls[topicID].numOptions, poll.numOptions,
polls[topicID].dataHash, poll.dataHash,
polls[topicID].enableVoteChanges, poll.enableVoteChanges,
polls[topicID].timestamp, poll.timestamp,
voteCounts, voteCounts,
voters, voters,
totalVotes voterNames
); );
} }
function getSerializedVoters(uint topicID, uint[] memory voteCounts, uint totalVotes) private view returns (address[] memory) { function getSerializedVoters(uint topicID, uint[] memory voteCounts) private view returns (address[] memory) {
uint totalVotes = getTotalVotes(topicID);
address[] memory voters = new address[](totalVotes); address[] memory voters = new address[](totalVotes);
uint serializationIndex = 0; uint serializationIndex = 0;
@ -99,7 +102,7 @@ contract Voting {
} }
} }
return (voters); return voters;
} }
function isOptionValid(uint topicID, uint option) private view returns (bool) { function isOptionValid(uint topicID, uint option) private view returns (bool) {

Loading…
Cancel
Save