瀏覽代碼

regions-screen

Viktoriia 1 年之前
父節點
當前提交
03bf5b00eb

+ 2 - 0
Route.tsx

@@ -38,6 +38,7 @@ import TripsScreen from 'src/screens/InAppScreens/TravelsScreen/TripsScreen';
 import AddNewTripScreen from 'src/screens/InAppScreens/TravelsScreen/AddNewTripScreen';
 import AddNewTripScreen from 'src/screens/InAppScreens/TravelsScreen/AddNewTripScreen';
 import AddRegionsScreen from 'src/screens/InAppScreens/TravelsScreen/AddRegionsScreen';
 import AddRegionsScreen from 'src/screens/InAppScreens/TravelsScreen/AddRegionsScreen';
 import CountriesScreen from 'src/screens/InAppScreens/TravelsScreen/CountriesScreen';
 import CountriesScreen from 'src/screens/InAppScreens/TravelsScreen/CountriesScreen';
+import RegionsScreen from 'src/screens/InAppScreens/TravelsScreen/RegionsScreen';
 
 
 import { NAVIGATION_PAGES } from './src/types';
 import { NAVIGATION_PAGES } from './src/types';
 import { storage, StoreType } from './src/storage';
 import { storage, StoreType } from './src/storage';
@@ -179,6 +180,7 @@ const Route = () => {
                   <ScreenStack.Screen name={NAVIGATION_PAGES.ADD_TRIP} component={AddNewTripScreen} />
                   <ScreenStack.Screen name={NAVIGATION_PAGES.ADD_TRIP} component={AddNewTripScreen} />
                   <ScreenStack.Screen name={NAVIGATION_PAGES.ADD_REGIONS} component={AddRegionsScreen} />
                   <ScreenStack.Screen name={NAVIGATION_PAGES.ADD_REGIONS} component={AddRegionsScreen} />
                   <ScreenStack.Screen name={NAVIGATION_PAGES.COUNTRIES} component={CountriesScreen} />
                   <ScreenStack.Screen name={NAVIGATION_PAGES.COUNTRIES} component={CountriesScreen} />
+                  <ScreenStack.Screen name={NAVIGATION_PAGES.REGIONS} component={RegionsScreen} />
                 </ScreenStack.Navigator>
                 </ScreenStack.Navigator>
               )}
               )}
             </BottomTab.Screen>
             </BottomTab.Screen>

+ 1 - 1
app.config.ts

@@ -21,7 +21,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
   // Should be updated after every production release (deploy to AppStore/PlayMarket)
   // Should be updated after every production release (deploy to AppStore/PlayMarket)
   version: '1.0.0',
   version: '1.0.0',
   // Should be updated after every dependency change
   // Should be updated after every dependency change
-  runtimeVersion: '1.2',
+  runtimeVersion: '1.3',
   orientation: 'portrait',
   orientation: 'portrait',
   icon: './assets/icon.png',
   icon: './assets/icon.png',
   userInterfaceStyle: 'light',
   userInterfaceStyle: 'light',

+ 10 - 0
assets/icons/travels-screens/rotate.svg

@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2133_19451)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4.73572 4.47246C5.59286 3.64964 6.74826 3.14612 8.02131 3.14612C10.506 3.14612 12.5232 5.16243 12.7715 7.5389L12.6166 7.41952C12.2816 7.16139 11.8036 7.22739 11.549 7.56693C11.2943 7.90647 11.3594 8.39097 11.6944 8.6491L13.004 9.65833L13.4441 9.99741L13.8986 9.67866L15.3378 8.66943C15.6839 8.42677 15.7703 7.9457 15.5309 7.59494C15.2916 7.24418 14.817 7.15654 14.4709 7.3992L14.3005 7.5187C14.0399 4.32281 11.3637 1.60156 8.02131 1.60156C6.34316 1.60156 4.81679 2.26735 3.68792 3.35101C3.3824 3.64429 3.36929 4.13309 3.65863 4.44277C3.94797 4.75245 4.4302 4.76575 4.73572 4.47246ZM11.2636 11.5084C10.4065 12.3313 9.25109 12.8348 7.97804 12.8348C5.49334 12.8348 3.47613 10.8185 3.22785 8.442L3.38277 8.56138C3.71774 8.81951 4.19574 8.75351 4.4504 8.41397C4.70506 8.07444 4.63995 7.58993 4.30498 7.3318L2.9953 6.32257L2.55529 5.98349L2.10073 6.30224L0.661508 7.31147C0.315461 7.55413 0.229003 8.0352 0.468402 8.38596C0.707799 8.73672 1.1824 8.82436 1.52844 8.5817L1.69886 8.4622C1.95941 11.6581 4.63569 14.3793 7.97804 14.3793C9.65619 14.3793 11.1826 13.7136 12.3114 12.6299C12.6169 12.3366 12.6301 11.8478 12.3407 11.5381C12.0514 11.2284 11.5692 11.2152 11.2636 11.5084Z" fill="#0F3F4F"/>
+</g>
+<defs>
+<clipPath id="clip0_2133_19451">
+<rect width="16" height="16" fill="white" transform="translate(0 0.00195312)"/>
+</clipPath>
+</defs>
+</svg>

+ 1 - 1
package.json

