Improve vote screens to match linter

This commit is contained in:
Arnaud Vergnet 2020-08-02 19:51:19 +02:00
parent 0a9e0eb0ca
commit 142b861ccb
5 changed files with 394 additions and 359 deletions

View file

@ -2,36 +2,38 @@
import * as React from 'react'; import * as React from 'react';
import {View} from 'react-native'; import {View} from 'react-native';
import {Headline, withTheme} from "react-native-paper"; import {Headline, withTheme} from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import type {CustomTheme} from "../../../managers/ThemeManager"; import type {CustomTheme} from '../../../managers/ThemeManager';
type Props = { type PropsType = {
theme: CustomTheme theme: CustomTheme,
} };
class VoteNotAvailable extends React.Component<Props> { class VoteNotAvailable extends React.Component<PropsType> {
shouldComponentUpdate(): boolean {
return false;
}
shouldComponentUpdate() { render(): React.Node {
return false; const {props} = this;
} return (
<View
render() { style={{
return ( width: '100%',
<View style={{ marginTop: 10,
width: "100%", marginBottom: 10,
marginTop: 10, }}>
marginBottom: 10, <Headline
}}> style={{
<Headline color: props.theme.colors.textDisabled,
style={{ textAlign: 'center',
color: this.props.theme.colors.textDisabled, }}>
textAlign: "center", {i18n.t('screens.vote.noVote')}
}} </Headline>
>{i18n.t("screens.vote.noVote")}</Headline> </View>
</View> );
); }
}
} }
export default withTheme(VoteNotAvailable); export default withTheme(VoteNotAvailable);

View file

