index.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. import React, { FC, useCallback, useEffect, useState } from 'react';
  2. import { View, Text, Image, TouchableOpacity, Platform } from 'react-native';
  3. import ImageView from 'better-react-native-image-viewing';
  4. import { styles } from './styles';
  5. import {
  6. Button,
  7. EditNmModal,
  8. HorizontalTabView,
  9. Loading,
  10. Modal as ReactModal
  11. } from 'src/components';
  12. import { useFocusEffect } from '@react-navigation/native';
  13. import { Colors } from 'src/theme';
  14. import { styles as ButtonStyles } from 'src/components/RegionPopup/style';
  15. import { usePostSetToggleItem } from '@api/series';
  16. import { ScrollView } from 'react-native-gesture-handler';
  17. import { NAVIGATION_PAGES } from 'src/types';
  18. import { API_HOST } from 'src/constants';
  19. import { useGetDareRegionDataQuery, useGetNmRegionDataQuery } from '@api/regions';
  20. import { StoreType, storage } from 'src/storage';
  21. import { ButtonVariants } from 'src/types/components';
  22. import { qualityOptions } from '../../TravelsScreen/utils/constants';
  23. import moment from 'moment';
  24. import { useRegion } from 'src/contexts/RegionContext';
  25. import formatNumber from '../../TravelsScreen/utils/formatNumber';
  26. import { PhotosData, Props, SeriesData, SeriesGroup, SeriesItem } from './types';
  27. import ImageCarousel from './ImageCarousel';
  28. import TravelSeriesList from './TravelSeriesList';
  29. import MarkIcon from 'assets/icons/mark.svg';
  30. import ChevronLeft from 'assets/icons/chevron-left.svg';
  31. import CaseSvg from 'assets/icons/briefcase.svg';
  32. import HouseSvg from 'assets/icons/house.svg';
  33. import EditSvg from 'assets/icons/travels-screens/pen-to-square.svg';
  34. import CalendarSvg from 'assets/icons/travels-screens/calendar.svg';
  35. import RotateSvg from 'assets/icons/travels-screens/rotate.svg';
  36. const RegionViewScreen: FC<Props> = ({ navigation, route }) => {
  37. const regionId = route.params?.regionId;
  38. const type = route.params?.type;
  39. const disabled = route.params?.disabled;
  40. const token = storage.get('token', StoreType.STRING) as string;
  41. const [isLoading, setIsLoading] = useState(true);
  42. const [isModalVisible, setModalVisible] = useState(false);
  43. const [currentImageIndex, setCurrentImageIndex] = useState(0);
  44. const [activeIndex, setActiveIndex] = useState(0);
  45. const [indexSeries, setIndexSeries] = useState(0);
  46. const [routes, setRoutes] = useState<SeriesGroup[]>([]);
  47. const [series, setSeries] = useState<SeriesData[]>([]);
  48. const [photos, setPhotos] = useState<PhotosData[]>([]);
  49. const [name, setName] = useState(['', '']);
  50. const { data } =
  51. type === 'nm'
  52. ? useGetNmRegionDataQuery(regionId, type === 'nm', token && token)
  53. : useGetDareRegionDataQuery(regionId, type === 'dare', token && token);
  54. const { mutate: updateSeriesItem } = usePostSetToggleItem();
  55. const [isInfoModalVisible, setIsInfoModalVisible] = useState<boolean>(false);
  56. const [infoItem, setInfoItem] = useState<SeriesItem | null>(null);
  57. const [isEditModalVisible, setIsEditModalVisible] = useState(false);
  58. const [modalState, setModalState] = useState({
  59. selectedFirstYear: 2021,
  60. selectedLastYear: 2021,
  61. selectedQuality: qualityOptions[2],
  62. selectedNoOfVisits: 1,
  63. years: [],
  64. id: regionId
  65. });
  66. const {
  67. handleUpdateNM: updateNM,
  68. handleUpdateDare: updateDare,
  69. userData: regionData,
  70. setUserData: setRegionData,
  71. handleUpdateNMList,
  72. handleUpdateDareList
  73. } = useRegion();
  74. useEffect(() => {
  75. navigation.getParent()?.setOptions({
  76. tabBarStyle: {
  77. display: 'flex',
  78. ...Platform.select({
  79. android: {
  80. height: 58
  81. }
  82. })
  83. }
  84. });
  85. }, [navigation]);
  86. useFocusEffect(
  87. useCallback(() => {
  88. const fetchGroups = async () => {
  89. let staticGroups = [
  90. {
  91. key: 'all',
  92. title: 'All',
  93. series_id: 0
  94. }
  95. ];
  96. const routesData = data?.data?.series?.map((item) => ({
  97. key: item.series_id?.toString(),
  98. title: item.series_name,
  99. series_id: item.series_id,
  100. icon: item.icon,
  101. items: item.items
  102. }));
  103. routesData && staticGroups.push(...routesData);
  104. setPhotos(
  105. data?.data?.photos?.map((item) => ({
  106. ...item,
  107. uriSmall: `${API_HOST}/ajax/pic/${item.id}/small`,
  108. uri: `${API_HOST}/ajax/pic/${item.id}/full`
  109. })) ?? []
  110. );
  111. const [regionName, ...rest] = data?.data?.region_name?.split(/ – | - /) ?? [];
  112. const subname = rest?.join(' - ');
  113. setName([regionName, subname]);
  114. setSeries(data?.data?.series || []);
  115. setRoutes(staticGroups);
  116. setIsLoading(false);
  117. };
  118. if (data && data.result === 'OK') {
  119. fetchGroups();
  120. }
  121. }, [data])
  122. );
  123. const handleCheckboxChange = useCallback(
  124. async (item: SeriesItem, double: boolean, seriesId: number) => {
  125. setSeries((currentData) => {
  126. const groupIndex = currentData.findIndex((group) => group?.series_id === seriesId);
  127. if (groupIndex === -1) return currentData;
  128. const newData = [...currentData];
  129. const newGroup = { ...newData[groupIndex] };
  130. newGroup.items = newGroup.items.map((subItem) =>
  131. subItem.id === item.id
  132. ? {
  133. ...subItem,
  134. ...(double
  135. ? { visited_double: subItem.visited_double === 0 ? 1 : 0 }
  136. : { visited: subItem.visited === 0 ? 1 : 0 })
  137. }
  138. : subItem
  139. );
  140. newData[groupIndex] = newGroup;
  141. return newData;
  142. });
  143. const itemData = {
  144. token: token,
  145. series_id: seriesId,
  146. item_id: item.id,
  147. checked: (item.visited === 1 ? 0 : 1) as 0 | 1,
  148. double: (double && !item.visited_double ? 1 : 0) as 0 | 1
  149. };
  150. try {
  151. updateSeriesItem(itemData);
  152. } catch (error) {
  153. console.error('Failed to update checkbox state', error);
  154. }
  155. },
  156. [token, updateSeriesItem]
  157. );
  158. const handleModalStateChange = (updates: { [key: string]: any }) => {
  159. setModalState((prevState) => ({ ...prevState, ...updates }));
  160. };
  161. useEffect(() => {
  162. const currentYear = moment().year();
  163. let yearSelector: { label: string; value: number }[] = [{ label: 'visited', value: 1 }];
  164. for (let i = currentYear; i >= 1951; i--) {
  165. yearSelector.push({ label: i.toString(), value: i });
  166. }
  167. handleModalStateChange({ years: yearSelector });
  168. }, []);
  169. const openModal = (index: number) => {
  170. setCurrentImageIndex(index);
  171. setModalVisible(true);
  172. };
  173. if (isLoading) return <Loading />;
  174. const handleOpenEditModal = () => {
  175. handleModalStateChange({
  176. selectedFirstYear: regionData?.first_visit_year,
  177. selectedLastYear: regionData?.last_visit_year,
  178. selectedQuality:
  179. qualityOptions.find((quality) => quality.id === regionData?.best_visit_quality) ||
  180. qualityOptions[2],
  181. selectedNoOfVisits: regionData?.no_of_visits || 1,
  182. id: regionId
  183. });
  184. setIsEditModalVisible(true);
  185. };
  186. const handleUpdateNmModal = (
  187. region: number = regionId,
  188. first: number,
  189. last: number,
  190. visits: number,
  191. quality: number
  192. ) => {
  193. const updatedNM = {
  194. ...regionData,
  195. first_visit_year: first,
  196. last_visit_year: last,
  197. best_visit_quality: quality,
  198. no_of_visits: visits,
  199. visited: visits > 0 ? true : false
  200. };
  201. route.params?.isTravelsScreen
  202. ? handleUpdateNMList(region, first, last, visits, quality)
  203. : updateNM(region, first, last, visits, quality);
  204. updatedNM && setRegionData(updatedNM);
  205. };
  206. const handleUpdateNm = () => {
  207. route.params?.isTravelsScreen
  208. ? handleUpdateNMList(
  209. regionId,
  210. regionData.visited ? 0 : 1,
  211. regionData.visited ? 0 : 1,
  212. regionData.visited ? 0 : 1,
  213. 3
  214. )
  215. : updateNM(
  216. regionId,
  217. regionData.visited ? 0 : 1,
  218. regionData.visited ? 0 : 1,
  219. regionData.visited ? 0 : 1,
  220. 3
  221. );
  222. setRegionData({
  223. ...regionData,
  224. first_visit_year: regionData.visited ? 0 : 1,
  225. last_visit_year: regionData.visited ? 0 : 1,
  226. best_visit_quality: 3,
  227. no_of_visits: regionData.visited ? 0 : 1,
  228. visited: !regionData.visited
  229. });
  230. };
  231. const handleUpdateDare = () => {
  232. route.params?.isTravelsScreen
  233. ? handleUpdateDareList(regionId, regionData.visited ? 0 : 1)
  234. : updateDare(regionId, regionData.visited ? 0 : 1);
  235. setRegionData({
  236. ...regionData,
  237. first_visit_year: regionData.visited ? 0 : 1,
  238. last_visit_year: regionData.visited ? 0 : 1,
  239. best_visit_quality: 3,
  240. no_of_visits: regionData.visited ? 0 : 1,
  241. visited: !regionData.visited
  242. });
  243. };
  244. return (
  245. <View style={styles.container}>
  246. <TouchableOpacity
  247. onPress={() => {
  248. navigation.goBack();
  249. }}
  250. style={styles.backButton}
  251. >
  252. <View style={styles.chevronWrapper}>
  253. <ChevronLeft fill={Colors.WHITE} />
  254. </View>
  255. </TouchableOpacity>
  256. <ScrollView
  257. contentContainerStyle={{ flexGrow: 1 }}
  258. nestedScrollEnabled={true}
  259. showsVerticalScrollIndicator={false}
  260. >
  261. {photos.length > 0 ? (
  262. <ImageCarousel
  263. photos={photos}
  264. activeIndex={activeIndex}
  265. setActiveIndex={setActiveIndex}
  266. openModal={openModal}
  267. />
  268. ) : (
  269. <View style={styles.emptyImage}>
  270. <Image
  271. source={require('../../../../../assets/images/logo-opacity.png')}
  272. style={{ width: 100, height: 100 }}
  273. />
  274. <Text style={styles.emptyImageText}>No image available at this location</Text>
  275. </View>
  276. )}
  277. <View style={styles.wrapper}>
  278. <View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
  279. {regionData?.visited && regionData?.first_visit_year > 1 && !disabled && (
  280. <View style={styles.infoContent}>
  281. <CalendarSvg height={18} width={18} fill={Colors.DARK_BLUE} />
  282. <Text style={styles.visitedButtonText}>{regionData?.first_visit_year}</Text>
  283. </View>
  284. )}
  285. {regionData?.visited && type === 'nm' && !disabled && (
  286. <View style={styles.infoContent}>
  287. <RotateSvg fill={Colors.DARK_BLUE} />
  288. <Text style={styles.visitedButtonText}>
  289. {regionData.no_of_visits >= 10 ? '10+' : regionData.no_of_visits}
  290. </Text>
  291. </View>
  292. )}
  293. </View>
  294. <View style={styles.nameContainer}>
  295. <Text style={styles.title}>{name[0]}</Text>
  296. <View style={ButtonStyles.btnContainer}>
  297. {regionData?.visited && type === 'nm' && !disabled ? (
  298. <TouchableOpacity onPress={handleOpenEditModal} style={ButtonStyles.editBtn}>
  299. <EditSvg width={14} height={14} />
  300. </TouchableOpacity>
  301. ) : null}
  302. {!disabled ? (
  303. <TouchableOpacity
  304. style={[
  305. ButtonStyles.btn,
  306. regionData?.visited && !disabled
  307. ? ButtonStyles.visitedButton
  308. : ButtonStyles.markVisitedButton
  309. ]}
  310. onPress={() => (type === 'nm' ? handleUpdateNm() : handleUpdateDare())}
  311. >
  312. {regionData?.visited ? (
  313. <View style={ButtonStyles.visitedContainer}>
  314. <MarkIcon width={16} height={16} />
  315. <Text style={ButtonStyles.visitedButtonText}>Visited</Text>
  316. </View>
  317. ) : (
  318. <Text style={[ButtonStyles.markVisitedButtonText]}>Mark Visited</Text>
  319. )}
  320. </TouchableOpacity>
  321. ) : null}
  322. </View>
  323. </View>
  324. <Text style={styles.subtitle}>{name[1]}</Text>
  325. <View style={styles.divider} />
  326. <View style={styles.stats}>
  327. {data?.data.users_from_region_count ?? 0 > 0 ? (
  328. <TouchableOpacity
  329. style={[styles.statItem, { justifyContent: 'flex-start' }]}
  330. onPress={() =>
  331. navigation.navigate(
  332. ...([
  333. NAVIGATION_PAGES.USERS_LIST,
  334. {
  335. id: regionId,
  336. isFromHere: true,
  337. type: 'nm'
  338. }
  339. ] as never)
  340. )
  341. }
  342. >
  343. <View style={styles.icon}>
  344. <HouseSvg />
  345. </View>
  346. <View
  347. style={{
  348. height: 12,
  349. width: 1,
  350. backgroundColor: Colors.DARK_BLUE,
  351. marginRight: 6
  352. }}
  353. />
  354. <View style={styles.userImageContainer}>
  355. {data?.data.users_from_region &&
  356. data?.data.users_from_region.length > 0 &&
  357. data?.data.users_from_region?.map((user, index: number) => (
  358. <Image
  359. key={index}
  360. source={{ uri: API_HOST + user }}
  361. style={styles.userImage}
  362. />
  363. ))}
  364. <View style={styles.userCountContainer}>
  365. <Text style={styles.userCount}>
  366. {formatNumber(data?.data?.users_from_region_count ?? 0)}
  367. </Text>
  368. </View>
  369. </View>
  370. </TouchableOpacity>
  371. ) : (
  372. <View style={[styles.statItem, { justifyContent: 'flex-start' }]} />
  373. )}
  374. {data?.data.users_who_visited_region_count ?? 0 > 0 ? (
  375. <TouchableOpacity
  376. style={[styles.statItem, { justifyContent: 'flex-end' }]}
  377. onPress={() =>
  378. navigation.navigate(
  379. ...([
  380. NAVIGATION_PAGES.USERS_LIST,
  381. {
  382. id: regionId,
  383. isFromHere: false,
  384. type
  385. }
  386. ] as never)
  387. )
  388. }
  389. >
  390. <View style={styles.icon}>
  391. <CaseSvg />
  392. </View>
  393. <View
  394. style={{
  395. height: 12,
  396. width: 1,
  397. backgroundColor: Colors.DARK_BLUE,
  398. marginRight: 6
  399. }}
  400. />
  401. <View style={styles.userImageContainer}>
  402. {data?.data.users_who_visited_region &&
  403. data?.data.users_who_visited_region.length > 0 &&
  404. data?.data.users_who_visited_region?.map((user, index) => (
  405. <Image
  406. key={index}
  407. source={{ uri: API_HOST + user }}
  408. style={[styles.userImage]}
  409. />
  410. ))}
  411. <View style={styles.userCountContainer}>
  412. <Text style={styles.userCount}>
  413. {formatNumber(data?.data.users_who_visited_region_count ?? 0)}
  414. </Text>
  415. </View>
  416. </View>
  417. </TouchableOpacity>
  418. ) : (
  419. <View style={[styles.statItem, { justifyContent: 'flex-end' }]} />
  420. )}
  421. </View>
  422. <View style={[styles.divider, { marginBottom: 8 }]} />
  423. {series.length > 0 ? (
  424. <>
  425. <Text style={styles.travelSeriesTitle}>TRAVEL SERIES</Text>
  426. <HorizontalTabView
  427. index={indexSeries}
  428. setIndex={setIndexSeries}
  429. routes={routes}
  430. renderScene={({ route }: { route: SeriesGroup }) => <View style={{ height: 0 }} />}
  431. />
  432. <TravelSeriesList
  433. series={series}
  434. indexSeries={indexSeries}
  435. routes={routes}
  436. handleCheckboxChange={handleCheckboxChange}
  437. setIsInfoModalVisible={setIsInfoModalVisible}
  438. setInfoItem={setInfoItem}
  439. disabled={disabled}
  440. />
  441. </>
  442. ) : null}
  443. </View>
  444. <ImageView
  445. images={photos}
  446. imageIndex={currentImageIndex}
  447. visible={isModalVisible}
  448. onRequestClose={() => setModalVisible(false)}
  449. backgroundColor={Colors.DARK_BLUE}
  450. onImageIndexChange={setActiveIndex}
  451. FooterComponent={({ imageIndex }) => (
  452. <View style={styles.imageFooter}>
  453. <Text style={styles.imageDescription}>{photos[imageIndex].title}</Text>
  454. <TouchableOpacity
  455. onPress={() => {
  456. setModalVisible(false);
  457. navigation.navigate(
  458. ...([
  459. NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW,
  460. { userId: photos[imageIndex].user_id, hideTabBar: true }
  461. ] as never)
  462. );
  463. }}
  464. disabled={disabled}
  465. style={styles.imageOwner}
  466. >
  467. <Text style={styles.imageOwnerText}>{photos[imageIndex].first_name}</Text>
  468. <Text style={styles.imageOwnerText}>{photos[imageIndex].last_name}</Text>
  469. </TouchableOpacity>
  470. </View>
  471. )}
  472. />
  473. <ReactModal
  474. visible={isInfoModalVisible}
  475. children={
  476. <View style={styles.modalView}>
  477. <Text style={styles.infoTitle}>{infoItem?.name}</Text>
  478. <Text style={styles.infoText}>{infoItem?.description}</Text>
  479. <Button
  480. variant={ButtonVariants.OPACITY}
  481. containerStyles={styles.btnContainer}
  482. textStyles={{
  483. color: Colors.DARK_BLUE
  484. }}
  485. onPress={() => setIsInfoModalVisible(false)}
  486. children={'Got it'}
  487. />
  488. </View>
  489. }
  490. onRequestClose={() => setIsInfoModalVisible(false)}
  491. headerTitle={'Info'}
  492. visibleInPercent={'auto'}
  493. />
  494. </ScrollView>
  495. <EditNmModal
  496. isVisible={isEditModalVisible}
  497. onClose={() => setIsEditModalVisible(false)}
  498. modalState={modalState}
  499. updateModalState={handleModalStateChange}
  500. updateNM={handleUpdateNmModal}
  501. />
  502. </View>
  503. );
  504. };
  505. export default RegionViewScreen;