|
@@ -0,0 +1,492 @@
|
|
|
+import React, { FC, useCallback, useEffect, useState } from 'react';
|
|
|
+import { View, Text, Image, TouchableOpacity, Platform } from 'react-native';
|
|
|
+import ImageView from 'better-react-native-image-viewing';
|
|
|
+import { styles } from '../RegionViewScreen/styles';
|
|
|
+import { Button, HorizontalTabView, Loading, Modal as ReactModal } from 'src/components';
|
|
|
+import { useFocusEffect } from '@react-navigation/native';
|
|
|
+import { Colors } from 'src/theme';
|
|
|
+import { ScrollView } from 'react-native-gesture-handler';
|
|
|
+
|
|
|
+import { styles as ButtonStyles } from 'src/components/RegionPopup/style';
|
|
|
+import { usePostSetToggleItem } from '@api/series';
|
|
|
+import { NAVIGATION_PAGES } from 'src/types';
|
|
|
+import { API_HOST } from 'src/constants';
|
|
|
+import { StoreType, storage } from 'src/storage';
|
|
|
+import { ButtonVariants } from 'src/types/components';
|
|
|
+import { useRegion } from 'src/contexts/RegionContext';
|
|
|
+import formatNumber from '../../TravelsScreen/utils/formatNumber';
|
|
|
+import { PhotosData, Props, SeriesData, SeriesGroup, SeriesItem } from '../RegionViewScreen/types';
|
|
|
+import ImageCarousel from '../RegionViewScreen/ImageCarousel';
|
|
|
+import TravelSeriesList from '../RegionViewScreen/TravelSeriesList';
|
|
|
+import { useGetCountryDataQuery } from '@api/countries';
|
|
|
+import EditModal from '../../TravelsScreen/Components/EditSlowModal';
|
|
|
+
|
|
|
+import MarkIcon from 'assets/icons/mark.svg';
|
|
|
+import ChevronLeft from 'assets/icons/chevron-left.svg';
|
|
|
+import CaseSvg from 'assets/icons/briefcase.svg';
|
|
|
+import HouseSvg from 'assets/icons/house.svg';
|
|
|
+import EditSvg from 'assets/icons/travels-screens/pen-to-square.svg';
|
|
|
+import CheckSvg from 'assets/icons/travels-screens/circle-check.svg';
|
|
|
+import CheckRegularSvg from 'assets/icons/travels-screens/circle-check-regular.svg';
|
|
|
+
|
|
|
+const CountryViewScreen: FC<Props> = ({ navigation, route }) => {
|
|
|
+ const countryId = route.params?.regionId;
|
|
|
+ const disabled = route.params?.disabled;
|
|
|
+ const token = storage.get('token', StoreType.STRING) as string;
|
|
|
+ const [isLoading, setIsLoading] = useState(true);
|
|
|
+ const [isModalVisible, setModalVisible] = useState(false);
|
|
|
+ const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
|
|
+ const [activeIndex, setActiveIndex] = useState(0);
|
|
|
+ const [indexSeries, setIndexSeries] = useState(0);
|
|
|
+ const [routes, setRoutes] = useState<SeriesGroup[]>([]);
|
|
|
+ const [series, setSeries] = useState<SeriesData[]>([]);
|
|
|
+ const [photos, setPhotos] = useState<PhotosData[]>([]);
|
|
|
+ const [name, setName] = useState('');
|
|
|
+ const { data } = useGetCountryDataQuery(countryId, true, token && token);
|
|
|
+ const { mutate: updateSeriesItem } = usePostSetToggleItem();
|
|
|
+ const [isInfoModalVisible, setIsInfoModalVisible] = useState<boolean>(false);
|
|
|
+ const [infoItem, setInfoItem] = useState<SeriesItem | null>(null);
|
|
|
+ const [isEditSlowModalVisible, setIsEditSlowModalVisible] = useState<boolean>(false);
|
|
|
+
|
|
|
+ const {
|
|
|
+ handleUpdateSlow: updateSlow,
|
|
|
+ userData: regionData,
|
|
|
+ setUserData: setRegionData,
|
|
|
+ handleUpdateSlowList
|
|
|
+ } = useRegion();
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ navigation.getParent()?.setOptions({
|
|
|
+ tabBarStyle: {
|
|
|
+ display: 'none',
|
|
|
+ position: 'absolute',
|
|
|
+ ...Platform.select({
|
|
|
+ android: {
|
|
|
+ height: 58
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }, [navigation]);
|
|
|
+
|
|
|
+ useFocusEffect(
|
|
|
+ useCallback(() => {
|
|
|
+ const fetchGroups = async () => {
|
|
|
+ let staticGroups = [
|
|
|
+ {
|
|
|
+ key: 'all',
|
|
|
+ title: 'All',
|
|
|
+ series_id: 0
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ const routesData = data?.data?.series?.map((item) => ({
|
|
|
+ key: item.series_id?.toString(),
|
|
|
+ title: item.series_name,
|
|
|
+ series_id: item.series_id,
|
|
|
+ icon: item.icon,
|
|
|
+ items: item.items
|
|
|
+ }));
|
|
|
+
|
|
|
+ routesData && staticGroups.push(...routesData);
|
|
|
+
|
|
|
+ setPhotos(
|
|
|
+ data?.data?.photos?.map((item) => ({
|
|
|
+ ...item,
|
|
|
+ uriSmall: `${API_HOST}/ajax/pic/${item.id}/small`,
|
|
|
+ uri: `${API_HOST}/ajax/pic/${item.id}/full`
|
|
|
+ })) ?? []
|
|
|
+ );
|
|
|
+
|
|
|
+ setName(data?.data.name ?? '');
|
|
|
+
|
|
|
+ setSeries(data?.data?.series || []);
|
|
|
+ setRoutes(staticGroups);
|
|
|
+ setIsLoading(false);
|
|
|
+ };
|
|
|
+
|
|
|
+ if (data && data.result === 'OK') {
|
|
|
+ fetchGroups();
|
|
|
+ }
|
|
|
+ }, [data])
|
|
|
+ );
|
|
|
+
|
|
|
+ const handleCheckboxChange = useCallback(
|
|
|
+ async (item: SeriesItem, double: boolean, seriesId: number) => {
|
|
|
+ setSeries((currentData) => {
|
|
|
+ const groupIndex = currentData.findIndex((group) => group?.series_id === seriesId);
|
|
|
+
|
|
|
+ if (groupIndex === -1) return currentData;
|
|
|
+
|
|
|
+ const newData = [...currentData];
|
|
|
+ const newGroup = { ...newData[groupIndex] };
|
|
|
+
|
|
|
+ newGroup.items = newGroup.items.map((subItem) =>
|
|
|
+ subItem.id === item.id
|
|
|
+ ? {
|
|
|
+ ...subItem,
|
|
|
+ ...(double
|
|
|
+ ? { visited_double: subItem.visited_double === 0 ? 1 : 0 }
|
|
|
+ : { visited: subItem.visited === 0 ? 1 : 0 })
|
|
|
+ }
|
|
|
+ : subItem
|
|
|
+ );
|
|
|
+
|
|
|
+ newData[groupIndex] = newGroup;
|
|
|
+ return newData;
|
|
|
+ });
|
|
|
+
|
|
|
+ const itemData = {
|
|
|
+ token: token,
|
|
|
+ series_id: seriesId,
|
|
|
+ item_id: item.id,
|
|
|
+ checked: (item.visited === 1 ? 0 : 1) as 0 | 1,
|
|
|
+ double: (double && !item.visited_double ? 1 : 0) as 0 | 1
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ updateSeriesItem(itemData);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Failed to update checkbox state', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [token, updateSeriesItem]
|
|
|
+ );
|
|
|
+
|
|
|
+ const openModal = (index: number) => {
|
|
|
+ setCurrentImageIndex(index);
|
|
|
+ setModalVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ if (isLoading) return <Loading />;
|
|
|
+
|
|
|
+ const handleUpdateSlowModal = (
|
|
|
+ id: number,
|
|
|
+ v: boolean,
|
|
|
+ s11: boolean,
|
|
|
+ s31: boolean,
|
|
|
+ s101: boolean
|
|
|
+ ) => {
|
|
|
+ const updatedSlow = {
|
|
|
+ ...regionData,
|
|
|
+ visited: v,
|
|
|
+ slow11: Number(s11) as 0 | 1,
|
|
|
+ slow31: Number(s31) as 0 | 1,
|
|
|
+ slow101: Number(s101) as 0 | 1
|
|
|
+ };
|
|
|
+
|
|
|
+ const updatedSlowData = {
|
|
|
+ token,
|
|
|
+ id,
|
|
|
+ v,
|
|
|
+ s11,
|
|
|
+ s31,
|
|
|
+ s101
|
|
|
+ };
|
|
|
+
|
|
|
+ route.params?.isTravelsScreen
|
|
|
+ ? handleUpdateSlowList(id, v, s11, s31, s101)
|
|
|
+ : updateSlow(id, v, s11, s31, s101);
|
|
|
+ updatedSlow && setRegionData(updatedSlow);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleUpdateSlow = () => {
|
|
|
+ route.params?.isTravelsScreen
|
|
|
+ ? handleUpdateSlowList(
|
|
|
+ countryId,
|
|
|
+ !regionData.visited,
|
|
|
+ Boolean(regionData.slow11),
|
|
|
+ Boolean(regionData.slow31),
|
|
|
+ Boolean(regionData.slow101)
|
|
|
+ )
|
|
|
+ : updateSlow(
|
|
|
+ countryId,
|
|
|
+ !regionData.visited,
|
|
|
+ Boolean(regionData.slow11),
|
|
|
+ Boolean(regionData.slow31),
|
|
|
+ Boolean(regionData.slow101)
|
|
|
+ );
|
|
|
+
|
|
|
+ setRegionData({
|
|
|
+ ...regionData,
|
|
|
+ visited: !regionData.visited
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const renderDurationIcon = (condition: 0 | 1) =>
|
|
|
+ condition ? <CheckSvg fill={Colors.DARK_BLUE} /> : <CheckRegularSvg />;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <View style={styles.container}>
|
|
|
+ <TouchableOpacity
|
|
|
+ onPress={() => {
|
|
|
+ navigation.goBack();
|
|
|
+ }}
|
|
|
+ style={styles.backButton}
|
|
|
+ >
|
|
|
+ <View style={styles.chevronWrapper}>
|
|
|
+ <ChevronLeft fill={Colors.WHITE} />
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+ <ScrollView
|
|
|
+ contentContainerStyle={{ flexGrow: 1 }}
|
|
|
+ nestedScrollEnabled={true}
|
|
|
+ showsVerticalScrollIndicator={false}
|
|
|
+ >
|
|
|
+ {photos.length > 0 ? (
|
|
|
+ <ImageCarousel
|
|
|
+ photos={photos}
|
|
|
+ activeIndex={activeIndex}
|
|
|
+ setActiveIndex={setActiveIndex}
|
|
|
+ openModal={openModal}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <View style={styles.emptyImage}>
|
|
|
+ <Image
|
|
|
+ source={require('../../../../../assets/images/logo-opacity.png')}
|
|
|
+ style={{ width: 100, height: 100 }}
|
|
|
+ />
|
|
|
+ <Text style={styles.emptyImageText}>No image available at this location</Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <View style={styles.wrapper}>
|
|
|
+ {regionData?.visited && !disabled && (
|
|
|
+ <View style={styles.durationContainer}>
|
|
|
+ <View style={styles.durationItem}>
|
|
|
+ {renderDurationIcon(regionData.slow11)}
|
|
|
+ <Text style={styles.visitDuration}>11+ days</Text>
|
|
|
+ </View>
|
|
|
+ <View style={styles.durationItem}>
|
|
|
+ {renderDurationIcon(regionData.slow31)}
|
|
|
+ <Text style={styles.visitDuration}>31+ days</Text>
|
|
|
+ </View>
|
|
|
+ <View style={styles.durationItem}>
|
|
|
+ {renderDurationIcon(regionData.slow101)}
|
|
|
+ <Text style={styles.visitDuration}>101+ days</Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ <View style={styles.nameContainer}>
|
|
|
+ <Text style={styles.title}>{name}</Text>
|
|
|
+ <View style={ButtonStyles.btnContainer}>
|
|
|
+ {regionData?.visited && !disabled ? (
|
|
|
+ <TouchableOpacity
|
|
|
+ onPress={() => setIsEditSlowModalVisible(true)}
|
|
|
+ style={ButtonStyles.editBtn}
|
|
|
+ >
|
|
|
+ <EditSvg width={14} height={14} />
|
|
|
+ </TouchableOpacity>
|
|
|
+ ) : null}
|
|
|
+ {!disabled ? (
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[
|
|
|
+ ButtonStyles.btn,
|
|
|
+ regionData?.visited && !disabled
|
|
|
+ ? ButtonStyles.visitedButton
|
|
|
+ : ButtonStyles.markVisitedButton
|
|
|
+ ]}
|
|
|
+ onPress={handleUpdateSlow}
|
|
|
+ >
|
|
|
+ {regionData?.visited ? (
|
|
|
+ <View style={ButtonStyles.visitedContainer}>
|
|
|
+ <MarkIcon width={16} height={16} />
|
|
|
+ <Text style={ButtonStyles.visitedButtonText}>Visited</Text>
|
|
|
+ </View>
|
|
|
+ ) : (
|
|
|
+ <Text style={[ButtonStyles.markVisitedButtonText]}>Mark Visited</Text>
|
|
|
+ )}
|
|
|
+ </TouchableOpacity>
|
|
|
+ ) : null}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View style={styles.divider} />
|
|
|
+
|
|
|
+ <View style={styles.stats}>
|
|
|
+ {data?.data.users_from_country_count ?? 0 > 0 ? (
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[styles.statItem, { justifyContent: 'flex-start' }]}
|
|
|
+ onPress={() =>
|
|
|
+ navigation.navigate(
|
|
|
+ ...([
|
|
|
+ NAVIGATION_PAGES.USERS_LIST,
|
|
|
+ {
|
|
|
+ id: countryId,
|
|
|
+ isFromHere: true,
|
|
|
+ type: 'country'
|
|
|
+ }
|
|
|
+ ] as never)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <View style={styles.icon}>
|
|
|
+ <HouseSvg />
|
|
|
+ </View>
|
|
|
+ <View
|
|
|
+ style={{
|
|
|
+ height: 12,
|
|
|
+ width: 1,
|
|
|
+ backgroundColor: Colors.DARK_BLUE,
|
|
|
+ marginRight: 6
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ <View style={styles.userImageContainer}>
|
|
|
+ {data?.data.users_from_country &&
|
|
|
+ data?.data.users_from_country.length > 0 &&
|
|
|
+ data?.data.users_from_country?.map((user, index: number) => (
|
|
|
+ <Image
|
|
|
+ key={index}
|
|
|
+ source={{ uri: API_HOST + user }}
|
|
|
+ style={styles.userImage}
|
|
|
+ />
|
|
|
+ ))}
|
|
|
+ <View style={styles.userCountContainer}>
|
|
|
+ <Text style={styles.userCount}>
|
|
|
+ {formatNumber(data?.data?.users_from_country_count ?? 0)}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+ ) : (
|
|
|
+ <View style={[styles.statItem, { justifyContent: 'flex-start' }]} />
|
|
|
+ )}
|
|
|
+
|
|
|
+ {data?.data.users_who_visited_country_count ?? 0 > 0 ? (
|
|
|
+ <TouchableOpacity
|
|
|
+ style={[styles.statItem, { justifyContent: 'flex-end' }]}
|
|
|
+ onPress={() =>
|
|
|
+ navigation.navigate(
|
|
|
+ ...([
|
|
|
+ NAVIGATION_PAGES.USERS_LIST,
|
|
|
+ {
|
|
|
+ id: countryId,
|
|
|
+ isFromHere: false,
|
|
|
+ type: 'country'
|
|
|
+ }
|
|
|
+ ] as never)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <View style={styles.icon}>
|
|
|
+ <CaseSvg />
|
|
|
+ </View>
|
|
|
+ <View
|
|
|
+ style={{
|
|
|
+ height: 12,
|
|
|
+ width: 1,
|
|
|
+ backgroundColor: Colors.DARK_BLUE,
|
|
|
+ marginRight: 6
|
|
|
+ }}
|
|
|
+ />
|
|
|
+
|
|
|
+ <View style={styles.userImageContainer}>
|
|
|
+ {data?.data.users_who_visited_country &&
|
|
|
+ data?.data.users_who_visited_country.length > 0 &&
|
|
|
+ data?.data.users_who_visited_country?.map((user, index) => (
|
|
|
+ <Image
|
|
|
+ key={index}
|
|
|
+ source={{ uri: API_HOST + user }}
|
|
|
+ style={[styles.userImage]}
|
|
|
+ />
|
|
|
+ ))}
|
|
|
+ <View style={styles.userCountContainer}>
|
|
|
+ <Text style={styles.userCount}>
|
|
|
+ {formatNumber(data?.data.users_who_visited_country_count ?? 0)}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </TouchableOpacity>
|
|
|
+ ) : (
|
|
|
+ <View style={[styles.statItem, { justifyContent: 'flex-end' }]} />
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View style={[styles.divider, { marginBottom: 8 }]} />
|
|
|
+
|
|
|
+ {series.length > 0 ? (
|
|
|
+ <>
|
|
|
+ <Text style={styles.travelSeriesTitle}>TRAVEL SERIES</Text>
|
|
|
+ <HorizontalTabView
|
|
|
+ index={indexSeries}
|
|
|
+ setIndex={setIndexSeries}
|
|
|
+ routes={routes}
|
|
|
+ renderScene={({ route }: { route: SeriesGroup }) => <View style={{ height: 0 }} />}
|
|
|
+ />
|
|
|
+ <TravelSeriesList
|
|
|
+ series={series}
|
|
|
+ indexSeries={indexSeries}
|
|
|
+ routes={routes}
|
|
|
+ handleCheckboxChange={handleCheckboxChange}
|
|
|
+ setIsInfoModalVisible={setIsInfoModalVisible}
|
|
|
+ setInfoItem={setInfoItem}
|
|
|
+ disabled={disabled}
|
|
|
+ />
|
|
|
+ </>
|
|
|
+ ) : null}
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <ImageView
|
|
|
+ images={photos}
|
|
|
+ imageIndex={currentImageIndex}
|
|
|
+ visible={isModalVisible}
|
|
|
+ onRequestClose={() => setModalVisible(false)}
|
|
|
+ backgroundColor={Colors.DARK_BLUE}
|
|
|
+ onImageIndexChange={setActiveIndex}
|
|
|
+ FooterComponent={({ imageIndex }) => (
|
|
|
+ <View style={styles.imageFooter}>
|
|
|
+ <Text style={styles.imageDescription}>{photos[imageIndex].title}</Text>
|
|
|
+ <TouchableOpacity
|
|
|
+ onPress={() => {
|
|
|
+ setModalVisible(false);
|
|
|
+ navigation.navigate(
|
|
|
+ ...([
|
|
|
+ NAVIGATION_PAGES.PUBLIC_PROFILE_VIEW,
|
|
|
+ { userId: photos[imageIndex].user_id, hideTabBar: true }
|
|
|
+ ] as never)
|
|
|
+ );
|
|
|
+ }}
|
|
|
+ disabled={disabled}
|
|
|
+ style={styles.imageOwner}
|
|
|
+ >
|
|
|
+ <Text style={styles.imageOwnerText}>{photos[imageIndex].first_name}</Text>
|
|
|
+ <Text style={styles.imageOwnerText}>{photos[imageIndex].last_name}</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ReactModal
|
|
|
+ visible={isInfoModalVisible}
|
|
|
+ children={
|
|
|
+ <View style={styles.modalView}>
|
|
|
+ <Text style={styles.infoTitle}>{infoItem?.name}</Text>
|
|
|
+ <Text style={styles.infoText}>{infoItem?.description}</Text>
|
|
|
+ <Button
|
|
|
+ variant={ButtonVariants.OPACITY}
|
|
|
+ containerStyles={styles.btnContainer}
|
|
|
+ textStyles={{
|
|
|
+ color: Colors.DARK_BLUE
|
|
|
+ }}
|
|
|
+ onPress={() => setIsInfoModalVisible(false)}
|
|
|
+ children={'Got it'}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ }
|
|
|
+ onRequestClose={() => setIsInfoModalVisible(false)}
|
|
|
+ headerTitle={'Info'}
|
|
|
+ visibleInPercent={'auto'}
|
|
|
+ />
|
|
|
+ </ScrollView>
|
|
|
+ <EditModal
|
|
|
+ isVisible={isEditSlowModalVisible}
|
|
|
+ onClose={() => setIsEditSlowModalVisible(false)}
|
|
|
+ item={{ ...regionData, country_id: countryId }}
|
|
|
+ updateSlow={(id, v, s11, s31, s101) => {
|
|
|
+ handleUpdateSlowModal(id, v, s11, s31, s101);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default CountryViewScreen;
|