@ -1,116 +1,134 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Avatar, Card, List, ProgressBar, Subheading, withTheme} from "react-native-paper"; import {
import {FlatList, StyleSheet} from "react-native"; Avatar,
Card,
List,
ProgressBar,
Subheading,
withTheme,
} from 'react-native-paper';
import {FlatList, StyleSheet} from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import type {team} from "../../../screens/Amicale/VoteScreen"; import type {VoteTeamType} from '../../../screens/Amicale/VoteScreen';
import type {CustomTheme} from "../../../managers/ThemeManager"; import type {CustomTheme} from '../../../managers/ThemeManager';
type PropsType = {
type Props = { teams: Array<VoteTeamType>,
teams: Array<team>, dateEnd: string,
dateEnd: string, theme: CustomTheme,
theme: CustomTheme, };
}
class VoteResults extends React.Component<Props> {
totalVotes: number;
winnerIds: Array<number>;
constructor(props) {
super();
props.teams.sort(this.sortByVotes);
this.getTotalVotes(props.teams);
this.getWinnerIds(props.teams);
}
shouldComponentUpdate() {
return false;
}
sortByVotes = (a: team, b: team) => b.votes - a.votes;
getTotalVotes(teams: Array<team>) {
this.totalVotes = 0;
for (let i = 0; i < teams.length; i++) {
this.totalVotes += teams[i].votes;
}
}
getWinnerIds(teams: Array<team>) {
let max = teams[0].votes;
this.winnerIds = [];
for (let i = 0; i < teams.length; i++) {
if (teams[i].votes === max)
this.winnerIds.push(teams[i].id);
else
break;
}
}
voteKeyExtractor = (item: team) => item.id.toString();
resultRenderItem = ({item}: { item: team }) => {
const isWinner = this.winnerIds.indexOf(item.id) !== -1;
const isDraw = this.winnerIds.length > 1;
const colors = this.props.theme.colors;
return (
<Card style={{
marginTop: 10,
elevation: isWinner ? 5 : 3,
}}>
<List.Item
title={item.name}
description={item.votes + ' ' + i18n.t('screens.vote.results.votes')}
left={props => isWinner
? <List.Icon {...props} icon={isDraw ? "trophy-outline" : "trophy"} color={colors.primary}/>
: null}
titleStyle={{
color: isWinner
? colors.primary
: colors.text
}}
style={{padding: 0}}
/>
<ProgressBar progress={item.votes / this.totalVotes} color={colors.primary}/>
</Card>
);
};
render() {
return (
<Card style={styles.card}>
<Card.Title
title={i18n.t('screens.vote.results.title')}
subtitle={i18n.t('screens.vote.results.subtitle') + ' ' + this.props.dateEnd}
left={(props) => <Avatar.Icon
{...props}
icon={"podium-gold"}
/>}
/>
<Card.Content>
<Subheading>{i18n.t('screens.vote.results.totalVotes') + ' ' + this.totalVotes}</Subheading>
{/*$FlowFixMe*/}
<FlatList
data={this.props.teams}
keyExtractor={this.voteKeyExtractor}
renderItem={this.resultRenderItem}
/>
</Card.Content>
</Card>
);
}
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
card: { card: {
margin: 10, margin: 10,
}, },
icon: { icon: {
backgroundColor: 'transparent' backgroundColor: 'transparent',
}, },
}); });
class VoteResults extends React.Component<PropsType> {
totalVotes: number;
winnerIds: Array<number>;
constructor(props: PropsType) {
super();
props.teams.sort(this.sortByVotes);
this.getTotalVotes(props.teams);
this.getWinnerIds(props.teams);
}
shouldComponentUpdate(): boolean {
return false;
}
getTotalVotes(teams: Array<VoteTeamType>) {
this.totalVotes = 0;
for (let i = 0; i < teams.length; i += 1) {
this.totalVotes += teams[i].votes;
}
}
getWinnerIds(teams: Array<VoteTeamType>) {
const max = teams[0].votes;
this.winnerIds = [];
for (let i = 0; i < teams.length; i += 1) {
if (teams[i].votes === max) this.winnerIds.push(teams[i].id);
else break;
}
}
sortByVotes = (a: VoteTeamType, b: VoteTeamType): number => b.votes - a.votes;
voteKeyExtractor = (item: VoteTeamType): string => item.id.toString();
resultRenderItem = ({item}: {item: VoteTeamType}): React.Node => {
const isWinner = this.winnerIds.indexOf(item.id) !== -1;
const isDraw = this.winnerIds.length > 1;
const {props} = this;
return (
<Card
style={{
marginTop: 10,
elevation: isWinner ? 5 : 3,
}}>
<List.Item
title={item.name}
description={`${item.votes} ${i18n.t('screens.vote.results.votes')}`}
left={({size}: {size: number}): React.Node =>
isWinner ? (
<List.Icon
size={size}
icon={isDraw ? 'trophy-outline' : 'trophy'}
color={props.theme.colors.primary}
/>
) : null
}
titleStyle={{
color: isWinner
? props.theme.colors.primary
: props.theme.colors.text,
}}
style={{padding: 0}}
/>
<ProgressBar
progress={item.votes / this.totalVotes}
color={props.theme.colors.primary}
/>
</Card>
);
};
render(): React.Node {
const {props} = this;
return (
<Card style={styles.card}>
<Card.Title
title={i18n.t('screens.vote.results.title')}
subtitle={`${i18n.t('screens.vote.results.subtitle')} ${
props.dateEnd
}`}
left={({size}: {size: number}): React.Node => (
<Avatar.Icon size={size} icon="podium-gold" />
)}
/>
<Card.Content>
<Subheading>{`${i18n.t('screens.vote.results.totalVotes')} ${
this.totalVotes
}`}</Subheading>
{/* $FlowFixMe */}
<FlatList
data={props.teams}
keyExtractor={this.voteKeyExtractor}
renderItem={this.resultRenderItem}
/>
</Card.Content>
</Card>
);
}
}
export default withTheme(VoteResults); export default withTheme(VoteResults);

View file

