Application Android et IOS pour l'amicale des élèves
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VoteScreen.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. // @flow
  2. import * as React from 'react';
  3. import {FlatList, RefreshControl, View} from "react-native";
  4. import AuthenticatedScreen from "../../components/Amicale/AuthenticatedScreen";
  5. import {getTimeOnlyString, stringToDate} from "../../utils/Planning";
  6. import VoteTease from "../../components/Amicale/Vote/VoteTease";
  7. import VoteSelect from "../../components/Amicale/Vote/VoteSelect";
  8. import VoteResults from "../../components/Amicale/Vote/VoteResults";
  9. import VoteWait from "../../components/Amicale/Vote/VoteWait";
  10. import {StackNavigationProp} from "@react-navigation/stack";
  11. import i18n from "i18n-js";
  12. import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
  13. import MascotPopup from "../../components/Mascot/MascotPopup";
  14. import AsyncStorageManager from "../../managers/AsyncStorageManager";
  15. import {Button} from "react-native-paper";
  16. import VoteNotAvailable from "../../components/Amicale/Vote/VoteNotAvailable";
  17. export type team = {
  18. id: number,
  19. name: string,
  20. votes: number,
  21. }
  22. type teamResponse = {
  23. has_voted: boolean,
  24. teams: Array<team>,
  25. };
  26. type stringVoteDates = {
  27. date_begin: string,
  28. date_end: string,
  29. date_result_begin: string,
  30. date_result_end: string,
  31. }
  32. type objectVoteDates = {
  33. date_begin: Date,
  34. date_end: Date,
  35. date_result_begin: Date,
  36. date_result_end: Date,
  37. }
  38. // const FAKE_DATE = {
  39. // "date_begin": "2020-08-19 15:50",
  40. // "date_end": "2020-08-19 15:50",
  41. // "date_result_begin": "2020-08-19 19:50",
  42. // "date_result_end": "2020-08-19 22:50",
  43. // };
  44. //
  45. // const FAKE_DATE2 = {
  46. // "date_begin": null,
  47. // "date_end": null,
  48. // "date_result_begin": null,
  49. // "date_result_end": null,
  50. // };
  51. //
  52. // const FAKE_TEAMS = {
  53. // has_voted: false,
  54. // teams: [
  55. // {
  56. // id: 1,
  57. // name: "TEST TEAM1",
  58. // },
  59. // {
  60. // id: 2,
  61. // name: "TEST TEAM2",
  62. // },
  63. // ],
  64. // };
  65. // const FAKE_TEAMS2 = {
  66. // has_voted: false,
  67. // teams: [
  68. // {
  69. // id: 1,
  70. // name: "TEST TEAM1",
  71. // votes: 9,
  72. // },
  73. // {
  74. // id: 2,
  75. // name: "TEST TEAM2",
  76. // votes: 9,
  77. // },
  78. // {
  79. // id: 3,
  80. // name: "TEST TEAM3",
  81. // votes: 5,
  82. // },
  83. // ],
  84. // };
  85. const MIN_REFRESH_TIME = 5 * 1000;
  86. type Props = {
  87. navigation: StackNavigationProp
  88. }
  89. type State = {
  90. hasVoted: boolean,
  91. mascotDialogVisible: boolean,
  92. }
  93. /**
  94. * Screen displaying vote information and controls
  95. */
  96. export default class VoteScreen extends React.Component<Props, State> {
  97. state = {
  98. hasVoted: false,
  99. mascotDialogVisible: AsyncStorageManager.getInstance().preferences.voteShowBanner.current === "1",
  100. };
  101. teams: Array<team>;
  102. hasVoted: boolean;
  103. datesString: null | stringVoteDates;
  104. dates: null | objectVoteDates;
  105. today: Date;
  106. mainFlatListData: Array<{ key: string }>;
  107. lastRefresh: Date | null;
  108. authRef: { current: null | AuthenticatedScreen };
  109. constructor() {
  110. super();
  111. this.hasVoted = false;
  112. this.today = new Date();
  113. this.authRef = React.createRef();
  114. this.lastRefresh = null;
  115. this.mainFlatListData = [
  116. {key: 'main'},
  117. {key: 'info'},
  118. ]
  119. }
  120. /**
  121. * Reloads vote data if last refresh delta is smaller than the minimum refresh time
  122. */
  123. reloadData = () => {
  124. let canRefresh;
  125. const lastRefresh = this.lastRefresh;
  126. if (lastRefresh != null)
  127. canRefresh = (new Date().getTime() - lastRefresh.getTime()) > MIN_REFRESH_TIME;
  128. else
  129. canRefresh = true;
  130. if (canRefresh && this.authRef.current != null)
  131. this.authRef.current.reload()
  132. };
  133. /**
  134. * Generates the objects containing string and Date representations of key vote dates
  135. */
  136. generateDateObject() {
  137. const strings = this.datesString;
  138. if (strings != null) {
  139. const dateBegin = stringToDate(strings.date_begin);
  140. const dateEnd = stringToDate(strings.date_end);
  141. const dateResultBegin = stringToDate(strings.date_result_begin);
  142. const dateResultEnd = stringToDate(strings.date_result_end);
  143. if (dateBegin != null && dateEnd != null && dateResultBegin != null && dateResultEnd != null) {
  144. this.dates = {
  145. date_begin: dateBegin,
  146. date_end: dateEnd,
  147. date_result_begin: dateResultBegin,
  148. date_result_end: dateResultEnd,
  149. };
  150. } else
  151. this.dates = null;
  152. } else
  153. this.dates = null;
  154. }
  155. /**
  156. * Gets the string representation of the given date.
  157. *
  158. * If the given date is the same day as today, only return the tile.
  159. * Otherwise, return the full date.
  160. *
  161. * @param date The Date object representation of the wanted date
  162. * @param dateString The string representation of the wanted date
  163. * @returns {string}
  164. */
  165. getDateString(date: Date, dateString: string): string {
  166. if (this.today.getDate() === date.getDate()) {
  167. const str = getTimeOnlyString(dateString);
  168. return str != null ? str : "";
  169. } else
  170. return dateString;
  171. }
  172. isVoteRunning() {
  173. return this.dates != null && this.today > this.dates.date_begin && this.today < this.dates.date_end;
  174. }
  175. isVoteStarted() {
  176. return this.dates != null && this.today > this.dates.date_begin;
  177. }
  178. isResultRunning() {
  179. return this.dates != null && this.today > this.dates.date_result_begin && this.today < this.dates.date_result_end;
  180. }
  181. isResultStarted() {
  182. return this.dates != null && this.today > this.dates.date_result_begin;
  183. }
  184. mainRenderItem = ({item}: { item: { key: string } }) => {
  185. if (item.key === 'info')
  186. return (
  187. <View>
  188. <Button
  189. mode={"contained"}
  190. icon={"help-circle"}
  191. onPress={this.showMascotDialog}
  192. style={{
  193. marginLeft: "auto",
  194. marginRight: "auto",
  195. marginTop: 20
  196. }}>
  197. {i18n.t("screens.vote.mascotDialog.title")}
  198. </Button>
  199. </View>
  200. );
  201. else
  202. return this.getContent();
  203. };
  204. getScreen = (data: Array<{ [key: string]: any } | null>) => {
  205. // data[0] = FAKE_TEAMS2;
  206. // data[1] = FAKE_DATE;
  207. this.lastRefresh = new Date();
  208. const teams: teamResponse | null = data[0];
  209. const dateStrings: stringVoteDates | null = data[1];
  210. if (dateStrings != null && dateStrings.date_begin == null)
  211. this.datesString = null;
  212. else
  213. this.datesString = dateStrings;
  214. if (teams != null) {
  215. this.teams = teams.teams;
  216. this.hasVoted = teams.has_voted;
  217. }
  218. this.generateDateObject();
  219. return (
  220. <View>
  221. {/*$FlowFixMe*/}
  222. <FlatList
  223. data={this.mainFlatListData}
  224. refreshControl={
  225. <RefreshControl
  226. refreshing={false}
  227. onRefresh={this.reloadData}
  228. />
  229. }
  230. extraData={this.state.hasVoted.toString()}
  231. renderItem={this.mainRenderItem}
  232. />
  233. </View>
  234. );
  235. };
  236. getContent() {
  237. if (!this.isVoteStarted())
  238. return this.getTeaseVoteCard();
  239. else if (this.isVoteRunning() && (!this.hasVoted && !this.state.hasVoted))
  240. return this.getVoteCard();
  241. else if (!this.isResultStarted())
  242. return this.getWaitVoteCard();
  243. else if (this.isResultRunning())
  244. return this.getVoteResultCard();
  245. else
  246. return <VoteNotAvailable/>;
  247. }
  248. onVoteSuccess = () => this.setState({hasVoted: true});
  249. /**
  250. * The user has not voted yet, and the votes are open
  251. */
  252. getVoteCard() {
  253. return <VoteSelect teams={this.teams} onVoteSuccess={this.onVoteSuccess} onVoteError={this.reloadData}/>;
  254. }
  255. /**
  256. * Votes have ended, results can be displayed
  257. */
  258. getVoteResultCard() {
  259. if (this.dates != null && this.datesString != null)
  260. return <VoteResults
  261. teams={this.teams}
  262. dateEnd={this.getDateString(
  263. this.dates.date_result_end,
  264. this.datesString.date_result_end)}
  265. />;
  266. else
  267. return <VoteNotAvailable/>;
  268. }
  269. /**
  270. * Vote will open shortly
  271. */
  272. getTeaseVoteCard() {
  273. if (this.dates != null && this.datesString != null)
  274. return <VoteTease
  275. startDate={this.getDateString(this.dates.date_begin, this.datesString.date_begin)}/>;
  276. else
  277. return <VoteNotAvailable/>;
  278. }
  279. /**
  280. * Votes have ended, or user has voted waiting for results
  281. */
  282. getWaitVoteCard() {
  283. let startDate = null;
  284. if (this.dates != null && this.datesString != null && this.dates.date_result_begin != null)
  285. startDate = this.getDateString(this.dates.date_result_begin, this.datesString.date_result_begin);
  286. return <VoteWait startDate={startDate} hasVoted={this.hasVoted || this.state.hasVoted}
  287. justVoted={this.state.hasVoted}
  288. isVoteRunning={this.isVoteRunning()}/>;
  289. }
  290. showMascotDialog = () => {
  291. this.setState({mascotDialogVisible: true})
  292. };
  293. hideMascotDialog = () => {
  294. AsyncStorageManager.getInstance().savePref(
  295. AsyncStorageManager.getInstance().preferences.voteShowBanner.key,
  296. '0'
  297. );
  298. this.setState({mascotDialogVisible: false})
  299. };
  300. /**
  301. * Renders the authenticated screen.
  302. *
  303. * Teams and dates are not mandatory to allow showing the information box even if api requests fail
  304. *
  305. * @returns {*}
  306. */
  307. render() {
  308. return (
  309. <View style={{flex: 1}}>
  310. <AuthenticatedScreen
  311. {...this.props}
  312. ref={this.authRef}
  313. requests={[
  314. {
  315. link: 'elections/teams',
  316. params: {},
  317. mandatory: false,
  318. },
  319. {
  320. link: 'elections/dates',
  321. params: {},
  322. mandatory: false,
  323. },
  324. ]}
  325. renderFunction={this.getScreen}
  326. />
  327. <MascotPopup
  328. visible={this.state.mascotDialogVisible}
  329. title={i18n.t("screens.vote.mascotDialog.title")}
  330. message={i18n.t("screens.vote.mascotDialog.message")}
  331. icon={"vote"}
  332. buttons={{
  333. action: null,
  334. cancel: {
  335. message: i18n.t("screens.vote.mascotDialog.button"),
  336. icon: "check",
  337. onPress: this.hideMascotDialog,
  338. }
  339. }}
  340. emotion={MASCOT_STYLE.CUTE}
  341. />
  342. </View>
  343. );
  344. }
  345. }