Browse Source

regions-screen

Viktoriia 1 year ago
parent
commit
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 AddRegionsScreen from 'src/screens/InAppScreens/TravelsScreen/AddRegionsScreen';
 import CountriesScreen from 'src/screens/InAppScreens/TravelsScreen/CountriesScreen';
+import RegionsScreen from 'src/screens/InAppScreens/TravelsScreen/RegionsScreen';
 
 import { NAVIGATION_PAGES } from './src/types';
 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_REGIONS} component={AddRegionsScreen} />
                   <ScreenStack.Screen name={NAVIGATION_PAGES.COUNTRIES} component={CountriesScreen} />
+                  <ScreenStack.Screen name={NAVIGATION_PAGES.REGIONS} component={RegionsScreen} />
                 </ScreenStack.Navigator>
               )}
             </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)
   version: '1.0.0',
   // Should be updated after every dependency change
-  runtimeVersion: '1.2',
+  runtimeVersion: '1.3',
   orientation: 'portrait',
   icon: './assets/icon.png',
   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",
     "postinstall-postinstall": "^2.1.0",
     "react": "18.2.0",
-    "react-native": "0.72.6",
+    "react-native": "0.72.10",
     "react-native-calendar-picker": "^7.1.4",
     "react-native-calendars": "^1.1304.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',
-      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',

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

@@ -6,3 +6,16 @@ export const qualityOptions = [
   { id: 5, name: 'Lived here' },
   { 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;
   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',
   PHOTOS = 'photos',
   TRIPS = 'trips',
-  SLOW = 'slow'
+  SLOW = 'slow',
+  QUICK_ENTER = 'quickEnter'
 }
 
 export enum API_ENDPOINT {
@@ -60,7 +61,11 @@ export enum API_ENDPOINT {
   UPDATE_TRIP = 'update-trip',
   DELETE_TRIP = 'delete-trip',
   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 {
@@ -108,7 +113,11 @@ export enum API {
   UPDATE_TRIP = `${API_ROUTE.TRIPS}/${API_ENDPOINT.UPDATE_TRIP}`,
   DELETE_TRIP = `${API_ROUTE.TRIPS}/${API_ENDPOINT.DELETE_TRIP}`,
   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;

+ 2 - 1
src/types/navigation.ts

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