Application Android et IOS pour l'amicale des élèves https://play.google.com/store/apps/details?id=fr.amicaleinsat.application
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.tsx 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. /*
  2. * Copyright (c) 2019 - 2020 Arnaud Vergnet.
  3. *
  4. * This file is part of Campus INSAT.
  5. *
  6. * Campus INSAT is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Campus INSAT is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. import React, { useRef, useState } from 'react';
  20. import { StyleSheet, View } from 'react-native';
  21. import i18n from 'i18n-js';
  22. import { Button } from 'react-native-paper';
  23. import { getTimeOnlyString, stringToDate } from '../../utils/Planning';
  24. import VoteTease from '../../components/Amicale/Vote/VoteTease';
  25. import VoteSelect from '../../components/Amicale/Vote/VoteSelect';
  26. import VoteResults from '../../components/Amicale/Vote/VoteResults';
  27. import VoteWait from '../../components/Amicale/Vote/VoteWait';
  28. import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
  29. import MascotPopup from '../../components/Mascot/MascotPopup';
  30. import VoteNotAvailable from '../../components/Amicale/Vote/VoteNotAvailable';
  31. import GENERAL_STYLES from '../../constants/Styles';
  32. import WebSectionList, {
  33. SectionListDataType,
  34. } from '../../components/Screens/WebSectionList';
  35. import { useAuthenticatedRequest } from '../../context/loginContext';
  36. export type VoteTeamType = {
  37. id: number;
  38. name: string;
  39. votes: number;
  40. };
  41. type TeamResponseType = {
  42. has_voted: boolean;
  43. teams: Array<VoteTeamType>;
  44. };
  45. type VoteDatesStringType = {
  46. date_begin: string;
  47. date_end: string;
  48. date_result_begin: string;
  49. date_result_end: string;
  50. };
  51. type VoteDatesObjectType = {
  52. date_begin: Date;
  53. date_end: Date;
  54. date_result_begin: Date;
  55. date_result_end: Date;
  56. };
  57. type ResponseType = {
  58. teams?: TeamResponseType;
  59. dates?: VoteDatesStringType;
  60. };
  61. type FlatlistType = {
  62. teams: Array<VoteTeamType>;
  63. hasVoted: boolean;
  64. datesString?: VoteDatesStringType;
  65. dates?: VoteDatesObjectType;
  66. };
  67. // const FAKE_DATE = {
  68. // "date_begin": "2020-08-19 15:50",
  69. // "date_end": "2020-08-19 15:50",
  70. // "date_result_begin": "2020-08-19 19:50",
  71. // "date_result_end": "2020-08-19 22:50",
  72. // };
  73. //
  74. // const FAKE_DATE2 = {
  75. // "date_begin": null,
  76. // "date_end": null,
  77. // "date_result_begin": null,
  78. // "date_result_end": null,
  79. // };
  80. //
  81. // const FAKE_TEAMS = {
  82. // has_voted: false,
  83. // teams: [
  84. // {
  85. // id: 1,
  86. // name: "TEST TEAM1",
  87. // },
  88. // {
  89. // id: 2,
  90. // name: "TEST TEAM2",
  91. // },
  92. // ],
  93. // };
  94. // const FAKE_TEAMS2 = {
  95. // has_voted: false,
  96. // teams: [
  97. // {
  98. // id: 1,
  99. // name: "TEST TEAM1",
  100. // votes: 9,
  101. // },
  102. // {
  103. // id: 2,
  104. // name: "TEST TEAM2",
  105. // votes: 9,
  106. // },
  107. // {
  108. // id: 3,
  109. // name: "TEST TEAM3",
  110. // votes: 5,
  111. // },
  112. // ],
  113. // };
  114. const styles = StyleSheet.create({
  115. button: {
  116. marginLeft: 'auto',
  117. marginRight: 'auto',
  118. marginTop: 20,
  119. },
  120. });
  121. /**
  122. * Screen displaying vote information and controls
  123. */
  124. export default function VoteScreen() {
  125. const [hasVoted, setHasVoted] = useState(false);
  126. const [mascotDialogVisible, setMascotDialogVisible] =
  127. useState<undefined | boolean>(undefined);
  128. const datesRequest =
  129. useAuthenticatedRequest<VoteDatesStringType>('elections/dates');
  130. const teamsRequest =
  131. useAuthenticatedRequest<TeamResponseType>('elections/teams');
  132. const today = new Date();
  133. const refresh = useRef<() => void | undefined>();
  134. /**
  135. * Gets the string representation of the given date.
  136. *
  137. * If the given date is the same day as today, only return the tile.
  138. * Otherwise, return the full date.
  139. *
  140. * @param date The Date object representation of the wanted date
  141. * @param dateString The string representation of the wanted date
  142. * @returns {string}
  143. */
  144. const getDateString = (date: Date, dateString: string) => {
  145. if (today.getDate() === date.getDate()) {
  146. const str = getTimeOnlyString(dateString);
  147. return str != null ? str : '';
  148. }
  149. return dateString;
  150. };
  151. const getMainRenderItem = ({
  152. item,
  153. }: {
  154. item: { key: string; data?: FlatlistType };
  155. }) => {
  156. if (item.key === 'info') {
  157. return (
  158. <View>
  159. <Button
  160. mode="contained"
  161. icon="help-circle"
  162. onPress={showMascotDialog}
  163. style={styles.button}
  164. >
  165. {i18n.t('screens.vote.mascotDialog.title')}
  166. </Button>
  167. </View>
  168. );
  169. }
  170. if (item.data) {
  171. return getContent(item.data);
  172. } else {
  173. return <View />;
  174. }
  175. };
  176. const createDataset = (
  177. data: ResponseType | undefined,
  178. _loading: boolean,
  179. _lastRefreshDate: Date | undefined,
  180. refreshData: () => void
  181. ) => {
  182. // data[0] = FAKE_TEAMS2;
  183. // data[1] = FAKE_DATE;
  184. const mainFlatListData: SectionListDataType<{
  185. key: string;
  186. data?: FlatlistType;
  187. }> = [
  188. {
  189. title: '',
  190. data: [{ key: 'main' }, { key: 'info' }],
  191. },
  192. ];
  193. refresh.current = refreshData;
  194. if (data) {
  195. const { teams, dates } = data;
  196. const flatlistData: FlatlistType = {
  197. teams: [],
  198. hasVoted: false,
  199. };
  200. if (dates && dates.date_begin != null) {
  201. flatlistData.datesString = dates;
  202. }
  203. if (teams) {
  204. flatlistData.teams = teams.teams;
  205. flatlistData.hasVoted = teams.has_voted;
  206. }
  207. flatlistData.dates = generateDateObject(flatlistData.datesString);
  208. }
  209. return mainFlatListData;
  210. };
  211. const getContent = (data: FlatlistType) => {
  212. const { dates } = data;
  213. if (!isVoteStarted(dates)) {
  214. return getTeaseVoteCard(data);
  215. }
  216. if (isVoteRunning(dates) && !data.hasVoted && !hasVoted) {
  217. return getVoteCard(data);
  218. }
  219. if (!isResultStarted(dates)) {
  220. return getWaitVoteCard(data);
  221. }
  222. if (isResultRunning(dates)) {
  223. return getVoteResultCard(data);
  224. }
  225. return <VoteNotAvailable />;
  226. };
  227. const onVoteSuccess = () => setHasVoted(true);
  228. /**
  229. * The user has not voted yet, and the votes are open
  230. */
  231. const getVoteCard = (data: FlatlistType) => {
  232. return (
  233. <VoteSelect
  234. teams={data.teams}
  235. onVoteSuccess={onVoteSuccess}
  236. onVoteError={() => {
  237. if (refresh.current) {
  238. refresh.current();
  239. }
  240. }}
  241. />
  242. );
  243. };
  244. /**
  245. * Votes have ended, results can be displayed
  246. */
  247. const getVoteResultCard = (data: FlatlistType) => {
  248. if (data.dates != null && data.datesString != null) {
  249. return (
  250. <VoteResults
  251. teams={data.teams}
  252. dateEnd={getDateString(
  253. data.dates.date_result_end,
  254. data.datesString.date_result_end
  255. )}
  256. />
  257. );
  258. }
  259. return <VoteNotAvailable />;
  260. };
  261. /**
  262. * Vote will open shortly
  263. */
  264. const getTeaseVoteCard = (data: FlatlistType) => {
  265. if (data.dates != null && data.datesString != null) {
  266. return (
  267. <VoteTease
  268. startDate={getDateString(
  269. data.dates.date_begin,
  270. data.datesString.date_begin
  271. )}
  272. />
  273. );
  274. }
  275. return <VoteNotAvailable />;
  276. };
  277. /**
  278. * Votes have ended, or user has voted waiting for results
  279. */
  280. const getWaitVoteCard = (data: FlatlistType) => {
  281. let startDate = null;
  282. if (
  283. data.dates != null &&
  284. data.datesString != null &&
  285. data.dates.date_result_begin != null
  286. ) {
  287. startDate = getDateString(
  288. data.dates.date_result_begin,
  289. data.datesString.date_result_begin
  290. );
  291. }
  292. return (
  293. <VoteWait
  294. startDate={startDate}
  295. hasVoted={data.hasVoted}
  296. justVoted={hasVoted}
  297. isVoteRunning={isVoteRunning()}
  298. />
  299. );
  300. };
  301. const showMascotDialog = () => setMascotDialogVisible(true);
  302. const hideMascotDialog = () => setMascotDialogVisible(false);
  303. const isVoteStarted = (dates?: VoteDatesObjectType) => {
  304. return dates != null && today > dates.date_begin;
  305. };
  306. const isResultRunning = (dates?: VoteDatesObjectType) => {
  307. return (
  308. dates != null &&
  309. today > dates.date_result_begin &&
  310. today < dates.date_result_end
  311. );
  312. };
  313. const isResultStarted = (dates?: VoteDatesObjectType) => {
  314. return dates != null && today > dates.date_result_begin;
  315. };
  316. const isVoteRunning = (dates?: VoteDatesObjectType) => {
  317. return dates != null && today > dates.date_begin && today < dates.date_end;
  318. };
  319. /**
  320. * Generates the objects containing string and Date representations of key vote dates
  321. */
  322. const generateDateObject = (
  323. strings?: VoteDatesStringType
  324. ): VoteDatesObjectType | undefined => {
  325. if (strings) {
  326. const dateBegin = stringToDate(strings.date_begin);
  327. const dateEnd = stringToDate(strings.date_end);
  328. const dateResultBegin = stringToDate(strings.date_result_begin);
  329. const dateResultEnd = stringToDate(strings.date_result_end);
  330. if (
  331. dateBegin != null &&
  332. dateEnd != null &&
  333. dateResultBegin != null &&
  334. dateResultEnd != null
  335. ) {
  336. return {
  337. date_begin: dateBegin,
  338. date_end: dateEnd,
  339. date_result_begin: dateResultBegin,
  340. date_result_end: dateResultEnd,
  341. };
  342. } else {
  343. return undefined;
  344. }
  345. } else {
  346. return undefined;
  347. }
  348. };
  349. const request = () => {
  350. return new Promise((resolve: (data: ResponseType) => void) => {
  351. datesRequest()
  352. .then((datesData) => {
  353. teamsRequest()
  354. .then((teamsData) => {
  355. resolve({
  356. dates: datesData,
  357. teams: teamsData,
  358. });
  359. })
  360. .catch(() =>
  361. resolve({
  362. dates: datesData,
  363. })
  364. );
  365. })
  366. .catch(() => resolve({}));
  367. });
  368. };
  369. return (
  370. <View style={GENERAL_STYLES.flex}>
  371. <WebSectionList
  372. request={request}
  373. createDataset={createDataset}
  374. extraData={hasVoted.toString()}
  375. renderItem={getMainRenderItem}
  376. />
  377. <MascotPopup
  378. visible={mascotDialogVisible}
  379. title={i18n.t('screens.vote.mascotDialog.title')}
  380. message={i18n.t('screens.vote.mascotDialog.message')}
  381. icon="vote"
  382. buttons={{
  383. cancel: {
  384. message: i18n.t('screens.vote.mascotDialog.button'),
  385. icon: 'check',
  386. onPress: hideMascotDialog,
  387. },
  388. }}
  389. emotion={MASCOT_STYLE.CUTE}
  390. />
  391. </View>
  392. );
  393. }