@ -1,137 +1,146 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Avatar, Button, Card, RadioButton} from "react-native-paper"; import {Avatar, Button, Card, RadioButton} from 'react-native-paper';
import {FlatList, StyleSheet, View} from "react-native"; import {FlatList, StyleSheet, View} from 'react-native';
import ConnectionManager from "../../../managers/ConnectionManager";
import LoadingConfirmDialog from "../../Dialogs/LoadingConfirmDialog";
import ErrorDialog from "../../Dialogs/ErrorDialog";
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import type {team} from "../../../screens/Amicale/VoteScreen"; import ConnectionManager from '../../../managers/ConnectionManager';
import LoadingConfirmDialog from '../../Dialogs/LoadingConfirmDialog';
import ErrorDialog from '../../Dialogs/ErrorDialog';
import type {VoteTeamType} from '../../../screens/Amicale/VoteScreen';
type Props = { type PropsType = {
teams: Array<team>, teams: Array<VoteTeamType>,
onVoteSuccess: () => void, onVoteSuccess: () => void,
onVoteError: () => void, onVoteError: () => void,
} };
type State = { type StateType = {
selectedTeam: string, selectedTeam: string,
voteDialogVisible: boolean, voteDialogVisible: boolean,
errorDialogVisible: boolean, errorDialogVisible: boolean,
currentError: number, 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: team) => item.id.toString();
voteRenderItem = ({item}: { item: team }) => <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) => {
ConnectionManager.getInstance().authenticatedRequest(
"elections/vote",
{"team": 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={i18n.t('screens.vote.select.title')}
subtitle={i18n.t('screens.vote.select.subtitle')}
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"}
>
{i18n.t('screens.vote.select.sendButton')}
</Button>
</Card.Actions>
</Card>
<LoadingConfirmDialog
visible={this.state.voteDialogVisible}
onDismiss={this.onVoteDialogDismiss}
onAccept={this.onVoteDialogAccept}
title={i18n.t('screens.vote.select.dialogTitle')}
titleLoading={i18n.t('screens.vote.select.dialogTitleLoading')}
message={i18n.t('screens.vote.select.dialogMessage')}
/>
<ErrorDialog
visible={this.state.errorDialogVisible}
onDismiss={this.onErrorDialogDismiss}
errorCode={this.state.currentError}
/>
</View>
);
}
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
card: { card: {
margin: 10, margin: 10,
}, },
icon: { icon: {
backgroundColor: 'transparent' backgroundColor: 'transparent',
}, },
}); });
export default class VoteSelect extends React.PureComponent<
PropsType,
StateType,
> {
constructor() {
super();
this.state = {
selectedTeam: 'none',
voteDialogVisible: false,
errorDialogVisible: false,
currentError: 0,
};
}
onVoteSelectionChange = (teamName: string): void =>
this.setState({selectedTeam: teamName});
voteKeyExtractor = (item: VoteTeamType): string => item.id.toString();
voteRenderItem = ({item}: {item: VoteTeamType}): React.Node => (
<RadioButton.Item label={item.name} value={item.id.toString()} />
);
showVoteDialog = (): void => this.setState({voteDialogVisible: true});
onVoteDialogDismiss = (): void => this.setState({voteDialogVisible: false});
onVoteDialogAccept = async (): Promise<void> => {
return new Promise((resolve: () => void) => {
const {state} = this;
ConnectionManager.getInstance()
.authenticatedRequest('elections/vote', {
team: parseInt(state.selectedTeam, 10),
})
.then(() => {
this.onVoteDialogDismiss();
const {props} = this;
props.onVoteSuccess();
resolve();
})
.catch((error: number) => {
this.onVoteDialogDismiss();
this.showErrorDialog(error);
resolve();
});
});
};
showErrorDialog = (error: number): void =>
this.setState({
errorDialogVisible: true,
currentError: error,
});
onErrorDialogDismiss = () => {
this.setState({errorDialogVisible: false});
const {props} = this;
props.onVoteError();
};
render(): React.Node {
const {state, props} = this;
return (
<View>
<Card style={styles.card}>
<Card.Title
title={i18n.t('screens.vote.select.title')}
subtitle={i18n.t('screens.vote.select.subtitle')}
left={({size}: {size: number}): React.Node => (
<Avatar.Icon size={size} icon="alert-decagram" />
)}
/>
<Card.Content>
<RadioButton.Group
onValueChange={this.onVoteSelectionChange}
value={state.selectedTeam}>
{/* $FlowFixMe */}
<FlatList
data={props.teams}
keyExtractor={this.voteKeyExtractor}
extraData={state.selectedTeam}
renderItem={this.voteRenderItem}
/>
</RadioButton.Group>
</Card.Content>
<Card.Actions>
<Button
icon="send"
mode="contained"
onPress={this.showVoteDialog}
style={{marginLeft: 'auto'}}
disabled={state.selectedTeam === 'none'}>
{i18n.t('screens.vote.select.sendButton')}
</Button>
</Card.Actions>
</Card>
<LoadingConfirmDialog
visible={state.voteDialogVisible}
onDismiss={this.onVoteDialogDismiss}
onAccept={this.onVoteDialogAccept}
title={i18n.t('screens.vote.select.dialogTitle')}
titleLoading={i18n.t('screens.vote.select.dialogTitleLoading')}
message={i18n.t('screens.vote.select.dialogMessage')}
/>
<ErrorDialog
visible={state.errorDialogVisible}
onDismiss={this.onErrorDialogDismiss}
errorCode={state.currentError}
/>
</View>
);
}
}

View file

