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

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