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.

ClubDisplayScreen.tsx 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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 * as React from 'react';
  20. import { Linking, StyleSheet, View } from 'react-native';
  21. import {
  22. Avatar,
  23. Button,
  24. Card,
  25. Chip,
  26. Paragraph,
  27. withTheme,
  28. } from 'react-native-paper';
  29. import i18n from 'i18n-js';
  30. import { StackNavigationProp } from '@react-navigation/stack';
  31. import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
  32. import CustomHTML from '../../../components/Overrides/CustomHTML';
  33. import { TAB_BAR_HEIGHT } from '../../../components/Tabbar/CustomTabBar';
  34. import type { ClubCategoryType, ClubType } from './ClubListScreen';
  35. import { ERROR_TYPE } from '../../../utils/WebData';
  36. import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
  37. import ImageGalleryButton from '../../../components/Media/ImageGalleryButton';
  38. type PropsType = {
  39. navigation: StackNavigationProp<any>;
  40. route: {
  41. params?: {
  42. data?: ClubType;
  43. categories?: Array<ClubCategoryType>;
  44. clubId?: number;
  45. };
  46. };
  47. theme: ReactNativePaper.Theme;
  48. };
  49. const AMICALE_MAIL = 'clubs@amicale-insat.fr';
  50. const styles = StyleSheet.create({
  51. category: {
  52. marginRight: 5,
  53. },
  54. categoryContainer: {
  55. flexDirection: 'row',
  56. marginTop: 5,
  57. },
  58. card: {
  59. marginTop: 10,
  60. },
  61. icon: {
  62. backgroundColor: 'transparent',
  63. },
  64. emailButton: {
  65. marginLeft: 'auto',
  66. },
  67. scroll: {
  68. paddingLeft: 5,
  69. paddingRight: 5,
  70. },
  71. imageButton: {
  72. width: 300,
  73. height: 300,
  74. marginLeft: 'auto',
  75. marginRight: 'auto',
  76. marginTop: 10,
  77. marginBottom: 10,
  78. },
  79. });
  80. /**
  81. * Class defining a club event information page.
  82. * If called with data and categories navigation parameters, will use those to display the data.
  83. * If called with clubId parameter, will fetch the information on the server
  84. */
  85. class ClubDisplayScreen extends React.Component<PropsType> {
  86. displayData: ClubType | null;
  87. categories: Array<ClubCategoryType> | null;
  88. clubId: number;
  89. shouldFetchData: boolean;
  90. constructor(props: PropsType) {
  91. super(props);
  92. this.displayData = null;
  93. this.categories = null;
  94. this.clubId = props.route.params?.clubId ? props.route.params.clubId : 0;
  95. this.shouldFetchData = true;
  96. if (
  97. props.route.params &&
  98. props.route.params.data &&
  99. props.route.params.categories
  100. ) {
  101. this.displayData = props.route.params.data;
  102. this.categories = props.route.params.categories;
  103. this.clubId = props.route.params.data.id;
  104. this.shouldFetchData = false;
  105. }
  106. }
  107. /**
  108. * Gets the name of the category with the given ID
  109. *
  110. * @param id The category's ID
  111. * @returns {string|*}
  112. */
  113. getCategoryName(id: number): string {
  114. let categoryName = '';
  115. if (this.categories !== null) {
  116. this.categories.forEach((item: ClubCategoryType) => {
  117. if (id === item.id) {
  118. categoryName = item.name;
  119. }
  120. });
  121. }
  122. return categoryName;
  123. }
  124. /**
  125. * Gets the view for rendering categories
  126. *
  127. * @param categories The categories to display (max 2)
  128. * @returns {null|*}
  129. */
  130. getCategoriesRender(categories: Array<number | null>) {
  131. if (this.categories == null) {
  132. return null;
  133. }
  134. const final: Array<React.ReactNode> = [];
  135. categories.forEach((cat: number | null) => {
  136. if (cat != null) {
  137. final.push(
  138. <Chip style={styles.category} key={cat}>
  139. {this.getCategoryName(cat)}
  140. </Chip>
  141. );
  142. }
  143. });
  144. return <View style={styles.categoryContainer}>{final}</View>;
  145. }
  146. /**
  147. * Gets the view for rendering club managers if any
  148. *
  149. * @param managers The list of manager names
  150. * @param email The club contact email
  151. * @returns {*}
  152. */
  153. getManagersRender(managers: Array<string>, email: string | null) {
  154. const { props } = this;
  155. const managersListView: Array<React.ReactNode> = [];
  156. managers.forEach((item: string) => {
  157. managersListView.push(<Paragraph key={item}>{item}</Paragraph>);
  158. });
  159. const hasManagers = managers.length > 0;
  160. return (
  161. <Card
  162. style={{
  163. marginBottom: TAB_BAR_HEIGHT + 20,
  164. ...styles.card,
  165. }}
  166. >
  167. <Card.Title
  168. title={i18n.t('screens.clubs.managers')}
  169. subtitle={
  170. hasManagers
  171. ? i18n.t('screens.clubs.managersSubtitle')
  172. : i18n.t('screens.clubs.managersUnavailable')
  173. }
  174. left={(iconProps) => (
  175. <Avatar.Icon
  176. size={iconProps.size}
  177. style={styles.icon}
  178. color={
  179. hasManagers
  180. ? props.theme.colors.success
  181. : props.theme.colors.primary
  182. }
  183. icon="account-tie"
  184. />
  185. )}
  186. />
  187. <Card.Content>
  188. {managersListView}
  189. {ClubDisplayScreen.getEmailButton(email, hasManagers)}
  190. </Card.Content>
  191. </Card>
  192. );
  193. }
  194. /**
  195. * Gets the email button to contact the club, or the amicale if the club does not have any managers
  196. *
  197. * @param email The club contact email
  198. * @param hasManagers True if the club has managers
  199. * @returns {*}
  200. */
  201. static getEmailButton(email: string | null, hasManagers: boolean) {
  202. const destinationEmail =
  203. email != null && hasManagers ? email : AMICALE_MAIL;
  204. const text =
  205. email != null && hasManagers
  206. ? i18n.t('screens.clubs.clubContact')
  207. : i18n.t('screens.clubs.amicaleContact');
  208. return (
  209. <Card.Actions>
  210. <Button
  211. icon="email"
  212. mode="contained"
  213. onPress={() => {
  214. Linking.openURL(`mailto:${destinationEmail}`);
  215. }}
  216. style={styles.emailButton}
  217. >
  218. {text}
  219. </Button>
  220. </Card.Actions>
  221. );
  222. }
  223. getScreen = (response: Array<ClubType | null>) => {
  224. let data: ClubType | null = response[0];
  225. if (data != null) {
  226. this.updateHeaderTitle(data);
  227. return (
  228. <CollapsibleScrollView style={styles.scroll} hasTab>
  229. {this.getCategoriesRender(data.category)}
  230. {data.logo !== null ? (
  231. <ImageGalleryButton
  232. images={[{ url: data.logo }]}
  233. style={styles.imageButton}
  234. />
  235. ) : (
  236. <View />
  237. )}
  238. {data.description !== null ? (
  239. // Surround description with div to allow text styling if the description is not html
  240. <Card.Content>
  241. <CustomHTML html={data.description} />
  242. </Card.Content>
  243. ) : (
  244. <View />
  245. )}
  246. {this.getManagersRender(data.responsibles, data.email)}
  247. </CollapsibleScrollView>
  248. );
  249. }
  250. return null;
  251. };
  252. /**
  253. * Updates the header title to match the given club
  254. *
  255. * @param data The club data
  256. */
  257. updateHeaderTitle(data: ClubType) {
  258. const { props } = this;
  259. props.navigation.setOptions({ title: data.name });
  260. }
  261. render() {
  262. const { props } = this;
  263. if (this.shouldFetchData) {
  264. return (
  265. <AuthenticatedScreen
  266. navigation={props.navigation}
  267. requests={[
  268. {
  269. link: 'clubs/info',
  270. params: { id: this.clubId },
  271. mandatory: true,
  272. },
  273. ]}
  274. renderFunction={this.getScreen}
  275. errorViewOverride={[
  276. {
  277. errorCode: ERROR_TYPE.BAD_INPUT,
  278. message: i18n.t('screens.clubs.invalidClub'),
  279. icon: 'account-question',
  280. showRetryButton: false,
  281. },
  282. ]}
  283. />
  284. );
  285. }
  286. return this.getScreen([this.displayData]);
  287. }
  288. }
  289. export default withTheme(ClubDisplayScreen);