123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- /*
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
- *
- * This file is part of Campus INSAT.
- *
- * Campus INSAT is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Campus INSAT is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
- */
-
- import React, { useLayoutEffect, useRef, useState } from 'react';
- import { Platform } from 'react-native';
- import { Searchbar } from 'react-native-paper';
- import i18n from 'i18n-js';
- import ClubListItem from '../../../components/Lists/Clubs/ClubListItem';
- import {
- isItemInCategoryFilter,
- stringMatchQuery,
- } from '../../../utils/Search';
- import ClubListHeader from '../../../components/Lists/Clubs/ClubListHeader';
- import MaterialHeaderButtons, {
- Item,
- } from '../../../components/Overrides/CustomHeaderButton';
- import WebSectionList from '../../../components/Screens/WebSectionList';
- import { useNavigation } from '@react-navigation/native';
- import { useAuthenticatedRequest } from '../../../context/loginContext';
-
- export type ClubCategoryType = {
- id: number;
- name: string;
- };
-
- export type ClubType = {
- id: number;
- name: string;
- description: string;
- logo: string;
- email: string | null;
- category: Array<number | null>;
- responsibles: Array<string>;
- };
-
- type ResponseType = {
- categories: Array<ClubCategoryType>;
- clubs: Array<ClubType>;
- };
-
- const LIST_ITEM_HEIGHT = 96;
-
- function ClubListScreen() {
- const navigation = useNavigation();
- const request = useAuthenticatedRequest<ResponseType>('clubs/list');
- const [
- currentlySelectedCategories,
- setCurrentlySelectedCategories,
- ] = useState<Array<number>>([]);
- const [currentSearchString, setCurrentSearchString] = useState('');
- const categories = useRef<Array<ClubCategoryType>>([]);
-
- useLayoutEffect(() => {
- const getSearchBar = () => {
- return (
- // @ts-ignore
- <Searchbar
- placeholder={i18n.t('screens.proximo.search')}
- onChangeText={onSearchStringChange}
- />
- );
- };
- const getHeaderButtons = () => {
- return (
- <MaterialHeaderButtons>
- <Item
- title="main"
- iconName="information"
- onPress={() => navigation.navigate('club-about')}
- />
- </MaterialHeaderButtons>
- );
- };
- navigation.setOptions({
- headerTitle: getSearchBar,
- headerRight: getHeaderButtons,
- headerBackTitleVisible: false,
- headerTitleContainerStyle:
- Platform.OS === 'ios'
- ? { marginHorizontal: 0, width: '70%' }
- : { marginHorizontal: 0, right: 50, left: 50 },
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [navigation]);
-
- const onSearchStringChange = (str: string) => {
- updateFilteredData(str, null);
- };
-
- /**
- * Callback used when clicking an article in the list.
- * It opens the modal to show detailed information about the article
- *
- * @param item The article pressed
- */
- const onListItemPress = (item: ClubType) => {
- navigation.navigate('club-information', {
- data: item,
- categories: categories.current,
- });
- };
-
- const onChipSelect = (id: number) => {
- updateFilteredData(null, id);
- };
-
- const createDataset = (data: ResponseType | undefined) => {
- if (data) {
- categories.current = data.categories;
- return [{ title: '', data: data.clubs }];
- } else {
- return [];
- }
- };
-
- /**
- * Gets the list header, with controls to change the categories filter
- *
- * @returns {*}
- */
- const getListHeader = (data: ResponseType | undefined) => {
- if (data) {
- return (
- <ClubListHeader
- categories={categories.current}
- selectedCategories={currentlySelectedCategories}
- onChipSelect={onChipSelect}
- />
- );
- } else {
- return null;
- }
- };
-
- const getCategoryOfId = (id: number): ClubCategoryType | null => {
- let cat = null;
- categories.current.forEach((item: ClubCategoryType) => {
- if (id === item.id) {
- cat = item;
- }
- });
- return cat;
- };
-
- const getRenderItem = ({ item }: { item: ClubType }) => {
- const onPress = () => {
- onListItemPress(item);
- };
- if (shouldRenderItem(item)) {
- return (
- <ClubListItem
- categoryTranslator={getCategoryOfId}
- item={item}
- onPress={onPress}
- height={LIST_ITEM_HEIGHT}
- />
- );
- }
- return null;
- };
-
- const keyExtractor = (item: ClubType): string => item.id.toString();
-
- /**
- * Updates the search string and category filter, saving them to the State.
- *
- * If the given category is already in the filter, it removes it.
- * Otherwise it adds it to the filter.
- *
- * @param filterStr The new filter string to use
- * @param categoryId The category to add/remove from the filter
- */
- const updateFilteredData = (
- filterStr: string | null,
- categoryId: number | null
- ) => {
- const newCategoriesState = [...currentlySelectedCategories];
- let newStrState = currentSearchString;
- if (filterStr !== null) {
- newStrState = filterStr;
- }
- if (categoryId !== null) {
- const index = newCategoriesState.indexOf(categoryId);
- if (index === -1) {
- newCategoriesState.push(categoryId);
- } else {
- newCategoriesState.splice(index, 1);
- }
- }
- if (filterStr !== null || categoryId !== null) {
- setCurrentSearchString(newStrState);
- setCurrentlySelectedCategories(newCategoriesState);
- }
- };
-
- /**
- * Checks if the given item should be rendered according to current name and category filters
- *
- * @param item The club to check
- * @returns {boolean}
- */
- const shouldRenderItem = (item: ClubType): boolean => {
- let shouldRender =
- currentlySelectedCategories.length === 0 ||
- isItemInCategoryFilter(currentlySelectedCategories, item.category);
- if (shouldRender) {
- shouldRender = stringMatchQuery(item.name, currentSearchString);
- }
- return shouldRender;
- };
-
- return (
- <WebSectionList
- request={request}
- createDataset={createDataset}
- keyExtractor={keyExtractor}
- renderItem={getRenderItem}
- renderListHeaderComponent={getListHeader}
- // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
- removeClippedSubviews={true}
- itemHeight={LIST_ITEM_HEIGHT}
- />
- );
- }
-
- export default ClubListScreen;
|