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.

ClubListScreen.tsx 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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, { useLayoutEffect, useRef, useState } from 'react';
  20. import { Platform } from 'react-native';
  21. import { Searchbar } from 'react-native-paper';
  22. import i18n from 'i18n-js';
  23. import ClubListItem from '../../../components/Lists/Clubs/ClubListItem';
  24. import {
  25. isItemInCategoryFilter,
  26. stringMatchQuery,
  27. } from '../../../utils/Search';
  28. import ClubListHeader from '../../../components/Lists/Clubs/ClubListHeader';
  29. import MaterialHeaderButtons, {
  30. Item,
  31. } from '../../../components/Overrides/CustomHeaderButton';
  32. import WebSectionList from '../../../components/Screens/WebSectionList';
  33. import { useNavigation } from '@react-navigation/native';
  34. import { useAuthenticatedRequest } from '../../../context/loginContext';
  35. export type ClubCategoryType = {
  36. id: number;
  37. name: string;
  38. };
  39. export type ClubType = {
  40. id: number;
  41. name: string;
  42. description: string;
  43. logo: string;
  44. email: string | null;
  45. category: Array<number | null>;
  46. responsibles: Array<string>;
  47. };
  48. type ResponseType = {
  49. categories: Array<ClubCategoryType>;
  50. clubs: Array<ClubType>;
  51. };
  52. const LIST_ITEM_HEIGHT = 96;
  53. function ClubListScreen() {
  54. const navigation = useNavigation();
  55. const request = useAuthenticatedRequest<ResponseType>('clubs/list');
  56. const [
  57. currentlySelectedCategories,
  58. setCurrentlySelectedCategories,
  59. ] = useState<Array<number>>([]);
  60. const [currentSearchString, setCurrentSearchString] = useState('');
  61. const categories = useRef<Array<ClubCategoryType>>([]);
  62. useLayoutEffect(() => {
  63. const getSearchBar = () => {
  64. return (
  65. // @ts-ignore
  66. <Searchbar
  67. placeholder={i18n.t('screens.proximo.search')}
  68. onChangeText={onSearchStringChange}
  69. />
  70. );
  71. };
  72. const getHeaderButtons = () => {
  73. return (
  74. <MaterialHeaderButtons>
  75. <Item
  76. title="main"
  77. iconName="information"
  78. onPress={() => navigation.navigate('club-about')}
  79. />
  80. </MaterialHeaderButtons>
  81. );
  82. };
  83. navigation.setOptions({
  84. headerTitle: getSearchBar,
  85. headerRight: getHeaderButtons,
  86. headerBackTitleVisible: false,
  87. headerTitleContainerStyle:
  88. Platform.OS === 'ios'
  89. ? { marginHorizontal: 0, width: '70%' }
  90. : { marginHorizontal: 0, right: 50, left: 50 },
  91. });
  92. // eslint-disable-next-line react-hooks/exhaustive-deps
  93. }, [navigation]);
  94. const onSearchStringChange = (str: string) => {
  95. updateFilteredData(str, null);
  96. };
  97. /**
  98. * Callback used when clicking an article in the list.
  99. * It opens the modal to show detailed information about the article
  100. *
  101. * @param item The article pressed
  102. */
  103. const onListItemPress = (item: ClubType) => {
  104. navigation.navigate('club-information', {
  105. data: item,
  106. categories: categories.current,
  107. });
  108. };
  109. const onChipSelect = (id: number) => {
  110. updateFilteredData(null, id);
  111. };
  112. const createDataset = (data: ResponseType | undefined) => {
  113. if (data) {
  114. categories.current = data.categories;
  115. return [{ title: '', data: data.clubs }];
  116. } else {
  117. return [];
  118. }
  119. };
  120. /**
  121. * Gets the list header, with controls to change the categories filter
  122. *
  123. * @returns {*}
  124. */
  125. const getListHeader = (data: ResponseType | undefined) => {
  126. if (data) {
  127. return (
  128. <ClubListHeader
  129. categories={categories.current}
  130. selectedCategories={currentlySelectedCategories}
  131. onChipSelect={onChipSelect}
  132. />
  133. );
  134. } else {
  135. return null;
  136. }
  137. };
  138. const getCategoryOfId = (id: number): ClubCategoryType | null => {
  139. let cat = null;
  140. categories.current.forEach((item: ClubCategoryType) => {
  141. if (id === item.id) {
  142. cat = item;
  143. }
  144. });
  145. return cat;
  146. };
  147. const getRenderItem = ({ item }: { item: ClubType }) => {
  148. const onPress = () => {
  149. onListItemPress(item);
  150. };
  151. if (shouldRenderItem(item)) {
  152. return (
  153. <ClubListItem
  154. categoryTranslator={getCategoryOfId}
  155. item={item}
  156. onPress={onPress}
  157. height={LIST_ITEM_HEIGHT}
  158. />
  159. );
  160. }
  161. return null;
  162. };
  163. const keyExtractor = (item: ClubType): string => item.id.toString();
  164. /**
  165. * Updates the search string and category filter, saving them to the State.
  166. *
  167. * If the given category is already in the filter, it removes it.
  168. * Otherwise it adds it to the filter.
  169. *
  170. * @param filterStr The new filter string to use
  171. * @param categoryId The category to add/remove from the filter
  172. */
  173. const updateFilteredData = (
  174. filterStr: string | null,
  175. categoryId: number | null
  176. ) => {
  177. const newCategoriesState = [...currentlySelectedCategories];
  178. let newStrState = currentSearchString;
  179. if (filterStr !== null) {
  180. newStrState = filterStr;
  181. }
  182. if (categoryId !== null) {
  183. const index = newCategoriesState.indexOf(categoryId);
  184. if (index === -1) {
  185. newCategoriesState.push(categoryId);
  186. } else {
  187. newCategoriesState.splice(index, 1);
  188. }
  189. }
  190. if (filterStr !== null || categoryId !== null) {
  191. setCurrentSearchString(newStrState);
  192. setCurrentlySelectedCategories(newCategoriesState);
  193. }
  194. };
  195. /**
  196. * Checks if the given item should be rendered according to current name and category filters
  197. *
  198. * @param item The club to check
  199. * @returns {boolean}
  200. */
  201. const shouldRenderItem = (item: ClubType): boolean => {
  202. let shouldRender =
  203. currentlySelectedCategories.length === 0 ||
  204. isItemInCategoryFilter(currentlySelectedCategories, item.category);
  205. if (shouldRender) {
  206. shouldRender = stringMatchQuery(item.name, currentSearchString);
  207. }
  208. return shouldRender;
  209. };
  210. return (
  211. <WebSectionList
  212. request={request}
  213. createDataset={createDataset}
  214. keyExtractor={keyExtractor}
  215. renderItem={getRenderItem}
  216. renderListHeaderComponent={getListHeader}
  217. // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
  218. removeClippedSubviews={true}
  219. itemHeight={LIST_ITEM_HEIGHT}
  220. />
  221. );
  222. }
  223. export default ClubListScreen;