Moved vote cards in own component files

This commit is contained in:
Arnaud Vergnet 2020-04-07 15:24:52 +02:00
parent 34594f82d9
commit 9fc3620044
8 changed files with 443 additions and 299 deletions

View file

@ -48,7 +48,7 @@ class AuthenticatedScreen extends React.Component<Props, State> {
this.setState({loading: true}); this.setState({loading: true});
if (this.connectionManager.isLoggedIn()) { if (this.connectionManager.isLoggedIn()) {
for (let i = 0; i < this.props.links.length; i++) { for (let i = 0; i < this.props.links.length; i++) {
this.connectionManager.authenticatedRequest(this.props.links[i].link) this.connectionManager.authenticatedRequest(this.props.links[i].link, null, null)
.then((data) => { .then((data) => {
this.onFinishedLoading(data, i, -1); this.onFinishedLoading(data, i, -1);
}) })

View file

@ -0,0 +1,104 @@
// @flow
import * as React from 'react';
import {Avatar, Card, List, ProgressBar, Subheading, withTheme} from "react-native-paper";
import {FlatList, StyleSheet} from "react-native";
type Props = {
teams: Array<Object>,
dateEnd: string,
}
class VoteResults extends React.Component<Props> {
totalVotes: number;
winnerId: number;
colors: Object;
constructor(props) {
super();
this.colors = props.theme.colors;
props.teams.sort(this.sortByVotes);
this.getTotalVotes(props.teams);
this.getWinnerId(props.teams);
}
shouldComponentUpdate() {
return false;
}
sortByVotes = (a: Object, b: Object) => b.votes - a.votes;
getTotalVotes(teams: Array<Object>) {
this.totalVotes = 0;
for (let i = 0; i < teams.length; i++) {
this.totalVotes += teams[i].votes;
}
}
getWinnerId(teams: Array<Object>) {
this.winnerId = teams[0].id;
}
voteKeyExtractor = (item: Object) => item.id.toString();
resultRenderItem = ({item}: Object) => {
const isWinner = this.winnerId === item.id;
return (
<Card style={{
marginTop: 10,
elevation: isWinner ? 5 : 3,
}}>
<List.Item
title={item.name}
description={item.votes + " VOTES"}
left={props => isWinner
? <List.Icon {...props} icon="trophy" color={this.colors.primary}/>
: null}
titleStyle={{
color: isWinner
? this.colors.primary
: this.colors.text
}}
style={{padding: 0}}
/>
<ProgressBar progress={item.votes / this.totalVotes} color={this.colors.primary}/>
</Card>
);
};
render() {
return (
<Card style={styles.card}>
<Card.Title
title={"RESULTS"}
subtitle={"AVAILABLE UNTIL " + this.props.dateEnd}
left={(props) => <Avatar.Icon
{...props}
icon={"podium-gold"}
/>}
/>
<Card.Content>
<Subheading>TOTAL VOTES : {this.totalVotes}</Subheading>
{/*$FlowFixMe*/}
<FlatList
data={this.props.teams}
keyExtractor={this.voteKeyExtractor}
renderItem={this.resultRenderItem}
/>
</Card.Content>
</Card>
);
}
}
const styles = StyleSheet.create({
card: {
margin: 10,
},
icon: {
backgroundColor: 'transparent'
},
});
export default withTheme(VoteResults);

View file

@ -0,0 +1,135 @@
// @flow
import * as React from 'react';
import {Avatar, Button, Card, RadioButton} from "react-native-paper";
import {FlatList, StyleSheet, View} from "react-native";
import ConnectionManager from "../../../managers/ConnectionManager";
import LoadingConfirmDialog from "../../Dialog/LoadingConfirmDialog";
import ErrorDialog from "../../Dialog/ErrorDialog";
type Props = {
teams: Array<Object>,
onVoteSuccess: Function,
onVoteError: Function,
}
type State = {
selectedTeam: string,
voteDialogVisible: boolean,
errorDialogVisible: boolean,
currentError: number,
}
export default class VoteSelect extends React.PureComponent<Props, State> {
state = {
selectedTeam: "none",
voteDialogVisible: false,
errorDialogVisible: false,
currentError: 0,
};
onVoteSelectionChange = (team: string) => this.setState({selectedTeam: team});
voteKeyExtractor = (item: Object) => item.id.toString();
voteRenderItem = ({item}: Object) => <RadioButton.Item label={item.name} value={item.id.toString()}/>;
showVoteDialog = () => this.setState({voteDialogVisible: true});
onVoteDialogDismiss = () => this.setState({voteDialogVisible: false,});
onVoteDialogAccept = async () => {
return new Promise((resolve, reject) => {
ConnectionManager.getInstance().authenticatedRequest(
"elections/vote",
["vote"],
[parseInt(this.state.selectedTeam)])
.then(() => {
this.onVoteDialogDismiss();
this.props.onVoteSuccess();
resolve();
})
.catch((error: number) => {
this.onVoteDialogDismiss();
this.showErrorDialog(error);
resolve();
});
});
};
showErrorDialog = (error: number) => this.setState({
errorDialogVisible: true,
currentError: error,
});
onErrorDialogDismiss = () => {
this.setState({errorDialogVisible: false});
this.props.onVoteError();
};
render() {
return (
<View>
<Card style={styles.card}>
<Card.Title
title={"VOTE OPEN"}
subtitle={"VOTE NOW"}
left={(props) => <Avatar.Icon
{...props}
icon={"alert-decagram"}
/>}
/>
<Card.Content>
<RadioButton.Group
onValueChange={this.onVoteSelectionChange}
value={this.state.selectedTeam}
>
{/*$FlowFixMe*/}
<FlatList
data={this.props.teams}
keyExtractor={this.voteKeyExtractor}
extraData={this.state.selectedTeam}
renderItem={this.voteRenderItem}
/>
</RadioButton.Group>
</Card.Content>
<Card.Actions>
<Button
icon="send"
mode="contained"
onPress={this.showVoteDialog}
style={{marginLeft: 'auto'}}
disabled={this.state.selectedTeam === "none"}
>
SEND VOTE
</Button>
</Card.Actions>
</Card>
<LoadingConfirmDialog
visible={this.state.voteDialogVisible}
onDismiss={this.onVoteDialogDismiss}
onAccept={this.onVoteDialogAccept}
title={"VOTE?"}
titleLoading={"SENDING VOTE..."}
message={"SURE?"}
/>
<ErrorDialog
visible={this.state.errorDialogVisible}
onDismiss={this.onErrorDialogDismiss}
errorCode={this.state.currentError}
/>
</View>
);
}
}
const styles = StyleSheet.create({
card: {
margin: 10,
},
icon: {
backgroundColor: 'transparent'
},
});

View file

@ -0,0 +1,45 @@
// @flow
import * as React from 'react';
import {Avatar, Card, Paragraph} from "react-native-paper";
import {StyleSheet} from "react-native";
type Props = {
startDate: string,
}
export default class VoteTease extends React.Component<Props> {
shouldComponentUpdate() {
return false;
}
render() {
return (
<Card style={styles.card}>
<Card.Title
title={"VOTE INCOMING"}
subtitle={"GET READY"}
left={props => <Avatar.Icon
{...props}
icon="vote"/>}
/>
<Card.Content>
<Paragraph>
VOTE STARTS
AT {this.props.startDate}
</Paragraph>
</Card.Content>
</Card>
);
}
}
const styles = StyleSheet.create({
card: {
margin: 10,
},
icon: {
backgroundColor: 'transparent'
},
});

View file

@ -0,0 +1,54 @@
// @flow
import * as React from 'react';
import {Avatar, Card, Paragraph} from "react-native-paper";
import {StyleSheet} from "react-native";
const ICON_AMICALE = require('../../../../assets/amicale.png');
type Props = {}
export default class VoteTitle extends React.Component<Props> {
shouldComponentUpdate() {
return false;
}
render() {
return (
<Card style={styles.card}>
<Card.Title
title={"VOTE"}
subtitle={"WHY"}
left={(props) => <Avatar.Image
{...props}
source={ICON_AMICALE}
style={styles.icon}
/>}
/>
<Card.Content>
<Paragraph>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rhoncus porttitor
suscipit. Quisque hendrerit, quam id vestibulum vestibulum, lorem nisi hendrerit nisi, a
eleifend sapien diam ut elit. Curabitur sit amet vulputate lectus. Donec semper cursus sapien
vel finibus.
</Paragraph>
<Paragraph>
Sed et venenatis turpis. Fusce malesuada magna urna, sed vehicula sem luctus in. Vivamus
faucibus vel eros a ultricies. In sed laoreet ante, luctus mattis tellus. Etiam vitae ipsum
sagittis, consequat purus sed, blandit risus.
</Paragraph>
</Card.Content>
</Card>
);
}
}
const styles = StyleSheet.create({
card: {
margin: 10,
},
icon: {
backgroundColor: 'transparent'
},
});

View file

@ -0,0 +1,73 @@
// @flow
import * as React from 'react';
import {ActivityIndicator, Card, Paragraph, withTheme} from "react-native-paper";
import {StyleSheet} from "react-native";
type Props = {
startDate: string | null,
justVoted: boolean,
hasVoted: boolean,
isVoteRunning: boolean,
}
class VoteWait extends React.Component<Props> {
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
shouldComponentUpdate() {
return false;
}
render() {
return (
<Card style={styles.card}>
<Card.Title
title={this.props.isVoteRunning ? "VOTE SUBMITTED" : "VOTES HAVE ENDED"}
subtitle={"WAITING FOR RESULTS"}
left={(props) => <ActivityIndicator {...props}/>}
/>
<Card.Content>
{
this.props.justVoted
? <Paragraph style={{color: this.colors.success}}>
VOTE SUBMITTED. THX FOR YOUR PARTICIPATION
</Paragraph>
: null
}
{
this.props.hasVoted
? <Paragraph style={{color: this.colors.success}}>
THX FOR THE VOTE
</Paragraph>
: null
}
{
this.props.startDate !== null
? <Paragraph>
RESULTS AVAILABLE
AT {this.props.startDate}
</Paragraph>
: <Paragraph>RESULTS AVAILABLE SHORTLY</Paragraph>
}
</Card.Content>
</Card>
);
}
}
const styles = StyleSheet.create({
card: {
margin: 10,
},
icon: {
backgroundColor: 'transparent'
},
});
export default withTheme(VoteWait);

View file

@ -190,11 +190,11 @@ export default class ConnectionManager {
return data; return data;
} }
async authenticatedRequest(path: string, keys: Array<string>, values: Array<any>) { async authenticatedRequest(path: string, keys: Array<string>|null, values: Array<any>|null) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.getToken() !== null) { if (this.getToken() !== null) {
let data = {}; let data = {};
if (keys !== undefined && values !== undefined && keys.length === values.length) if (keys !== null && values !== null && keys.length === values.length)
data = this.generatePostArguments(keys, values); data = this.generatePostArguments(keys, values);
console.log(data); console.log(data);
fetch(API_ENDPOINT + path, { fetch(API_ENDPOINT + path, {

View file

@ -1,32 +1,20 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {FlatList, StyleSheet, View} from "react-native"; import {FlatList, View} from "react-native";
import {
ActivityIndicator,
Avatar,
Button,
Card,
List,
Paragraph,
ProgressBar,
RadioButton,
Subheading,
withTheme
} from 'react-native-paper';
import AuthenticatedScreen from "../../components/Amicale/AuthenticatedScreen"; import AuthenticatedScreen from "../../components/Amicale/AuthenticatedScreen";
import {getTimeOnlyString, stringToDate} from "../../utils/Planning"; import {getTimeOnlyString, stringToDate} from "../../utils/Planning";
import LoadingConfirmDialog from "../../components/Dialog/LoadingConfirmDialog"; import VoteTitle from "../../components/Amicale/Vote/VoteTitle";
import ConnectionManager from "../../managers/ConnectionManager"; import VoteTease from "../../components/Amicale/Vote/VoteTease";
import ErrorDialog from "../../components/Dialog/ErrorDialog"; import VoteSelect from "../../components/Amicale/Vote/VoteSelect";
import VoteResults from "../../components/Amicale/Vote/VoteResults";
const ICON_AMICALE = require('../../../assets/amicale.png'); import VoteWait from "../../components/Amicale/Vote/VoteWait";
const FAKE_DATE = { const FAKE_DATE = {
"date_begin": "2020-04-06 21:50", "date_begin": "2020-04-06 21:50",
"date_end": "2020-04-07 23:50", "date_end": "2020-04-07 23:50",
"date_result_begin": "2020-04-07 21:50", "date_result_begin": "2020-04-07 21:50",
"date_result_end": "2020-04-07 21:50", "date_result_end": "2020-04-07 22:50",
}; };
const FAKE_DATE2 = { const FAKE_DATE2 = {
@ -65,31 +53,18 @@ const FAKE_TEAMS2 = {
], ],
}; };
type Props = { type Props = {}
navigation: Object,
theme: Object,
}
type State = { type State = {
selectedTeam: string,
hasVoted: boolean, hasVoted: boolean,
voteDialogVisible: boolean,
errorDialogVisible: boolean,
currentError: number,
} }
class VoteScreen extends React.Component<Props, State> { export default class VoteScreen extends React.Component<Props, State> {
state = { state = {
selectedTeam: "none",
voteDialogVisible: false,
errorDialogVisible: false,
currentError: 0,
hasVoted: false, hasVoted: false,
}; };
colors: Object;
teams: Array<Object>; teams: Array<Object>;
hasVoted: boolean; hasVoted: boolean;
datesString: Object; datesString: Object;
@ -98,13 +73,11 @@ class VoteScreen extends React.Component<Props, State> {
today: Date; today: Date;
mainFlatListData: Array<Object>; mainFlatListData: Array<Object>;
totalVotes: number;
authRef: Object; authRef: Object;
constructor(props) { constructor() {
super(props); super();
this.colors = props.theme.colors;
this.hasVoted = false; this.hasVoted = false;
this.today = new Date(); this.today = new Date();
this.authRef = React.createRef(); this.authRef = React.createRef();
@ -125,10 +98,11 @@ class VoteScreen extends React.Component<Props, State> {
}; };
} }
getDateString(date: Date, dateString: string) { getDateString(date: Date, dateString: string): string {
if (this.today.getDate() === date.getDate()) if (this.today.getDate() === date.getDate()) {
return getTimeOnlyString(dateString); const str = getTimeOnlyString(dateString);
else return str !== null ? str : "";
} else
return dateString; return dateString;
} }
@ -154,7 +128,7 @@ class VoteScreen extends React.Component<Props, State> {
mainRenderItem = ({item}: Object) => { mainRenderItem = ({item}: Object) => {
if (item.key === 'info') if (item.key === 'info')
return this.getTitleCard(); return <VoteTitle/>;
else if (item.key === 'main' && this.isVoteAvailable()) else if (item.key === 'main' && this.isVoteAvailable())
return this.getContent(); return this.getContent();
else else
@ -176,64 +150,13 @@ class VoteScreen extends React.Component<Props, State> {
{/*$FlowFixMe*/} {/*$FlowFixMe*/}
<FlatList <FlatList
data={this.mainFlatListData} data={this.mainFlatListData}
extraData={this.state.selectedTeam + this.state.hasVoted.toString()} extraData={this.state.hasVoted.toString()}
renderItem={this.mainRenderItem} renderItem={this.mainRenderItem}
/> />
<LoadingConfirmDialog
visible={this.state.voteDialogVisible}
onDismiss={this.onVoteDialogDismiss}
onAccept={this.onVoteDialogAccept}
title={"VOTE?"}
titleLoading={"SENDING VOTE..."}
message={"SURE?"}
/>
<ErrorDialog
visible={this.state.errorDialogVisible}
onDismiss={this.onErrorDialogDismiss}
errorCode={this.state.currentError}
/>
</View> </View>
); );
}; };
showVoteDialog = () => this.setState({voteDialogVisible: true});
onVoteDialogDismiss = (voteStatus: boolean) => {
voteStatus = voteStatus === undefined ? false : voteStatus;
this.setState({
voteDialogVisible: false,
hasVoted: voteStatus,
})
};
onVoteDialogAccept = async () => {
return new Promise((resolve, reject) => {
ConnectionManager.getInstance().authenticatedRequest(
"elections/vote",
["vote"],
[parseInt(this.state.selectedTeam)])
.then(() => {
this.onVoteDialogDismiss(true);
resolve();
})
.catch((error: number) => {
this.onVoteDialogDismiss(false);
this.showErrorDialog(error);
resolve();
});
});
};
showErrorDialog = (error: number) => this.setState({
errorDialogVisible: true,
currentError: error,
});
onErrorDialogDismiss = () => {
this.setState({errorDialogVisible: false});
this.reloadData();
};
getContent() { getContent() {
if (!this.isVoteStarted()) if (!this.isVoteStarted())
return this.getTeaseVoteCard(); return this.getTeaseVoteCard();
@ -247,218 +170,39 @@ class VoteScreen extends React.Component<Props, State> {
return null; return null;
} }
getTitleCard() { onVoteSuccess = () => this.setState({hasVoted: true});
return (
<Card style={styles.card}>
<Card.Title
title={"VOTE"}
subtitle={"WHY"}
left={(props) => <Avatar.Image
{...props}
source={ICON_AMICALE}
style={styles.icon}
/>}
/>
<Card.Content>
<Paragraph>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rhoncus porttitor
suscipit. Quisque hendrerit, quam id vestibulum vestibulum, lorem nisi hendrerit nisi, a
eleifend sapien diam ut elit. Curabitur sit amet vulputate lectus. Donec semper cursus sapien
vel finibus.
</Paragraph>
<Paragraph>
Sed et venenatis turpis. Fusce malesuada magna urna, sed vehicula sem luctus in. Vivamus
faucibus vel eros a ultricies. In sed laoreet ante, luctus mattis tellus. Etiam vitae ipsum
sagittis, consequat purus sed, blandit risus.
</Paragraph>
</Card.Content>
</Card>
);
}
onVoteSelectionChange = (team: string) => {
this.setState({selectedTeam: team})
};
onVotePress = () => {
this.showVoteDialog();
};
voteKeyExtractor = (item: Object) => item.id.toString();
voteRenderItem = ({item}: Object) => <RadioButton.Item label={item.name} value={item.id.toString()}/>;
/** /**
* The user has not voted yet, and the votes are open * The user has not voted yet, and the votes are open
*/ */
getVoteCard() { getVoteCard() {
return ( return <VoteSelect teams={this.teams} onVoteSuccess={this.onVoteSuccess} onVoteError={this.reloadData}/>;
<Card style={styles.card}>
<Card.Title
title={"VOTE OPEN"}
subtitle={"VOTE NOW"}
left={(props) => <Avatar.Icon
{...props}
icon={"alert-decagram"}
/>}
/>
<Card.Content>
<RadioButton.Group
onValueChange={this.onVoteSelectionChange}
value={this.state.selectedTeam}
>
{/*$FlowFixMe*/}
<FlatList
data={this.teams}
keyExtractor={this.voteKeyExtractor}
extraData={this.state.selectedTeam}
renderItem={this.voteRenderItem}
/>
</RadioButton.Group>
</Card.Content>
<Card.Actions>
<Button
icon="send"
mode="contained"
onPress={this.onVotePress}
style={{marginLeft: 'auto'}}
disabled={this.state.selectedTeam === "none"}
>
SEND VOTE
</Button>
</Card.Actions>
</Card>
);
}
sortByVotes = (a: Object, b: Object) => b.votes - a.votes;
getTotalVotes() {
let count = 0;
for (let i = 0; i < this.teams.length; i++) {
count += this.teams[i].votes;
}
return count;
}
getWinnerId() {
return this.teams[0].id;
} }
/** /**
* Votes have ended, results can be displayed * Votes have ended, results can be displayed
*/ */
getVoteResultCard() { getVoteResultCard() {
this.totalVotes = this.getTotalVotes(); return <VoteResults teams={this.teams}
this.teams.sort(this.sortByVotes); dateEnd={this.getDateString(this.dates.date_result_end, this.datesString.date_result_end)}/>;
return (
<Card style={styles.card}>
<Card.Title
title={"RESULTS"}
subtitle={"AVAILABLE UNTIL " + this.getDateString(this.dates.date_result_end, this.datesString.date_result_end)}
left={(props) => <Avatar.Icon
{...props}
icon={"podium-gold"}
/>}
/>
<Card.Content>
<Subheading>TOTAL VOTES : {this.totalVotes}</Subheading>
{/*$FlowFixMe*/}
<FlatList
data={this.teams}
keyExtractor={this.voteKeyExtractor}
renderItem={this.resultRenderItem}
/>
</Card.Content>
</Card>
);
} }
resultRenderItem = ({item}: Object) => {
const isWinner = this.getWinnerId() === item.id;
return (
<Card style={{
marginTop: 10,
elevation: isWinner ? 5 : 3,
}}>
<List.Item
title={item.name}
description={item.votes + " VOTES"}
left={props => isWinner
? <List.Icon {...props} icon="trophy" color={this.colors.primary}/>
: null}
titleStyle={{
color: isWinner
? this.colors.primary
: this.colors.text
}}
style={{padding: 0}}
/>
<ProgressBar progress={item.votes / this.totalVotes} color={this.colors.primary}/>
</Card>
);
};
/** /**
* Vote will open shortly * Vote will open shortly
*/ */
getTeaseVoteCard() { getTeaseVoteCard() {
return ( return <VoteTease startDate={this.getDateString(this.dates.date_begin, this.datesString.date_begin)}/>;
<Card style={styles.card}>
<Card.Title
title={"VOTE INCOMING"}
subtitle={"GET READY"}
left={props => <Avatar.Icon
{...props}
icon="vote"/>}
/>
<Card.Content>
<Paragraph>
VOTE STARTS
AT {this.getDateString(this.dates.date_begin, this.datesString.date_begin)}
</Paragraph>
</Card.Content>
</Card>
);
} }
/** /**
* Votes have ended waiting for results * Votes have ended, or user has voted waiting for results
*/ */
getWaitVoteCard() { getWaitVoteCard() {
return ( let startDate = null;
<Card style={styles.card}> if (this.dates.date_result_begin !== null)
<Card.Title startDate = this.getDateString(this.dates.date_result_begin, this.datesString.date_result_begin);
title={this.isVoteRunning() ? "VOTE SUBMITTED" : "VOTES HAVE ENDED"} return <VoteWait startDate={startDate} hasVoted={this.hasVoted} justVoted={this.state.hasVoted}
subtitle={"WAITING FOR RESULTS"} isVoteRunning={this.isVoteRunning()}/>;
left={(props) => <ActivityIndicator {...props}/>}
/>
<Card.Content>
{
this.state.hasVoted
? <Paragraph style={{color: this.colors.success}}>
VOTE SUBMITTED. THX FOR YOUR PARTICIPATION
</Paragraph>
: null
}
{
this.hasVoted
? <Paragraph style={{color: this.colors.success}}>
THX FOR THE VOTE
</Paragraph>
: null
}
{
this.dates.date_result_begin !== null
? <Paragraph>
RESULTS AVAILABLE
AT {this.getDateString(this.dates.date_result_begin, this.datesString.date_result_begin)}
</Paragraph>
: <Paragraph>RESULTS AVAILABLE SHORTLY</Paragraph>
}
</Card.Content>
</Card>
);
} }
render() { render() {
@ -481,14 +225,3 @@ class VoteScreen extends React.Component<Props, State> {
); );
} }
} }
const styles = StyleSheet.create({
card: {
margin: 10,
},
icon: {
backgroundColor: 'transparent'
},
});
export default withTheme(VoteScreen);