@@ -39,7 +39,7 @@
     "patch-package": "^8.0.0",
     "patch-package": "^8.0.0",
     "postinstall-postinstall": "^2.1.0",
     "postinstall-postinstall": "^2.1.0",
     "react": "18.2.0",
     "react": "18.2.0",
-    "react-native": "0.72.6",
+    "react-native": "0.72.10",
     "react-native-calendar-picker": "^7.1.4",
     "react-native-calendar-picker": "^7.1.4",
     "react-native-calendars": "^1.1304.1",
     "react-native-calendars": "^1.1304.1",
     "react-native-device-detection": "^0.2.1",
     "react-native-device-detection": "^0.2.1",

+ 3 - 0
src/modules/api/myRegions/index.ts

@@ -0,0 +1,3 @@
+export * from './queries';
+export * from './regions-api';
+export * from './regions-query-keys';

+ 4 - 0
src/modules/api/myRegions/queries/index.ts

@@ -0,0 +1,4 @@
+export * from './use-post-get-megaregions';
+export * from './use-post-get-regions-qe';
+export * from './use-post-set-update-nm';
+export * from './use-post-set-update-tcc';

+ 17 - 0
src/modules/api/myRegions/queries/use-post-get-megaregions.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { regionsQueryKeys } from '../regions-query-keys';
+import { regionsApi, type PostGetMegaReturn } from '../regions-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetMegaregionsQuery = (token: string, enabled: boolean) => {
+  return useQuery<PostGetMegaReturn, BaseAxiosError>({
+    queryKey: regionsQueryKeys.getMegaregions(token),
+    queryFn: async () => {
+      const response = await regionsApi.getMegaregions(token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/myRegions/queries/use-post-get-regions-qe.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { regionsQueryKeys } from '../regions-query-keys';
+import { regionsApi, type PostGetRegionsQeReturn } from '../regions-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetRegionQeQuery = (megaregion: number, token: string, enabled: boolean) => {
+  return useQuery<PostGetRegionsQeReturn, BaseAxiosError>({
+    queryKey: regionsQueryKeys.getRegionsQe(megaregion, token),
+    queryFn: async () => {
+      const response = await regionsApi.getRegionsQe(megaregion, token);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/myRegions/queries/use-post-set-update-nm.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { regionsQueryKeys } from '../regions-query-keys';
+import { type PostSetNmRegion, regionsApi } from '../regions-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostSetNmRegionMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostSetNmRegion, ResponseType>({
+    mutationKey: regionsQueryKeys.setNmRegion(),
+    mutationFn: async (data) => {
+      const response = await regionsApi.setNmRegion(data);
+      return response.data;
+    }
+  });
+};

+ 17 - 0
src/modules/api/myRegions/queries/use-post-set-update-tcc.tsx

@@ -0,0 +1,17 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { regionsQueryKeys } from '../regions-query-keys';
+import { type PostSetTCCRegion, regionsApi } from '../regions-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostSetTCCRegionMutation = () => {
+  return useMutation<ResponseType, BaseAxiosError, PostSetTCCRegion, ResponseType>({
+    mutationKey: regionsQueryKeys.setTCCRegion(),
+    mutationFn: async (data) => {
+      const response = await regionsApi.setTCCRegion(data);
+      return response.data;
+    }
+  });
+};

+ 64 - 0
src/modules/api/myRegions/regions-api.tsx

@@ -0,0 +1,64 @@
+import { request } from '../../../utils';
+import { API } from '../../../types';
+import { ResponseType } from '../response-type';
+
+export interface PostGetMegaReturn extends ResponseType {
+  data: {
+    id: number;
+    name: string;
+  }[];
+}
+
+export interface PostGetRegionsQeReturn extends ResponseType {
+  data: {
+    out_regs: {
+      id: number;
+      flag_1: string;
+      flag_2: string;
+      region_name: string;
+      essential: 0 | 1;
+      quality: number;
+      year: number;
+      last: number;
+      visits: number;
+    }[];
+    out_tcc: {
+      id: number;
+      flag: string;
+      flag2: string;
+      name: string;
+      visited: number;
+    }[];
+    out_mtp: {
+      mtp_id: number;
+      flag: string;
+      flag2: string;
+      name: string;
+      visited: number;
+    }[];
+  };
+}
+
+export interface PostSetNmRegion {
+  token: string;
+  region: number;
+  first: number;
+  last: number;
+  visits: number;
+  quality: number;
+}
+
+export interface PostSetTCCRegion {
+  token: string;
+  region: number;
+  visits: 0 | 1;
+}
+
+export const regionsApi = {
+  getMegaregions: (token: string) =>
+    request.postForm<PostGetMegaReturn>(API.GET_MEGAREGIONS, { token }),
+  getRegionsQe: (megaregion: number, token: string) =>
+    request.postForm<PostGetRegionsQeReturn>(API.GET_REGIONS_QE, { megaregion, token }),
+  setNmRegion: (data: PostSetNmRegion) => request.postForm<ResponseType>(API.SET_NM_REGION, data),
+  setTCCRegion: (data: PostSetTCCRegion) => request.postForm<ResponseType>(API.SET_TCC_REGION, data)
+};

+ 6 - 0
src/modules/api/myRegions/regions-query-keys.tsx

@@ -0,0 +1,6 @@
+export const regionsQueryKeys = {
+  getMegaregions: (token: string) => ['getMegaregions', { token }] as const,
+  getRegionsQe: (megaregion: number, token: string) => ['getRegionsQe', { megaregion, token }] as const,
+  setNmRegion: () => ['setNmRegion'] as const,
+  setTCCRegion: () => ['setTCCRegion'] as const,
+};

+ 109 - 0
src/screens/InAppScreens/TravelsScreen/Components/MyRegionsItems/NmRegionItem.tsx

@@ -0,0 +1,109 @@
+import React from 'react';
+import { View, Text, TouchableOpacity, Image } from 'react-native';
+
+import { NmRegion } from '../../utils/types';
+import { Colors } from 'src/theme';
+import { styles } from './styles';
+import { API_HOST } from 'src/constants';
+
+import MarkIcon from 'assets/icons/mark.svg';
+import EditSvg from 'assets/icons/travels-screens/pen-to-square.svg';
+import CalendarSvg from 'assets/icons/travels-screens/calendar.svg';
+import RotateSvg from 'assets/icons/travels-screens/rotate.svg';
+
+export const NmRegionItem = React.memo(
+  ({
+    item,
+    openEditModal,
+    updateNM
+  }: {
+    item: NmRegion;
+    openEditModal: (item: NmRegion) => void;
+    updateNM: (
+      region: number,
+      first: number,
+      last: number,
+      visits: number,
+      quality: number
+    ) => void;
+  }) => {
+    const name = item.region_name.split(/ – | - /);
+
+    return (
+      <View style={styles.regionItem}>
+        <View style={styles.regionItemHeader}>
+          <View style={{ gap: 6, flex: 1 }}>
+            <Text style={styles.regionItemName}>{name[0]}</Text>
+            <Text style={styles.regionItemSubname}>{name[1]}</Text>
+          </View>
+          <View style={{ flexDirection: 'row', alignSelf: 'flex-start' }}>
+            <Image
+              source={{ uri: `${API_HOST}/img/flags_new/${item.flag_1}` }}
+              style={styles.flag}
+            />
+            {item.flag_2 && (
+              <Image
+                source={{ uri: `${API_HOST}/img/flags_new/${item.flag_2}` }}
+                style={[styles.flag, { marginLeft: -5 }]}
+              />
+            )}
+          </View>
+        </View>
+
+        <View style={styles.divider} />
+
+        <View style={styles.regionItemContent}>
+          {item.visits > 0 && item.year > 1 && (
+            <View style={styles.infoContent}>
+              <CalendarSvg height={18} width={18} fill={Colors.DARK_BLUE} />
+              <Text style={{ fontWeight: 'bold', fontSize: 13, color: Colors.DARK_BLUE }}>
+                {item.year}
+              </Text>
+            </View>
+          )}
+
+          {item.visits > 0 && (
+            <View style={styles.infoContent}>
+              <RotateSvg fill={Colors.DARK_BLUE} />
+              <Text style={{ fontWeight: 'bold', fontSize: 13, color: Colors.DARK_BLUE }}>
+                {item.visits >= 10 ? '10+' : item.visits}
+              </Text>
+            </View>
+          )}
+
+          <View style={styles.btnContainer}>
+            {item.visits > 0 ? (
+              <TouchableOpacity onPress={() => openEditModal(item)} style={styles.editBtn}>
+                <EditSvg width={14} height={14} />
+              </TouchableOpacity>
+            ) : null}
+            <TouchableOpacity
+              style={[
+                styles.btn,
+                item.visits > 0 ? styles.visitedButton : styles.markVisitedButton
+              ]}
+              onPress={() =>
+                updateNM(
+                  item.id,
+                  item.visits > 0 ? 0 : 1,
+                  item.visits > 0 ? 0 : 1,
+                  item.visits > 0 ? 0 : 1,
+                  3
+                )
+              }
+            >
+              {item.visits > 0 ? (
+                <View style={styles.visitedContainer}>
+                  <MarkIcon width={16} height={16} />
+                  <Text style={styles.visitedButtonText}>Visited</Text>
+                </View>
+              ) : (
+                <Text style={[styles.markVisitedButtonText]}>Mark visited</Text>
+              )}
+            </TouchableOpacity>
+          </View>
+        </View>
+      </View>
+    );
+  }
+);

+ 63 - 0
src/screens/InAppScreens/TravelsScreen/Components/MyRegionsItems/TccRegionItem.tsx

@@ -0,0 +1,63 @@
+import React from 'react';
+import { View, Text, TouchableOpacity, Image } from 'react-native';
+
+import { TCCRegion } from '../../utils/types';
+import { styles } from './styles';
+import { API_HOST } from 'src/constants';
+
+import MarkIcon from 'assets/icons/mark.svg';
+
+export const TccRegionItem = React.memo(
+  ({
+    item,
+    updateTCC
+  }: {
+    item: TCCRegion;
+    updateTCC: (region: number, visits: 0 | 1) => void;
+  }) => {
+    const name = item.name.split(/ – | - /);
+
+    return (
+      <View style={styles.regionItem}>
+        <View style={styles.regionItemHeader}>
+          <View style={{ gap: 6, flex: 1 }}>
+            <Text style={styles.regionItemName}>{name[0]}</Text>
+            <Text style={styles.regionItemSubname}>{name[1]}</Text>
+          </View>
+          <View style={{ flexDirection: 'row', alignSelf: 'flex-start' }}>
+            <Image source={{ uri: `${API_HOST}/img/flags_new/${item.flag}` }} style={styles.flag} />
+            {item.flag2 && (
+              <Image
+                source={{ uri: `${API_HOST}/img/flags_new/${item.flag2}` }}
+                style={[styles.flag, { marginLeft: -5 }]}
+              />
+            )}
+          </View>
+        </View>
+
+        <View style={styles.divider} />
+
+        <View style={styles.regionItemContent}>
+          <View style={styles.btnContainer}>
+            <TouchableOpacity
+              style={[
+                styles.btn,
+                item.visited > 0 ? styles.visitedButton : styles.markVisitedButton
+              ]}
+              onPress={() => updateTCC(item.id, item.visited > 0 ? 0 : 1)}
+            >
+              {item.visited > 0 ? (
+                <View style={styles.visitedContainer}>
+                  <MarkIcon width={16} height={16} />
+                  <Text style={styles.visitedButtonText}>Visited</Text>
+                </View>
+              ) : (
+                <Text style={[styles.markVisitedButtonText]}>Mark visited</Text>
+              )}
+            </TouchableOpacity>
+          </View>
+        </View>
+      </View>
+    );
+  }
+);

+ 84 - 0
src/screens/InAppScreens/TravelsScreen/Components/MyRegionsItems/styles.tsx

@@ -0,0 +1,84 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+
+export const styles = StyleSheet.create({
+  regionItem: {
+    backgroundColor: '#FAFAFA',
+    padding: 12,
+    borderRadius: 8,
+    gap: 12,
+    marginVertical: 8
+  },
+  regionItemHeader: {
+    flexDirection: 'row',
+    gap: 12,
+    alignItems: 'center'
+  },
+  regionItemName: {
+    fontSize: 14,
+    fontWeight: 'bold',
+    color: Colors.DARK_BLUE
+  },
+  regionItemSubname: {
+    fontSize: 12,
+    fontWeight: '500',
+    color: Colors.DARK_BLUE,
+    flexShrink: 1
+  },
+  flag: {
+    height: 20,
+    width: 20,
+    borderRadius: 10,
+    resizeMode: 'cover',
+    borderWidth: 1,
+    borderColor: Colors.DARK_LIGHT
+  },
+  regionItemContent: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: 8
+  },
+  divider: { height: 1, backgroundColor: Colors.DARK_LIGHT },
+  btnContainer: {
+    alignItems: 'center',
+    justifyContent: 'flex-end',
+    flexDirection: 'row',
+    gap: 8,
+    flex: 1
+  },
+  btn: {
+    paddingVertical: 6,
+    paddingHorizontal: 12,
+    borderRadius: 6,
+    alignItems: 'center',
+    borderWidth: 1
+  },
+  markVisitedButton: {
+    backgroundColor: Colors.ORANGE,
+    borderColor: Colors.ORANGE
+  },
+  visitedButton: {
+    backgroundColor: 'transparent',
+    borderColor: Colors.BORDER_LIGHT
+  },
+  markVisitedButtonText: {
+    color: 'white',
+    fontWeight: 'bold',
+    fontSize: 13
+  },
+  visitedButtonText: {
+    color: Colors.DARK_BLUE,
+    fontWeight: 'bold',
+    fontSize: 13
+  },
+  visitedContainer: { gap: 6, flexDirection: 'row', alignItems: 'center' },
+  editBtn: {
+    borderRadius: 20,
+    borderWidth: 1,
+    borderColor: Colors.LIGHT_GRAY,
+    alignItems: 'center',
+    justifyContent: 'center',
+    padding: 7
+  },
+  infoContent: { flexDirection: 'row', alignItems: 'center', gap: 4 }
+});

+ 393 - 0
src/screens/InAppScreens/TravelsScreen/RegionsScreen/index.tsx

@@ -0,0 +1,393 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import { View, Text, TouchableOpacity, ScrollView, FlatList } from 'react-native';
+import ReactModal from 'react-native-modal';
+import * as Progress from 'react-native-progress';
+import { useFocusEffect } from '@react-navigation/native';
+import { Dropdown } from 'react-native-searchable-dropdown-kj';
+import moment from 'moment';
+
+import { Button, Header, PageWrapper, WarningModal } from 'src/components';
+import { CustomButton } from '../Components';
+
+import { StoreType, storage } from 'src/storage';
+import { NmRegion, TCCRegion } from '../utils/types';
+import { Colors } from 'src/theme';
+import { styles } from './styles';
+import {
+  useGetMegaregionsQuery,
+  useGetRegionQeQuery,
+  usePostSetNmRegionMutation,
+  usePostSetTCCRegionMutation
+} from '@api/myRegions';
+import { ButtonVariants } from 'src/types/components';
+import { noOfVisits, qualityOptions } from '../utils/constants';
+
+import ChevronIcon from 'assets/icons/travels-screens/down-arrow.svg';
+import AddImgSvg from 'assets/icons/travels-screens/add-img.svg';
+import { NmRegionItem } from '../Components/MyRegionsItems/NmRegionItem';
+import { TccRegionItem } from '../Components/MyRegionsItems/TccRegionItem';
+
+const RegionsScreen = () => {
+  const token = storage.get('token', StoreType.STRING) as string;
+  const { data: megaregions } = useGetMegaregionsQuery(token, true);
+  const [megaSelectorVisible, setMegaSelectorVisible] = useState(false);
+  const [selectedMega, setSelectedMega] = useState<{ name: string; id: number }>({
+    id: 1,
+    name: 'SOUTHERN EUROPE'
+  });
+  const { data: regionsQe } = useGetRegionQeQuery(selectedMega.id, token, true);
+  const [total, setTotal] = useState(0);
+  const [isEditModalVisible, setIsEditModalVisible] = useState(false);
+  const [currentItem, setCurrentItem] = useState<NmRegion | null>(null);
+  const [contentIndex, setContentIndex] = useState(0);
+  const [nmRegions, setNmRegions] = useState<NmRegion[] | null>(null);
+  const [filteredNmRegions, setFilteredNmRegions] = useState<NmRegion[] | null>(null);
+  const [tccRegions, setTccRegions] = useState<TCCRegion[] | null>(null);
+  const [filteredTccRegions, setFilteredTccRegions] = useState<TCCRegion[] | null>(null);
+  const { mutate: updateNM } = usePostSetNmRegionMutation();
+  const { mutate: updateTCC } = usePostSetTCCRegionMutation();
+  const [selectedQuality, setSelectedQuality] = useState(qualityOptions[2]);
+  const [selectedFirstYear, setSelectedFirstYear] = useState(1);
+  const [selectedLastYear, setSelectedLastYear] = useState(1);
+  const [years, setYears] = useState<{ label: string; value: number }[]>([]);
+  const [selectedNoOfVisits, setSelectedNoOfVisits] = useState(1);
+
+  const handleOpenEditModal = (item: NmRegion) => {
+    setCurrentItem(item);
+    setSelectedFirstYear(item.year);
+    setSelectedLastYear(item.last);
+    setSelectedNoOfVisits(item.visits);
+    setSelectedQuality(
+      qualityOptions.find((quality) => quality.id === item.quality) || qualityOptions[2]
+    );
+    setIsEditModalVisible(true);
+  };
+
+  const handleUpdateNM = useCallback(
+    (region: number, first: number, last: number, visits: number, quality: number) => {
+      const updatedNM = nmRegions?.map((item) => {
+        if (item.id === region) {
+          return {
+            ...item,
+            year: first,
+            last,
+            quality,
+            visits
+          };
+        }
+
+        return item;
+      });
+
+      const updatedNMData = {
+        token,
+        region,
+        first,
+        last,
+        visits,
+        quality
+      };
+
+      updateNM(updatedNMData);
+      updatedNM && setNmRegions(updatedNM);
+    },
+    [nmRegions]
+  );
+
+  const handleUpdateTCC = useCallback(
+    (region: number, visits: 0 | 1) => {
+      const updatedTCC = tccRegions?.map((item) => {
+        if (item.id === region) {
+          return {
+            ...item,
+            visited: visits
+          };
+        }
+
+        return item;
+      });
+
+      const updatedTCCData = {
+        token,
+        region,
+        visits
+      };
+
+      updateTCC(updatedTCCData);
+      updatedTCC && setTccRegions(updatedTCC);
+    },
+    [tccRegions]
+  );
+
+  useEffect(() => {
+    if (nmRegions && nmRegions.length) {
+      calcTotalCountries();
+    }
+  }, [nmRegions]);
+
+  useEffect(() => {
+    if (regionsQe && regionsQe.result === 'OK') {
+      setNmRegions(regionsQe.data.out_regs);
+      setTccRegions(regionsQe.data.out_tcc);
+    }
+  }, [regionsQe]);
+
+  useEffect(() => {
+    if (megaregions && megaregions.result === 'OK') {
+      setContentIndex(0);
+    }
+  }, [selectedMega]);
+
+  useEffect(() => {
+    switch (contentIndex) {
+      case 0:
+        setFilteredNmRegions(nmRegions);
+        setFilteredTccRegions(tccRegions);
+        break;
+      case 1:
+        setFilteredNmRegions(nmRegions?.filter((item) => item.visits <= 0) || []);
+        setFilteredTccRegions(tccRegions?.filter((item) => item.visited <= 0) || []);
+        break;
+      case 2:
+        setFilteredNmRegions(nmRegions?.filter((item) => item.visits > 0) || []);
+        setFilteredTccRegions(tccRegions?.filter((item) => item.visited > 0) || []);
+        break;
+    }
+  }, [contentIndex, nmRegions, tccRegions]);
+
+  useFocusEffect(
+    useCallback(() => {
+      if (megaregions && megaregions.result === 'OK') {
+        setSelectedMega(megaregions.data[1]);
+
+        const currentYear = moment().year();
+        let yearSelector: { label: string; value: number }[] = [{ label: 'visited', value: 1 }];
+        for (let i = currentYear; i >= 1951; i--) {
+          yearSelector.push({ label: i.toString(), value: i });
+        }
+        setYears(yearSelector);
+      }
+    }, [megaregions])
+  );
+
+  const calcTotalCountries = () => {
+    const visited = nmRegions?.filter((item) => item.visits > 0).length || 0;
+    setTotal(visited);
+  };
+
+  const renderItem = ({ item }: { item: NmRegion }) => (
+    <NmRegionItem item={item} openEditModal={handleOpenEditModal} updateNM={handleUpdateNM} />
+  );
+
+  const renderOption = (name: string) => (
+    <View style={styles.dropdownOption}>
+      <Text style={styles.placeholderStyle}>{name}</Text>
+    </View>
+  );
+
+  return (
+    <PageWrapper>
+      <Header label="Regions" />
+      <TouchableOpacity style={styles.megaSelector} onPress={() => setMegaSelectorVisible(true)}>
+        <Text style={styles.megaButtonText}>{selectedMega?.name}</Text>
+        <ChevronIcon width={18} height={18} />
+      </TouchableOpacity>
+
+      <View style={styles.buttonContainer}>
+        <CustomButton
+          title="All"
+          onPress={() => setContentIndex(0)}
+          isActive={contentIndex === 0}
+        />
+        <CustomButton
+          title="Not visited"
+          onPress={() => setContentIndex(1)}
+          isActive={contentIndex === 1}
+        />
+        <CustomButton
+          title="Visited"
+          onPress={() => setContentIndex(2)}
+          isActive={contentIndex === 2}
+        />
+      </View>
+
+      <View style={styles.progressHeader}>
+        <Text style={styles.textSmall}>Visited regions</Text>
+        <Text style={styles.textSmall}>
+          {nmRegions?.length
+            ? `${total}/${nmRegions.length} • ${((total * 100) / nmRegions.length).toFixed(2)}%`
+            : '0/0 • 100%'}
+        </Text>
+      </View>
+
+      <Progress.Bar
+        progress={nmRegions?.length ? total / nmRegions.length : 1}
+        width={null}
+        height={4}
+        color={Colors.DARK_BLUE}
+        borderWidth={0}
+        borderRadius={5}
+        unfilledColor={Colors.DARK_LIGHT}
+      />
+
+      {filteredNmRegions && (filteredNmRegions?.length || filteredTccRegions?.length) ? (
+        <FlatList
+          data={filteredNmRegions}
+          renderItem={renderItem}
+          keyExtractor={(item) => item.id.toString()}
+          showsVerticalScrollIndicator={false}
+          style={{ paddingTop: 8, marginBottom: 16 }}
+          ListFooterComponent={
+            filteredTccRegions && filteredTccRegions.length ? (
+              <View style={{ marginTop: 8 }}>
+                <Text style={[styles.textMedium, { textAlign: 'center' }]}>TCC regions</Text>
+                {filteredTccRegions?.map((item) => (
+                  <TccRegionItem item={item} updateTCC={handleUpdateTCC} key={item.id} />
+                ))}
+              </View>
+            ) : null
+          }
+        />
+      ) : null}
+
+      <ReactModal
+        isVisible={megaSelectorVisible}
+        onBackdropPress={() => setMegaSelectorVisible(false)}
+        style={styles.modal}
+        statusBarTranslucent={true}
+        presentationStyle="overFullScreen"
+      >
+        <View style={styles.wrapper}>
+          <ScrollView style={{ paddingBottom: 16 }} showsVerticalScrollIndicator={false}>
+            {megaregions?.data?.map((mega) => (
+              <TouchableOpacity
+                key={mega.id}
+                style={styles.btnOption}
+                onPress={() => {
+                  setMegaSelectorVisible(false);
+                  setSelectedMega(mega);
+                }}
+              >
+                <Text style={styles.btnOptionText}>{mega.name}</Text>
+              </TouchableOpacity>
+            ))}
+          </ScrollView>
+        </View>
+      </ReactModal>
+
+      <ReactModal
+        isVisible={isEditModalVisible}
+        onBackdropPress={() => setIsEditModalVisible(false)}
+        style={styles.modal}
+        statusBarTranslucent={true}
+        presentationStyle="overFullScreen"
+      >
+        <View style={styles.modalContent}>
+          <View style={styles.optionsContainer}>
+            <View style={styles.rowWrapper}>
+              <View style={styles.dropdownWrapper}>
+                <Text style={styles.textSmall}>First visit</Text>
+                <Dropdown
+                  style={styles.dropdown}
+                  placeholderStyle={styles.placeholderStyle}
+                  selectedTextStyle={styles.placeholderStyle}
+                  data={years}
+                  labelField="label"
+                  valueField="value"
+                  value={selectedFirstYear}
+                  placeholder="First visit"
+                  onChange={(item) => {
+                    setSelectedFirstYear(item.value);
+                    setSelectedLastYear(item.value);
+                  }}
+                  containerStyle={{ borderRadius: 4 }}
+                  renderItem={(item) => renderOption(item.label)}
+                />
+              </View>
+              <View style={styles.dropdownWrapper}>
+                <Text style={styles.textSmall}>Last visit</Text>
+                <Dropdown
+                  style={styles.dropdown}
+                  placeholderStyle={styles.placeholderStyle}
+                  selectedTextStyle={styles.placeholderStyle}
+                  data={years.filter((item) => item.value >= selectedFirstYear || item.value === 1)}
+                  labelField="label"
+                  valueField="value"
+                  value={selectedLastYear}
+                  placeholder="Last visit"
+                  onChange={(item) => setSelectedLastYear(item.value)}
+                  containerStyle={{ borderRadius: 4 }}
+                  renderItem={(item) => renderOption(item.label)}
+                />
+              </View>
+            </View>
+            <View style={styles.rowWrapper}>
+              <View style={styles.dropdownWrapper}>
+                <Text style={styles.textSmall}>Best visit quality</Text>
+                <Dropdown
+                  style={styles.dropdown}
+                  placeholderStyle={styles.placeholderStyle}
+                  containerStyle={{ borderRadius: 4 }}
+                  selectedTextStyle={styles.placeholderStyle}
+                  data={qualityOptions}
+                  labelField="name"
+                  valueField="id"
+                  value={selectedQuality.id}
+                  placeholder="Best visit quality"
+                  onChange={(item) => setSelectedQuality(item)}
+                  renderItem={(item) => renderOption(item.name)}
+                />
+              </View>
+              <View style={styles.dropdownWrapper}>
+                <Text style={styles.textSmall}>No of visits</Text>
+                <Dropdown
+                  style={styles.dropdown}
+                  placeholderStyle={styles.placeholderStyle}
+                  selectedTextStyle={styles.placeholderStyle}
+                  data={noOfVisits}
+                  labelField="label"
+                  valueField="value"
+                  value={selectedNoOfVisits}
+                  placeholder="No of visits"
+                  onChange={(item) => setSelectedNoOfVisits(item.value)}
+                  containerStyle={{ borderRadius: 4 }}
+                  renderItem={(item) => renderOption(item.label)}
+                />
+              </View>
+            </View>
+          </View>
+          <View style={styles.photosContent}>
+            <Text style={styles.textMedium}>Photos</Text>
+            <TouchableOpacity style={styles.addImgBtn}>
+              <AddImgSvg fill={Colors.DARK_BLUE} />
+              <Text style={styles.textSmall}>Add</Text>
+            </TouchableOpacity>
+          </View>
+          <Button
+            children="Done"
+            onPress={() => {
+              setIsEditModalVisible(false);
+              currentItem?.id &&
+                handleUpdateNM(
+                  currentItem.id,
+                  selectedFirstYear,
+                  selectedLastYear,
+                  selectedNoOfVisits,
+                  selectedQuality.id
+                );
+            }}
+          />
+          <Button
+            children="Close"
+            onPress={() => setIsEditModalVisible(false)}
+            variant={ButtonVariants.OPACITY}
+            containerStyles={styles.closeBtn}
+            textStyles={{ color: Colors.DARK_BLUE }}
+          />
+        </View>
+      </ReactModal>
+    </PageWrapper>
+  );
+};
+
+export default RegionsScreen;

+ 100 - 0
src/screens/InAppScreens/TravelsScreen/RegionsScreen/styles.tsx

@@ -0,0 +1,100 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from 'src/theme';
+
+export const styles = StyleSheet.create({
+  btnOption: {
+    paddingHorizontal: 16,
+    paddingVertical: 9,
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: 16
+  },
+  btnOptionText: { fontSize: 16, fontWeight: '600', color: Colors.DARK_BLUE },
+  wrapper: {
+    backgroundColor: Colors.WHITE,
+    padding: 16,
+    borderTopLeftRadius: 10,
+    borderTopRightRadius: 10,
+    height: '86%'
+  },
+  modal: {
+    justifyContent: 'flex-end',
+    margin: 0
+  },
+  megaSelector: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingHorizontal: 12,
+    paddingVertical: 8,
+    borderRadius: 6,
+    backgroundColor: Colors.DARK_LIGHT,
+    justifyContent: 'space-between',
+    marginBottom: 16
+  },
+  megaButtonText: {
+    color: Colors.DARK_BLUE,
+    fontWeight: 'bold',
+    fontSize: 13
+  },
+  progressHeader: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    marginBottom: 8
+  },
+  textSmall: {
+    color: Colors.DARK_BLUE,
+    fontWeight: '600',
+    fontSize: 12
+  },
+  buttonContainer: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    marginBottom: 16
+  },
+  modalContent: {
+    backgroundColor: 'white',
+    borderRadius: 15,
+    paddingHorizontal: 16,
+    gap: 16,
+    paddingVertical: 24
+  },
+  optionsContainer: {
+    gap: 16
+  },
+  closeBtn: { backgroundColor: Colors.WHITE, borderColor: '#B7C6CB' },
+  rowWrapper: {
+    width: '100%',
+    display: 'flex',
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    gap: 16
+  },
+  dropdownWrapper: { gap: 4, flex: 1 },
+  dropdown: {
+    height: 36,
+    backgroundColor: '#F4F4F4',
+    borderRadius: 4,
+    paddingHorizontal: 8
+  },
+  dropdownOption: { paddingVertical: 12, paddingHorizontal: 16 },
+  placeholderStyle: {
+    fontSize: 14,
+    color: Colors.DARK_BLUE,
+    fontWeight: '500'
+  },
+  textMedium: { fontWeight: 'bold', fontSize: 14, color: Colors.DARK_BLUE },
+  photosContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
+  addImgBtn: {
+    flexDirection: 'row',
+    gap: 4,
+    alignItems: 'center',
+    paddingHorizontal: 16,
+    paddingVertical: 7,
+    borderWidth: 1,
+    borderColor: Colors.BORDER_LIGHT,
+    borderRadius: 6
+  }
+});

