index.tsx 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import React, { useCallback, useEffect, useRef, useState } from 'react';
  2. import {
  3. View,
  4. Text,
  5. Image,
  6. TouchableOpacity,
  7. ScrollView,
  8. LayoutAnimation,
  9. findNodeHandle
  10. } from 'react-native';
  11. import { FlashList } from '@shopify/flash-list';
  12. import { CommonActions, useFocusEffect, useNavigation } from '@react-navigation/native';
  13. import { styles } from './styles';
  14. import { NAVIGATION_PAGES } from 'src/types';
  15. import { StoreType, storage } from 'src/storage';
  16. import { Header, Input, PageWrapper } from 'src/components';
  17. import { Colors } from 'src/theme';
  18. import SearchIcon from 'assets/icons/search.svg';
  19. import CalendarIcon from 'assets/icons/events/calendar-solid.svg';
  20. import EarthIcon from 'assets/icons/travels-section/earth.svg';
  21. import NomadsIcon from 'assets/icons/bottom-navigation/travellers.svg';
  22. import CalendarPlusIcon from 'assets/icons/events/calendar-plus.svg';
  23. import ShoppingCartIcon from 'assets/icons/events/shopping-cart.svg';
  24. import { SingleEvent, useGetCanAddEventQuery, useGetEventsListQuery } from '@api/events';
  25. import moment from 'moment';
  26. import { API_HOST } from 'src/constants';
  27. import { Grayscale } from 'react-native-color-matrix-image-filters';
  28. import { renderSpotsText } from './utils';
  29. import ChevronIcon from 'assets/icons/chevron-left.svg';
  30. const EventsScreen = () => {
  31. const token = (storage.get('token', StoreType.STRING) as string) ?? null;
  32. const { data, refetch } = useGetEventsListQuery(token, 0, true);
  33. const { data: pastData } = useGetEventsListQuery(token, 1, true);
  34. const { data: canAddEvent } = useGetCanAddEventQuery(token, true);
  35. const navigation = useNavigation();
  36. const [searchQuery, setSearchQuery] = useState('');
  37. const [events, setEvents] = useState<SingleEvent[]>([]);
  38. const [pastEvents, setPastEvents] = useState<SingleEvent[]>([]);
  39. const [filteredEvents, setFilteredEvents] = useState<SingleEvent[]>([]);
  40. const [filteredPastEvents, setFilteredPastEvents] = useState<SingleEvent[]>([]);
  41. const date = new Date();
  42. const [isExpanded, setIsExpanded] = useState(false);
  43. const scrollViewRef = useRef<ScrollView>(null);
  44. const sectionRef = useRef<View>(null);
  45. const toggleExpand = () => {
  46. LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut, () => {
  47. if (!isExpanded && sectionRef.current && scrollViewRef.current) {
  48. sectionRef.current.measureLayout(
  49. findNodeHandle(scrollViewRef.current)!,
  50. (x, y) => {
  51. scrollViewRef.current?.scrollTo({ y, animated: true });
  52. },
  53. () => console.warn('events measureLayout error')
  54. );
  55. }
  56. });
  57. setIsExpanded(!isExpanded);
  58. };
  59. useEffect(() => {
  60. if (data && data.data) {
  61. setEvents(data.data);
  62. setFilteredEvents(data.data);
  63. }
  64. }, [data]);
  65. useEffect(() => {
  66. if (pastData && pastData.data) {
  67. setPastEvents(pastData.data);
  68. setFilteredPastEvents(pastData.data);
  69. }
  70. }, [pastData]);
  71. useFocusEffect(
  72. useCallback(() => {
  73. refetch();
  74. }, [navigation])
  75. );
  76. const handleSearch = (text: string) => {
  77. if (text) {
  78. const searchData =
  79. events.filter((item: any) => {
  80. const itemData = item.name ? item.name.toLowerCase() : ''.toLowerCase();
  81. const textData = text.toLowerCase();
  82. return itemData.indexOf(textData) > -1;
  83. }) ?? [];
  84. setFilteredEvents(searchData);
  85. const searchPastData =
  86. pastEvents.filter((item: any) => {
  87. const itemData = item.name ? item.name.toLowerCase() : ''.toLowerCase();
  88. const textData = text.toLowerCase();
  89. return itemData.indexOf(textData) > -1;
  90. }) ?? [];
  91. setFilteredPastEvents(searchPastData);
  92. setSearchQuery(text);
  93. } else {
  94. setFilteredEvents(events);
  95. setFilteredPastEvents(pastEvents);
  96. setSearchQuery(text);
  97. }
  98. };
  99. const formatEventDate = (event: SingleEvent) => {
  100. if (event.date_from && event.date_to) {
  101. return `${moment(event.date_from, 'YYYY-MM-DD').format('DD MMM YYYY')} - ${moment(event.date_to, 'YYYY-MM-DD').format('DD MMM YYYY')}`;
  102. } else {
  103. return moment(event.date, 'YYYY-MM-DD').format('DD MMMM YYYY');
  104. }
  105. };
  106. const renderEventCard = ({ item }: { item: SingleEvent }) => {
  107. let staticImgUrl = '/static/img/events/meeting.webp';
  108. let badgeColor = Colors.DARK_BLUE;
  109. let badgeText = '';
  110. if (item.full) {
  111. badgeColor = Colors.LIGHT_GRAY;
  112. badgeText = 'FULL';
  113. } else if (item.type === 2) {
  114. badgeColor = Colors.ORANGE;
  115. badgeText = 'TOUR';
  116. staticImgUrl = '/static/img/events/trip.webp';
  117. } else if (item.type === 3) {
  118. badgeColor = Colors.DARK_BLUE;
  119. badgeText = 'CONF';
  120. staticImgUrl = '/static/img/events/conference.webp';
  121. }
  122. const photo = item.photo
  123. ? API_HOST + '/webapi/events/get-square-photo/' + item.id + '?cacheBust=' + date
  124. : API_HOST + staticImgUrl;
  125. return (
  126. <Grayscale amount={item.active === 0 ? 1 : 0}>
  127. <TouchableOpacity
  128. style={[
  129. styles.card,
  130. item.type === 2 || item.type === 3 || item.full
  131. ? { backgroundColor: Colors.FILL_LIGHT }
  132. : { backgroundColor: Colors.WHITE }
  133. ]}
  134. onPress={() =>
  135. navigation.navigate(...([NAVIGATION_PAGES.EVENT, { url: item.url }] as never))
  136. }
  137. disabled={item.active === 0}
  138. >
  139. <View style={styles.imageWrapper}>
  140. <Image source={{ uri: photo }} style={styles.image} resizeMode="cover" />
  141. {/* {item.isPaid && (
  142. <View style={styles.iconOverlay}>
  143. <ShoppingCartIcon fill={Colors.WHITE} width={12} />
  144. </View>
  145. )} */}
  146. {item.joined ? (
  147. <View style={styles.joinedOverlay}>
  148. <Text style={{ color: Colors.WHITE, fontSize: 12, fontFamily: 'redhat-700' }}>
  149. Joined
  150. </Text>
  151. </View>
  152. ) : null}
  153. </View>
  154. <View style={styles.info}>
  155. <Text style={styles.title} numberOfLines={2}>
  156. {item.name}
  157. </Text>
  158. <View style={[styles.row]}>
  159. <View style={styles.row}>
  160. <CalendarIcon fill={Colors.DARK_BLUE} height={14} width={14} />
  161. <Text style={[styles.dateAndLocation, { flex: 0 }]} numberOfLines={1}>
  162. {formatEventDate(item)}
  163. </Text>
  164. </View>
  165. </View>
  166. <View style={styles.row}>
  167. <EarthIcon fill={Colors.DARK_BLUE} height={14} width={14} />
  168. <Text style={styles.dateAndLocation} numberOfLines={1}>
  169. {item.address1}
  170. </Text>
  171. </View>
  172. {item.registrations_info !== 1 && (
  173. <View style={styles.row}>
  174. <NomadsIcon fill={Colors.DARK_BLUE} height={14} width={14} />
  175. <Text style={styles.dateAndLocation} numberOfLines={1}>
  176. {renderSpotsText(item)}
  177. </Text>
  178. </View>
  179. )}
  180. </View>
  181. {item.type !== 1 || item.full ? (
  182. <View
  183. style={[
  184. styles.statusBadge,
  185. { backgroundColor: item.active ? badgeColor : Colors.LIGHT_GRAY }
  186. ]}
  187. >
  188. <View style={styles.rotatedContainer}>
  189. <Text style={styles.statusText}>{badgeText}</Text>
  190. </View>
  191. </View>
  192. ) : null}
  193. </TouchableOpacity>
  194. </Grayscale>
  195. );
  196. };
  197. return (
  198. <PageWrapper>
  199. <Header
  200. label="Events"
  201. rightElement={
  202. canAddEvent?.can ? (
  203. <TouchableOpacity
  204. onPress={() => navigation.navigate(NAVIGATION_PAGES.CREATE_EVENT as never)}
  205. style={{ width: 30 }}
  206. >
  207. <CalendarPlusIcon fill={Colors.DARK_BLUE} />
  208. </TouchableOpacity>
  209. ) : null
  210. }
  211. />
  212. <ScrollView ref={scrollViewRef} nestedScrollEnabled showsVerticalScrollIndicator={false}>
  213. <View style={styles.searchContainer}>
  214. <Input
  215. inputMode={'search'}
  216. placeholder={'Search'}
  217. onChange={(text) => handleSearch(text)}
  218. value={searchQuery}
  219. icon={<SearchIcon fill={'#C8C8C8'} width={14} height={14} />}
  220. height={38}
  221. />
  222. </View>
  223. <FlashList
  224. data={filteredEvents}
  225. scrollEnabled={false}
  226. keyExtractor={(item) => item.id.toString()}
  227. renderItem={renderEventCard}
  228. estimatedItemSize={100}
  229. contentContainerStyle={styles.listContainer}
  230. showsVerticalScrollIndicator={false}
  231. />
  232. {filteredPastEvents && filteredPastEvents.length ? (
  233. <View ref={sectionRef} style={styles.sectionContainer}>
  234. <TouchableOpacity onPress={toggleExpand} style={styles.header}>
  235. <View style={styles.headerContainer}>
  236. <Text style={styles.headerText}>Past Events</Text>
  237. </View>
  238. <View style={styles.chevronContainer}>
  239. <ChevronIcon
  240. fill={Colors.DARK_BLUE}
  241. style={[styles.headerIcon, isExpanded ? styles.rotate : null]}
  242. />
  243. </View>
  244. </TouchableOpacity>
  245. {isExpanded ? (
  246. <FlashList
  247. data={filteredPastEvents}
  248. scrollEnabled={false}
  249. keyExtractor={(item) => item.id.toString()}
  250. renderItem={renderEventCard}
  251. estimatedItemSize={100}
  252. contentContainerStyle={styles.listContainer}
  253. showsVerticalScrollIndicator={false}
  254. />
  255. ) : null}
  256. </View>
  257. ) : null}
  258. </ScrollView>
  259. </PageWrapper>
  260. );
  261. };
  262. export default EventsScreen;