mirror of https://gitlab.com/ecentrics/concordia
Apostolos Fanakis
4 years ago
9 changed files with 387 additions and 9 deletions
@ -0,0 +1,82 @@ |
|||||
|
import React, { useMemo } from 'react'; |
||||
|
import Chart from 'react-apexcharts'; |
||||
|
import { Grid, Header } from 'semantic-ui-react'; |
||||
|
import PropTypes from 'prop-types'; |
||||
|
import { useTranslation } from 'react-i18next'; |
||||
|
import { CASTED_OPTION_COLOR, DEFAULT_OPTION_COLOR } from '../../../constants/polls/PollGraph'; |
||||
|
|
||||
|
const PollGraph = (props) => { |
||||
|
const { |
||||
|
pollOptions, voteCounts, hasUserVoted, userVoteHash, |
||||
|
} = props; |
||||
|
const { t } = useTranslation(); |
||||
|
|
||||
|
const chartOptions = useMemo(() => ({ |
||||
|
chart: { |
||||
|
id: 'topic-poll', |
||||
|
}, |
||||
|
plotOptions: { |
||||
|
bar: { |
||||
|
horizontal: true, |
||||
|
}, |
||||
|
}, |
||||
|
colors: [ |
||||
|
(value) => { |
||||
|
if (hasUserVoted && pollOptions[value.dataPointIndex].hash === userVoteHash) { |
||||
|
return CASTED_OPTION_COLOR; |
||||
|
} |
||||
|
return DEFAULT_OPTION_COLOR; |
||||
|
}, |
||||
|
], |
||||
|
xaxis: { |
||||
|
categories: pollOptions.map((pollOption) => pollOption.label), |
||||
|
}, |
||||
|
}), [hasUserVoted, pollOptions, userVoteHash]); |
||||
|
|
||||
|
const chartSeries = useMemo(() => [{ |
||||
|
name: 'votes', |
||||
|
data: voteCounts, |
||||
|
}], [voteCounts]); |
||||
|
|
||||
|
return ( |
||||
|
<Grid columns="equal"> |
||||
|
<Grid.Row> |
||||
|
<Grid.Column /> |
||||
|
<Grid.Column width={8}> |
||||
|
<Chart |
||||
|
options={chartOptions} |
||||
|
series={chartSeries} |
||||
|
type="bar" |
||||
|
/> |
||||
|
</Grid.Column> |
||||
|
<Grid.Column /> |
||||
|
</Grid.Row> |
||||
|
<Grid.Row> |
||||
|
<Grid.Column textAlign="center"> |
||||
|
<Header as="h4"> |
||||
|
{t('topic.poll.tab.results.votes.count', { |
||||
|
totalVotes: voteCounts.reduce((accumulator, voteCount) => accumulator + voteCount, 0), |
||||
|
})} |
||||
|
</Header> |
||||
|
</Grid.Column> |
||||
|
</Grid.Row> |
||||
|
</Grid> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
PollGraph.defaultProps = { |
||||
|
hasUserVoted: false, |
||||
|
userVoteHash: '', |
||||
|
}; |
||||
|
|
||||
|
PollGraph.propTypes = { |
||||
|
pollOptions: PropTypes.arrayOf(PropTypes.exact({ |
||||
|
label: PropTypes.string, |
||||
|
hash: PropTypes.string, |
||||
|
})).isRequired, |
||||
|
voteCounts: PropTypes.arrayOf(PropTypes.number).isRequired, |
||||
|
hasUserVoted: PropTypes.bool, |
||||
|
userVoteHash: PropTypes.string, |
||||
|
}; |
||||
|
|
||||
|
export default PollGraph; |
@ -0,0 +1,62 @@ |
|||||
|
import React, { useState } from 'react'; |
||||
|
import PropTypes from 'prop-types'; |
||||
|
import { Form } from 'semantic-ui-react'; |
||||
|
import { useTranslation } from 'react-i18next'; |
||||
|
|
||||
|
const PollVote = (props) => { |
||||
|
const { |
||||
|
pollOptions, enableVoteChanges, hasUserVoted, userVoteHash, |
||||
|
} = props; |
||||
|
const [selectedOptionHash, setSelectedOptionHash] = useState(userVoteHash); |
||||
|
const { t } = useTranslation(); |
||||
|
|
||||
|
const onOptionSelected = (e, { value }) => { |
||||
|
setSelectedOptionHash(value); |
||||
|
}; |
||||
|
|
||||
|
const onCastVote = () => { |
||||
|
console.log('vote'); |
||||
|
// TODO |
||||
|
// TODO: callback for immediate poll data refresh on vote? |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<Form onSubmit={onCastVote}> |
||||
|
<Form.Group grouped> |
||||
|
<label htmlFor="poll">{t('topic.poll.tab.vote.form.radio.label')}</label> |
||||
|
{pollOptions.map((pollOption) => ( |
||||
|
<Form.Radio |
||||
|
key={pollOption.hash} |
||||
|
label={pollOption.label} |
||||
|
value={pollOption.hash} |
||||
|
checked={pollOption.hash === selectedOptionHash} |
||||
|
disabled={hasUserVoted && !enableVoteChanges && pollOption.hash !== selectedOptionHash} |
||||
|
onChange={onOptionSelected} |
||||
|
/> |
||||
|
))} |
||||
|
</Form.Group> |
||||
|
<Form.Button |
||||
|
type="submit" |
||||
|
disabled={(hasUserVoted && !enableVoteChanges) || (selectedOptionHash === userVoteHash)} |
||||
|
> |
||||
|
{t('topic.poll.tab.vote.form.button.submit')} |
||||
|
</Form.Button> |
||||
|
</Form> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
PollVote.defaultProps = { |
||||
|
userVoteHash: '', |
||||
|
}; |
||||
|
|
||||
|
PollVote.propTypes = { |
||||
|
pollOptions: PropTypes.arrayOf(PropTypes.exact({ |
||||
|
label: PropTypes.string, |
||||
|
hash: PropTypes.string, |
||||
|
})).isRequired, |
||||
|
enableVoteChanges: PropTypes.bool.isRequired, |
||||
|
hasUserVoted: PropTypes.bool.isRequired, |
||||
|
userVoteHash: PropTypes.string, |
||||
|
}; |
||||
|
|
||||
|
export default PollVote; |
@ -0,0 +1,99 @@ |
|||||
|
import React, { useMemo, useState } from 'react'; |
||||
|
import { useSelector } from 'react-redux'; |
||||
|
import { VOTING_CONTRACT } from 'concordia-shared/src/constants/contracts/ContractNames'; |
||||
|
import { |
||||
|
Container, Header, Icon, Tab, |
||||
|
} from 'semantic-ui-react'; |
||||
|
import { useTranslation } from 'react-i18next'; |
||||
|
import { drizzle } from '../../redux/store'; |
||||
|
import PollGraph from './PollGraph'; |
||||
|
import CustomLoadingTabPane from '../CustomLoadingTabPane'; |
||||
|
import { GRAPH_TAB, VOTE_TAB } from '../../constants/polls/PollTabs'; |
||||
|
import PollVote from './PollVote'; |
||||
|
|
||||
|
const { contracts: { [VOTING_CONTRACT]: { methods: { pollExists: { cacheCall: pollExistsChainData } } } } } = drizzle; |
||||
|
|
||||
|
const hashOption = (val) => { |
||||
|
let hash = 0; |
||||
|
let i; |
||||
|
let chr; |
||||
|
|
||||
|
for (i = 0; i < val.length; i++) { |
||||
|
chr = val.charCodeAt(i); |
||||
|
hash = ((hash << 5) - hash) + chr; |
||||
|
hash |= 0; |
||||
|
} |
||||
|
|
||||
|
return `${hash}`; |
||||
|
}; |
||||
|
|
||||
|
const PollView = (props) => { |
||||
|
const { topicId } = props; |
||||
|
const userAddress = useSelector((state) => state.user.address); |
||||
|
const hasSignedUp = useSelector((state) => state.user.hasSignedUp); |
||||
|
const getPollInfoResults = useSelector((state) => state.contracts[VOTING_CONTRACT].getPollInfo); |
||||
|
const [getPollInfoCallHash, setGetPollInfoCallHash] = useState(null); |
||||
|
const [pollOptions, setPollOptions] = useState([ |
||||
|
{ |
||||
|
label: 'option 1', |
||||
|
hash: hashOption('option 1'), |
||||
|
}, |
||||
|
{ |
||||
|
label: 'option 2', |
||||
|
hash: hashOption('option 2'), |
||||
|
}, |
||||
|
{ |
||||
|
label: 'option 3', |
||||
|
hash: hashOption('option 3'), |
||||
|
}, |
||||
|
{ |
||||
|
label: 'option 4', |
||||
|
hash: hashOption('option 4'), |
||||
|
}, |
||||
|
{ |
||||
|
label: 'option 5', |
||||
|
hash: hashOption('option 5'), |
||||
|
}, |
||||
|
|
||||
|
]); |
||||
|
const [voteCounts, setVoteCounts] = useState([2, 8, 4, 12, 7]); |
||||
|
const [voteLoading, setVoteLoading] = useState(false); |
||||
|
const [resultsLoading, setResultsLoading] = useState(false); |
||||
|
const { t } = useTranslation(); |
||||
|
|
||||
|
// TODO: get vote options |
||||
|
|
||||
|
// TODO: get current results |
||||
|
|
||||
|
// TODO: check poll hash validity, add invalid view |
||||
|
|
||||
|
const pollVoteTab = useMemo(() => ( |
||||
|
<PollVote pollOptions={pollOptions} enableVoteChanges hasUserVoted userVoteHash={pollOptions[2].hash} /> |
||||
|
), [pollOptions]); |
||||
|
|
||||
|
const pollGraphTab = useMemo(() => ( |
||||
|
<PollGraph pollOptions={pollOptions} voteCounts={voteCounts} hasUserVoted userVoteHash={pollOptions[2].hash} /> |
||||
|
), [pollOptions, voteCounts]); |
||||
|
|
||||
|
const panes = useMemo(() => { |
||||
|
const pollVotePane = (<CustomLoadingTabPane loading={voteLoading}>{pollVoteTab}</CustomLoadingTabPane>); |
||||
|
const pollGraphPane = (<CustomLoadingTabPane loading={resultsLoading}>{pollGraphTab}</CustomLoadingTabPane>); |
||||
|
|
||||
|
return ([ |
||||
|
{ menuItem: t(VOTE_TAB.intl_display_name_id), render: () => pollVotePane }, |
||||
|
{ menuItem: t(GRAPH_TAB.intl_display_name_id), render: () => pollGraphPane }, |
||||
|
]); |
||||
|
}, [pollGraphTab, pollVoteTab, resultsLoading, t, voteLoading]); |
||||
|
|
||||
|
return ( |
||||
|
<Container id="topic-poll-container" textAlign="left"> |
||||
|
<Header as="h3"> |
||||
|
<Icon name="chart pie" size="large" /> |
||||
|
Do you thing asdf or fdsa? |
||||
|
</Header> |
||||
|
<Tab panes={panes} /> |
||||
|
</Container> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default PollView; |
@ -0,0 +1,2 @@ |
|||||
|
export const DEFAULT_OPTION_COLOR = '#3B5066'; |
||||
|
export const CASTED_OPTION_COLOR = '#0b2540'; |
@ -0,0 +1,16 @@ |
|||||
|
export const VOTE_TAB = { |
||||
|
id: 'vote-tab', |
||||
|
intl_display_name_id: 'topic.poll.vote.tab.title', |
||||
|
}; |
||||
|
|
||||
|
export const GRAPH_TAB = { |
||||
|
id: 'graph-tab', |
||||
|
intl_display_name_id: 'topic.poll.graph.tab.title', |
||||
|
}; |
||||
|
|
||||
|
const pollTabs = [ |
||||
|
VOTE_TAB, |
||||
|
GRAPH_TAB, |
||||
|
]; |
||||
|
|
||||
|
export default pollTabs; |
Loading…
Reference in new issue