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

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