index.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import React, { useCallback, useEffect, useRef, useState } from 'react';
  2. import { View, Text, TouchableOpacity, FlatList } from 'react-native';
  3. import { useFocusEffect, useNavigation } from '@react-navigation/native';
  4. import { PageWrapper, Header, Modal, FlatList as List, WarningModal } from 'src/components';
  5. import TripItem from '../Components/TripItem';
  6. import { useGetTripsYearsQuery, useGetTripsForYearQuery } from '@api/trips';
  7. import { StoreType, storage } from 'src/storage';
  8. import { TripsData } from '../utils/types';
  9. import { NAVIGATION_PAGES } from 'src/types';
  10. import { styles } from './styles';
  11. import ChevronIcon from '../../../../../assets/icons/travels-screens/chevron-bottom.svg';
  12. import AddIcon from '../../../../../assets/icons/travels-screens/circle-plus.svg';
  13. import TripSvg from '../../../../../assets/icons/travels-screens/trip.svg';
  14. import InfoIcon from 'assets/icons/info-solid.svg';
  15. import { Colors } from 'src/theme';
  16. import ChevronLeftIcon from 'assets/icons/chevron-left.svg';
  17. import { getFontSize } from 'src/utils';
  18. import { FlashList, FlashListRef } from '@shopify/flash-list';
  19. import WarningIcon from 'assets/icons/warning.svg';
  20. import moment from 'moment';
  21. const TripsScreen = ({ route }: { route: any }) => {
  22. const token = storage.get('token', StoreType.STRING) as string;
  23. const navigation = useNavigation();
  24. const [isDatePickerVisible, setDatePickerVisible] = useState(false);
  25. const { data: years, refetch } = useGetTripsYearsQuery(token, true);
  26. const [selectedYear, setSelectedYear] = useState<string | null>(moment().year().toString());
  27. const { data: tripsData, refetch: refetchTrips } = useGetTripsForYearQuery(
  28. token,
  29. selectedYear as string,
  30. selectedYear ? true : false
  31. );
  32. const [trips, setTrips] = useState<TripsData[]>([]);
  33. const [statistics, setStatistics] = useState<{
  34. countries: {
  35. description: string;
  36. list: {
  37. id: number;
  38. country: string;
  39. days_spent: number;
  40. flag: string | null;
  41. regions: {
  42. id: number;
  43. region: string;
  44. days_spent: number;
  45. }[];
  46. }[];
  47. };
  48. general: [string];
  49. regions: {
  50. description: string;
  51. list: {
  52. id: number;
  53. days_spent: number;
  54. flag: string | null;
  55. flag1: string | null;
  56. flag2: string | null;
  57. region: string;
  58. }[];
  59. };
  60. dates_missing: 0 | 1;
  61. } | null>(null);
  62. const [isWarningModalVisible, setIsWarningModalVisible] = useState(false);
  63. const flashListRef = useRef<FlashListRef<any> | null>(null);
  64. useFocusEffect(
  65. useCallback(() => {
  66. const fetchData = async () => {
  67. try {
  68. await refetch();
  69. await refetchTrips();
  70. } catch (error) {
  71. console.error(error);
  72. }
  73. };
  74. if (route.params?.saved || route.params?.updated || route.params?.deleted) {
  75. fetchData();
  76. setIsWarningModalVisible(true);
  77. }
  78. }, [route.params])
  79. );
  80. useEffect(() => {
  81. if (years && years.data && !selectedYear) {
  82. setSelectedYear(years.data[0]);
  83. }
  84. }, [years]);
  85. useEffect(() => {
  86. if (!isWarningModalVisible) {
  87. navigation.setParams({ saved: false, updated: false, deleted: false } as never);
  88. }
  89. }, [isWarningModalVisible]);
  90. useEffect(() => {
  91. if (tripsData && tripsData.trips) {
  92. setStatistics(tripsData.statistics);
  93. setTrips(tripsData.trips);
  94. }
  95. }, [tripsData]);
  96. const renderItem = useCallback(
  97. ({ item }: { item: TripsData }) => (
  98. <TripItem item={item} isNew={Number(selectedYear) >= 2025} />
  99. ),
  100. [selectedYear]
  101. );
  102. const onAddNewTripPress = useCallback(() => {
  103. if (Number(selectedYear) >= 2025) {
  104. navigation.navigate(NAVIGATION_PAGES.ADD_TRIP_2025 as never);
  105. } else {
  106. navigation.navigate(NAVIGATION_PAGES.ADD_TRIP as never);
  107. }
  108. }, [navigation, selectedYear]);
  109. const handleScrollToTrip = () => {
  110. if (!flashListRef?.current) return;
  111. const index = trips.findIndex((trip) => trip.dates_missing === 1);
  112. if (index === -1) return;
  113. flashListRef.current.scrollToIndex({
  114. index,
  115. animated: true,
  116. viewPosition: 0
  117. });
  118. };
  119. return (
  120. <PageWrapper>
  121. <View
  122. style={{
  123. alignItems: 'center',
  124. paddingVertical: 16,
  125. flexDirection: 'row',
  126. justifyContent: 'center'
  127. }}
  128. >
  129. <Text
  130. style={{ color: Colors.DARK_BLUE, fontFamily: 'redhat-700', fontSize: getFontSize(14) }}
  131. >
  132. Trips
  133. </Text>
  134. </View>
  135. <View style={styles.tabContainer}>
  136. <TouchableOpacity style={styles.regionSelector} onPress={() => setDatePickerVisible(true)}>
  137. <Text style={[styles.regionText]}>{selectedYear}</Text>
  138. <ChevronIcon />
  139. </TouchableOpacity>
  140. <TouchableOpacity style={styles.addNewTab} onPress={onAddNewTripPress}>
  141. <AddIcon />
  142. <Text style={styles.addNewTabText}>Add New Trip</Text>
  143. </TouchableOpacity>
  144. </View>
  145. {trips.length === 0 ? (
  146. <View style={styles.noTripsContainer}>
  147. <View style={styles.noTripsIcon}>
  148. <TripSvg fill={Colors.WHITE} />
  149. </View>
  150. <Text style={styles.noTripsText}>No trips at the moment</Text>
  151. </View>
  152. ) : (
  153. <FlashList
  154. ref={flashListRef}
  155. data={trips}
  156. renderItem={renderItem}
  157. keyExtractor={(item, index) => `${item.id}-${index}`}
  158. style={styles.tripsList}
  159. contentContainerStyle={styles.tripsListContentContainer}
  160. showsVerticalScrollIndicator={false}
  161. ListHeaderComponent={
  162. Number(selectedYear) >= 2025 ? (
  163. <View
  164. style={{
  165. marginTop: 12,
  166. marginBottom: 8,
  167. backgroundColor: Colors.FILL_LIGHT,
  168. borderRadius: 8,
  169. paddingHorizontal: 16,
  170. paddingVertical: 12,
  171. gap: 6
  172. }}
  173. >
  174. {statistics?.general ? (
  175. <Text style={{ fontSize: 14, fontWeight: '600', color: Colors.DARK_BLUE }}>
  176. {statistics.general}
  177. </Text>
  178. ) : null}
  179. <TouchableOpacity
  180. onPress={() =>
  181. navigation.navigate(
  182. ...([
  183. NAVIGATION_PAGES.REGIONS_VISITED,
  184. {
  185. title: statistics?.countries?.description,
  186. isRegion: false,
  187. data: statistics?.countries?.list
  188. }
  189. ] as never)
  190. )
  191. }
  192. hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
  193. >
  194. <Text style={{ fontSize: 14, fontWeight: '600', color: Colors.ORANGE }}>
  195. {statistics?.countries?.description}
  196. </Text>
  197. </TouchableOpacity>
  198. <TouchableOpacity
  199. onPress={() =>
  200. navigation.navigate(
  201. ...([
  202. NAVIGATION_PAGES.REGIONS_VISITED,
  203. {
  204. title: statistics?.regions?.description,
  205. isRegion: true,
  206. data: statistics?.regions?.list
  207. }
  208. ] as never)
  209. )
  210. }
  211. hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
  212. >
  213. <Text style={{ fontSize: 14, fontWeight: '600', color: Colors.ORANGE }}>
  214. {statistics?.regions?.description}
  215. </Text>
  216. </TouchableOpacity>
  217. {statistics?.dates_missing ? (
  218. <TouchableOpacity
  219. onPress={handleScrollToTrip}
  220. hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
  221. style={{ marginTop: 8, flexDirection: 'row', gap: 8, alignItems: 'center' }}
  222. >
  223. <WarningIcon color={Colors.RED} width={20} height={20} />
  224. <Text style={{ fontSize: 14, fontWeight: '600', color: Colors.RED }}>
  225. For accurate annual statistics, please enter exact dates for all trips.
  226. </Text>
  227. </TouchableOpacity>
  228. ) : null}
  229. </View>
  230. ) : null
  231. }
  232. />
  233. )}
  234. <Modal
  235. onRequestClose={() => setDatePickerVisible(false)}
  236. headerTitle={'Select Year'}
  237. visible={isDatePickerVisible}
  238. >
  239. <List
  240. itemObject={(object) => {
  241. setSelectedYear(object);
  242. setDatePickerVisible(false);
  243. }}
  244. initialData={years?.data}
  245. date={true}
  246. />
  247. </Modal>
  248. <WarningModal
  249. type={'success'}
  250. isVisible={isWarningModalVisible}
  251. onClose={() => {
  252. setIsWarningModalVisible(false);
  253. }}
  254. title={
  255. route.params?.saved
  256. ? 'Trip added'
  257. : route.params?.deleted
  258. ? 'Trip deleted'
  259. : 'Trip updated'
  260. }
  261. message={
  262. route.params?.saved
  263. ? 'Trip was successfully added to your list of trips'
  264. : route.params?.deleted
  265. ? 'This trip was successfully deleted.'
  266. : 'Trip was successfully updated'
  267. }
  268. />
  269. </PageWrapper>
  270. );
  271. };
  272. export default TripsScreen;