@ -1,45 +1,45 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {Avatar, Card, Paragraph} from "react-native-paper"; import {Avatar, Card, Paragraph} from 'react-native-paper';
import {StyleSheet} from "react-native"; import {StyleSheet} from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
type Props = { type PropsType = {
startDate: string, startDate: string,
} };
export default class VoteTease extends React.Component<Props> {
shouldComponentUpdate() {
return false;
}
render() {
return (
<Card style={styles.card}>
<Card.Title
title={i18n.t('screens.vote.tease.title')}
subtitle={i18n.t('screens.vote.tease.subtitle')}
left={props => <Avatar.Icon
{...props}
icon="vote"/>}
/>
<Card.Content>
<Paragraph>
{i18n.t('screens.vote.tease.message') + ' ' + this.props.startDate}
</Paragraph>
</Card.Content>
</Card>
);
}
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
card: { card: {
margin: 10, margin: 10,
}, },
icon: { icon: {
backgroundColor: 'transparent' backgroundColor: 'transparent',
}, },
}); });
export default class VoteTease extends React.Component<PropsType> {
shouldComponentUpdate(): boolean {
return false;
}
render(): React.Node {
const {props} = this;
return (
<Card style={styles.card}>
<Card.Title
title={i18n.t('screens.vote.tease.title')}
subtitle={i18n.t('screens.vote.tease.subtitle')}
left={({size}: {size: number}): React.Node => (
<Avatar.Icon size={size} icon="vote" />
)}
/>
<Card.Content>
<Paragraph>
{`${i18n.t('screens.vote.tease.message')} ${props.startDate}`}
</Paragraph>
</Card.Content>
</Card>
);
}
}

View file

@ -1,72 +1,78 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import {ActivityIndicator, Card, Paragraph, withTheme} from "react-native-paper"; import {
import {StyleSheet} from "react-native"; ActivityIndicator,
Card,
Paragraph,
withTheme,
} from 'react-native-paper';
import {StyleSheet} from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import type {CustomTheme} from "../../../managers/ThemeManager"; import type {CustomTheme} from '../../../managers/ThemeManager';
type Props = { type PropsType = {
startDate: string | null, startDate: string | null,
justVoted: boolean, justVoted: boolean,
hasVoted: boolean, hasVoted: boolean,
isVoteRunning: boolean, isVoteRunning: boolean,
theme: CustomTheme, theme: CustomTheme,
} };
class VoteWait extends React.Component<Props> {
shouldComponentUpdate() {
return false;
}
render() {
const colors = this.props.theme.colors;
const startDate = this.props.startDate;
return (
<Card style={styles.card}>
<Card.Title
title={this.props.isVoteRunning
? i18n.t('screens.vote.wait.titleSubmitted')
: i18n.t('screens.vote.wait.titleEnded')}
subtitle={i18n.t('screens.vote.wait.subtitle')}
left={(props) => <ActivityIndicator {...props}/>}
/>
<Card.Content>
{
this.props.justVoted
? <Paragraph style={{color: colors.success}}>
{i18n.t('screens.vote.wait.messageSubmitted')}
</Paragraph>
: null
}
{
this.props.hasVoted
? <Paragraph style={{color: colors.success}}>
{i18n.t('screens.vote.wait.messageVoted')}
</Paragraph>
: null
}
{
startDate != null
? <Paragraph>
{i18n.t('screens.vote.wait.messageDate') + ' ' + startDate}
</Paragraph>
: <Paragraph>{i18n.t('screens.vote.wait.messageDateUndefined')}</Paragraph>
}
</Card.Content>
</Card>
);
}
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
card: { card: {
margin: 10, margin: 10,
}, },
icon: { icon: {
backgroundColor: 'transparent' backgroundColor: 'transparent',
}, },
}); });
class VoteWait extends React.Component<PropsType> {
shouldComponentUpdate(): boolean {
return false;
}
render(): React.Node {
const {props} = this;
const {startDate} = props;
return (
<Card style={styles.card}>
<Card.Title
title={
props.isVoteRunning
? i18n.t('screens.vote.wait.titleSubmitted')
: i18n.t('screens.vote.wait.titleEnded')
}
subtitle={i18n.t('screens.vote.wait.subtitle')}
left={({size}: {size: number}): React.Node => (
<ActivityIndicator size={size} />
)}
/>
<Card.Content>
{props.justVoted ? (
<Paragraph style={{color: props.theme.colors.success}}>
{i18n.t('screens.vote.wait.messageSubmitted')}
</Paragraph>
) : null}
{props.hasVoted ? (
<Paragraph style={{color: props.theme.colors.success}}>
{i18n.t('screens.vote.wait.messageVoted')}
</Paragraph>
) : null}
{startDate != null ? (
<Paragraph>
{`${i18n.t('screens.vote.wait.messageDate')} ${startDate}`}
</Paragraph>
) : (
<Paragraph>
{i18n.t('screens.vote.wait.messageDateUndefined')}
</Paragraph>
)}
</Card.Content>
</Card>
);
}
}
export default withTheme(VoteWait); export default withTheme(VoteWait);