+ 8 - 1
src/screens/InAppScreens/TravelsScreen/index.tsx

@@ -33,7 +33,14 @@ const TravelsScreen = () => {
     },
     },
     {
     {
       label: 'Regions',
       label: 'Regions',
-      icon: <RegionsIcon fill={Colors.DARK_BLUE} width={20} height={20} />
+      icon: <RegionsIcon fill={Colors.DARK_BLUE} width={20} height={20} />,
+      buttonFn: (navigation) => {
+        if (!token) {
+          setIsModalVisible(true);
+        } else {
+          navigation.navigate(NAVIGATION_PAGES.REGIONS);
+        }
+      }
     },
     },
     {
     {
       label: 'DARE',
       label: 'DARE',

+ 13 - 0
src/screens/InAppScreens/TravelsScreen/utils/constants.ts

@@ -6,3 +6,16 @@ export const qualityOptions = [
   { id: 5, name: 'Lived here' },
   { id: 5, name: 'Lived here' },
   { id: 6, name: 'Travelguru' }
   { id: 6, name: 'Travelguru' }
 ];
 ];
+
+export const noOfVisits = [
+  { label: '1', value: 1 },
+  { label: '2', value: 2 },
+  { label: '3', value: 3 },
+  { label: '4', value: 4 },
+  { label: '5', value: 5 },
+  { label: '6', value: 6 },
+  { label: '7', value: 7 },
+  { label: '8', value: 8 },
+  { label: '9', value: 9 },
+  { label: '10+', value: 10 }
+];

+ 20 - 0
src/screens/InAppScreens/TravelsScreen/utils/types.ts

@@ -89,3 +89,23 @@ export interface SlowData {
   slow101: 0 | 1;
   slow101: 0 | 1;
   yes: number;
   yes: number;
 }
 }
+
+export interface NmRegion {
+  id: number;
+  flag_1: string;
+  flag_2: string;
+  region_name: string;
+  essential: 0 | 1;
+  quality: number;
+  year: number;
+  last: number;
+  visits: number;
+}
+
+export interface TCCRegion {
+  id: number;
+  flag: string;
+  flag2: string;
+  name: string;
+  visited: number;
+}

+ 12 - 3
src/types/api.ts

@@ -11,7 +11,8 @@ export enum API_ROUTE {
   KYE = 'kye',
   KYE = 'kye',
   PHOTOS = 'photos',
   PHOTOS = 'photos',
   TRIPS = 'trips',
   TRIPS = 'trips',
-  SLOW = 'slow'
+  SLOW = 'slow',
+  QUICK_ENTER = 'quickEnter'
 }
 }
 
 
 export enum API_ENDPOINT {
 export enum API_ENDPOINT {
@@ -60,7 +61,11 @@ export enum API_ENDPOINT {
   UPDATE_TRIP = 'update-trip',
   UPDATE_TRIP = 'update-trip',
   DELETE_TRIP = 'delete-trip',
   DELETE_TRIP = 'delete-trip',
   GET_SLOW = 'get-slow-app',
   GET_SLOW = 'get-slow-app',
-  SET_SLOW = 'set-slow'
+  SET_SLOW = 'set-slow',
+  GET_MEGAREGIONS = 'get-megaregions',
+  GET_REGIONS_QE = 'get-regions-qe',
+  SET_NM_REGION = 'updateNM',
+  SET_TCC_REGION = 'updateTCC'
 }
 }
 
 
 export enum API {
 export enum API {
@@ -108,7 +113,11 @@ export enum API {
   UPDATE_TRIP = `${API_ROUTE.TRIPS}/${API_ENDPOINT.UPDATE_TRIP}`,
   UPDATE_TRIP = `${API_ROUTE.TRIPS}/${API_ENDPOINT.UPDATE_TRIP}`,
   DELETE_TRIP = `${API_ROUTE.TRIPS}/${API_ENDPOINT.DELETE_TRIP}`,
   DELETE_TRIP = `${API_ROUTE.TRIPS}/${API_ENDPOINT.DELETE_TRIP}`,
   GET_SLOW = `${API_ROUTE.SLOW}/${API_ENDPOINT.GET_SLOW}`,
   GET_SLOW = `${API_ROUTE.SLOW}/${API_ENDPOINT.GET_SLOW}`,
-  SET_SLOW = `${API_ROUTE.SLOW}/${API_ENDPOINT.SET_SLOW}`
+  SET_SLOW = `${API_ROUTE.SLOW}/${API_ENDPOINT.SET_SLOW}`,
+  GET_MEGAREGIONS = `${API_ROUTE.REGIONS}/${API_ENDPOINT.GET_MEGAREGIONS}`,
+  GET_REGIONS_QE = `${API_ROUTE.REGIONS}/${API_ENDPOINT.GET_REGIONS_QE}`,
+  SET_NM_REGION = `${API_ROUTE.QUICK_ENTER}/${API_ENDPOINT.SET_NM_REGION}`,
+  SET_TCC_REGION = `${API_ROUTE.QUICK_ENTER}/${API_ENDPOINT.SET_TCC_REGION}`
 }
 }
 
 
 export type BaseAxiosError = AxiosError;
 export type BaseAxiosError = AxiosError;

+ 2 - 1
src/types/navigation.ts

@@ -31,5 +31,6 @@ export enum NAVIGATION_PAGES {
   TRIPS = 'inAppTrips',
   TRIPS = 'inAppTrips',
   ADD_TRIP = 'inAppAddTrip',
   ADD_TRIP = 'inAppAddTrip',
   ADD_REGIONS = 'inAppAddRegions',
   ADD_REGIONS = 'inAppAddRegions',
-  COUNTRIES = 'inAppCountries'
+  COUNTRIES = 'inAppCountries',
+  REGIONS = 'inAppRegions',
 }
 }