Bladeren bron

regions selector for trips

Viktoriia 1 maand geleden
bovenliggende
commit
e2a2d6d435

+ 19 - 5
src/components/FlatList/index.tsx

@@ -3,7 +3,7 @@ import { FlatList as List, View } from 'react-native';
 import { SafeAreaView } from 'react-native-safe-area-context';
 import { Input } from '../Input';
 import { styles } from './styles';
-import { Item, ItemData } from './item';
+import { Item, ItemData, RegionsData } from './item';
 import { useGetRegionsWithFlagQuery } from '@api/regions';
 import { useFocusEffect } from '@react-navigation/native';
 import { Loading } from '../Loading';
@@ -14,28 +14,42 @@ type Props = {
   initialData?: ItemData[] | string[];
   date?: boolean;
   countries?: boolean;
+  filteredRegions?: RegionsData[];
 };
 
 //TODO: rework to generic types + custom props
 
-export const FlatList: FC<Props> = ({ itemObject, initialData, date, countries }) => {
+export const FlatList: FC<Props> = ({
+  itemObject,
+  initialData,
+  date,
+  countries,
+  filteredRegions
+}) => {
   const [selectedObject, setSelectedObject] = useState<{ name: string; id: number } | string>();
   const [search, setSearch] = useState('');
   const [filteredData, setFilteredData] = useState<ItemData[] | string[]>([]);
   const [masterData, setMasterData] = useState<ItemData[] | string[]>([]);
   const [loading, setLoading] = useState<boolean>(true);
 
-  const { data } = useGetRegionsWithFlagQuery(true);
+  const { data } = useGetRegionsWithFlagQuery(filteredRegions ? false : true);
 
   useFocusEffect(
     useCallback(() => {
+      if (filteredRegions) {
+        setFilteredData(filteredRegions ?? []);
+        setMasterData(filteredRegions ?? []);
+        setLoading(false);
+
+        return;
+      }
       const dataToUse = initialData || data?.data;
       if (dataToUse) {
         setFilteredData(dataToUse);
         setMasterData(dataToUse);
       }
       setLoading(false);
-    }, [data, initialData])
+    }, [data, initialData, filteredRegions])
   );
 
   if (loading) return <Loading />;
@@ -76,6 +90,7 @@ export const FlatList: FC<Props> = ({ itemObject, initialData, date, countries }
         initial={initialData ? true : false}
         date={date}
         countries={countries}
+        isRegionFromTrips={!!filteredRegions}
       />
     );
   };
@@ -98,7 +113,6 @@ export const FlatList: FC<Props> = ({ itemObject, initialData, date, countries }
           itemVisiblePercentThreshold: 50,
           minimumViewTime: 1000
         }}
-        estimatedItemSize={50}
         data={filteredData as ItemData[]}
         renderItem={renderItem}
         keyExtractor={(item) => (date ? item.toString() : item.id.toString())}

+ 73 - 20
src/components/FlatList/item.tsx

@@ -13,15 +13,20 @@ export const Item = ({
   selected,
   initial,
   date,
-  countries
+  countries,
+  isRegionFromTrips
 }: ItemProps) => {
   const name = initial && date ? item : initial ? item.country : item.name?.split('–') || '';
 
+  const [regionName, ...rest] =
+    isRegionFromTrips && typeof item.name === 'string' ? item.name.split(/ – | - /) : ['', ''];
+  const subName = rest.length > 0 ? rest.join(' - ') : '';
+
   return (
     <TouchableOpacity onPress={onPress} style={[styles.item, { backgroundColor }]}>
       {item?.country === 'All Regions' && <View style={{ width: 20 }}></View>}
       <View style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 10 }}>
-        {item.flag && (
+        {item.flag && !isRegionFromTrips ? (
           <Image
             width={48}
             height={48}
@@ -30,26 +35,64 @@ export const Item = ({
               uri: initial ? `${API_HOST}${item.flag}` : `${API_HOST}/img/flags_new/${item.flag}`
             }}
           />
-        )}
-        <View style={{ flexShrink: 1 }}>
-          <View style={{ flexDirection: 'row' }}>
-            <Text style={[styles.title, { color: Colors.DARK_BLUE, flexShrink: 1 }]}>
-              {initial ? (name as string) : (name as string[])[0]}
-            </Text>
-            {initial && !date && item.country !== 'All Regions' && !countries && (
-              <View style={styles.regionIndicator}>
-                <Text style={[styles.text, { color: Colors.WHITE, fontWeight: 'bold' }]}>
-                  {item.dare ? 'DARE' : 'NM'}
-                </Text>
-              </View>
+        ) : null}
+        {isRegionFromTrips ? (
+          <View style={{ flexDirection: 'row', alignItems: 'center' }}>
+            <Image
+              width={48}
+              height={48}
+              style={{ borderRadius: 48 / 2, borderWidth: 1, borderColor: Colors.LIGHT_GRAY }}
+              source={{
+                uri: `${API_HOST}${item.flag1}`
+              }}
+            />
+            {item.flag2 ? (
+              <Image
+                width={48}
+                height={48}
+                style={{
+                  borderRadius: 48 / 2,
+                  borderWidth: 1,
+                  borderColor: Colors.LIGHT_GRAY,
+                  marginLeft: -15
+                }}
+                source={{
+                  uri: `${API_HOST}${item.flag2}`
+                }}
+              />
+            ) : null}
+          </View>
+        ) : null}
+        {isRegionFromTrips ? (
+          <View style={{ flexShrink: 1 }}>
+            <View style={{ flexDirection: 'row' }}>
+              <Text style={[styles.title, { color: Colors.DARK_BLUE, flexShrink: 1 }]}>
+                {regionName}
+              </Text>
+            </View>
+            {subName && <Text style={[styles.text, { color: Colors.DARK_BLUE }]}>{subName}</Text>}
+          </View>
+        ) : (
+          <View style={{ flexShrink: 1 }}>
+            <View style={{ flexDirection: 'row' }}>
+              <Text style={[styles.title, { color: Colors.DARK_BLUE, flexShrink: 1 }]}>
+                {initial ? (name as string) : (name as string[])[0]}
+              </Text>
+              {initial && !date && item.country !== 'All Regions' && !countries && (
+                <View style={styles.regionIndicator}>
+                  <Text style={[styles.text, { color: Colors.WHITE, fontWeight: 'bold' }]}>
+                    {item.dare ? 'DARE' : 'NM'}
+                  </Text>
+                </View>
+              )}
+            </View>
+            {item.name && (
+              <Text style={[styles.text, { color: Colors.DARK_BLUE }]}>
+                {initial ? item.name : (name as string[])[1]}
+              </Text>
             )}
           </View>
-          {item.name && (
-            <Text style={[styles.text, { color: Colors.DARK_BLUE }]}>
-              {initial ? item.name : (name as string[])[1]}
-            </Text>
-          )}
-        </View>
+        )}
       </View>
       <View style={{ marginRight: 10, width: 20 }}>{selected && <MarkSVG />}</View>
     </TouchableOpacity>
@@ -62,6 +105,15 @@ export type ItemData = {
   flag?: string;
   country?: string;
   dare?: boolean;
+  flag1?: string;
+  flag2?: string | null;
+};
+
+export type RegionsData = {
+  id: number;
+  name: string;
+  flag1: string;
+  flag2: string | null;
 };
 
 type ItemProps = {
@@ -72,4 +124,5 @@ type ItemProps = {
   initial?: boolean;
   date?: boolean;
   countries?: boolean;
+  isRegionFromTrips?: boolean;
 };

+ 2 - 0
src/modules/api/trips/queries/index.ts

@@ -5,3 +5,5 @@ export * from './use-post-delete-trip';
 export * from './use-post-get-trip';
 export * from './use-post-set-new-trip';
 export * from './use-post-set-update-trip';
+export * from './use-post-get-trips-for-region';
+export * from './use-post-get-regions-that-have-trips';

+ 17 - 0
src/modules/api/trips/queries/use-post-get-regions-that-have-trips.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { tripsQueryKeys } from '../trips-query-keys';
+import { tripsApi, type PostGetRegionsThatHaveTripsReturn } from '../trips-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetRegionsThatHaveTripsQuery = (enabled: boolean) => {
+  return useQuery<PostGetRegionsThatHaveTripsReturn, BaseAxiosError>({
+    queryKey: tripsQueryKeys.getRegionsThatHaveTrips(),
+    queryFn: async () => {
+      const response = await tripsApi.getRegionsThatHaveTrips();
+      return response.data;
+    },
+    enabled
+  });
+};

+ 17 - 0
src/modules/api/trips/queries/use-post-get-trips-for-region.tsx

@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query';
+
+import { tripsQueryKeys } from '../trips-query-keys';
+import { tripsApi, type PostGetTripsForRegionReturn } from '../trips-api';
+
+import type { BaseAxiosError } from '../../../../types';
+
+export const useGetTripsForRegionQuery = (token: string, region: number, enabled: boolean) => {
+  return useQuery<PostGetTripsForRegionReturn, BaseAxiosError>({
+    queryKey: tripsQueryKeys.getTripsForRegion(token, region),
+    queryFn: async () => {
+      const response = await tripsApi.getTripsForRegion(token, region);
+      return response.data;
+    },
+    enabled
+  });
+};

+ 31 - 1
src/modules/api/trips/trips-api.tsx

@@ -53,6 +53,23 @@ export interface PostGetTripsForYearReturn extends ResponseType {
   } | null;
 }
 
+export interface PostGetTripsForRegionReturn extends ResponseType {
+  trips: {
+    id: number;
+    date_from: string;
+    date_to: string;
+    description: string;
+    regions: {
+      region_name: string;
+      flag1: string;
+      flag2: string | null;
+      status: 0 | 1;
+      id: number;
+    }[];
+    dates_missing: 0 | 1;
+  }[];
+}
+
 export interface RegionData {
   id: number;
   quality: number;
@@ -123,11 +140,22 @@ export interface PostGetRegionsForTripsReturn extends ResponseType {
   }[];
 }
 
+export interface PostGetRegionsThatHaveTripsReturn extends ResponseType {
+  data: {
+    id: number;
+    name: string;
+    flag1: string;
+    flag2: string | null;
+  }[];
+}
+
 export const tripsApi = {
   getTripsYears: (token: string) =>
     request.postForm<PostGetTripsYearsReturn>(API.GET_TRIPS_YEARS, { token }),
   getTripsForYear: (token: string, year: string) =>
     request.postForm<PostGetTripsForYearReturn>(API.GET_TRIPS_FOR_YEAR, { token, year }),
+  getTripsForRegion: (token: string, region: number) =>
+    request.postForm<PostGetTripsForRegionReturn>(API.GET_TRIPS_FOR_REGION, { token, region }),
   setNewTrip: (data: PostSetNewTrip) =>
     request.postForm<PostSetNewTripReturn>(API.SET_NEW_TRIP, data),
   updateTrip: (data: PostUpdateTrip) => request.postForm<ResponseType>(API.UPDATE_TRIP, data),
@@ -135,5 +163,7 @@ export const tripsApi = {
     request.postForm<ResponseType>(API.DELETE_TRIP, { token, trip_id }),
   getTrip: (token: string, trip_id: number) =>
     request.postForm<PostGetTripReturn>(API.GET_TRIP, { token, trip_id }),
-  getRegionsForTrips: () => request.get<PostGetRegionsForTripsReturn>(API.GET_REGIONS_FOR_TRIPS)
+  getRegionsForTrips: () => request.get<PostGetRegionsForTripsReturn>(API.GET_REGIONS_FOR_TRIPS),
+  getRegionsThatHaveTrips: () =>
+    request.get<PostGetRegionsThatHaveTripsReturn>(API.GET_REGIONS_THAT_HAVE_TRIPS)
 };

+ 4 - 1
src/modules/api/trips/trips-query-keys.tsx

@@ -5,5 +5,8 @@ export const tripsQueryKeys = {
   updateTrip: () => ['updateTrip'] as const,
   deleteTrip: () => ['deleteTrip'] as const,
   getTrip: (token: string, trip_id: number) => ['getTrip', { token, trip_id }] as const,
-  getRegionsForTrips: () => ['getRegionsForTrips'] as const
+  getRegionsForTrips: () => ['getRegionsForTrips'] as const,
+  getTripsForRegion: (token: string, region: number) =>
+    ['getTripsForRegion', { token, region }] as const,
+  getRegionsThatHaveTrips: () => ['getRegionsThatHaveTrips'] as const
 };

+ 55 - 4
src/screens/InAppScreens/TravelsScreen/Trips2025Screen/index.tsx

@@ -5,7 +5,12 @@ import { useFocusEffect, useNavigation } from '@react-navigation/native';
 import { PageWrapper, Header, Modal, FlatList as List, WarningModal } from 'src/components';
 import TripItem from '../Components/TripItem';
 
-import { useGetTripsYearsQuery, useGetTripsForYearQuery } from '@api/trips';
+import {
+  useGetTripsYearsQuery,
+  useGetTripsForYearQuery,
+  useGetTripsForRegionQuery,
+  useGetRegionsThatHaveTripsQuery
+} from '@api/trips';
 import { StoreType, storage } from 'src/storage';
 import { TripsData } from '../utils/types';
 import { NAVIGATION_PAGES } from 'src/types';
@@ -28,11 +33,23 @@ const TripsScreen = ({ route }: { route: any }) => {
   const [isDatePickerVisible, setDatePickerVisible] = useState(false);
   const { data: years, refetch } = useGetTripsYearsQuery(token, true);
   const [selectedYear, setSelectedYear] = useState<string | null>(moment().year().toString());
+  const [selectedRegion, setSelectedRegion] = useState<{ id: number; name: string } | null>(
+    route.params?.region
+      ? { id: route.params?.region?.id, name: route.params?.region?.name ?? '' }
+      : null
+  );
   const { data: tripsData, refetch: refetchTrips } = useGetTripsForYearQuery(
     token,
     selectedYear as string,
     selectedYear ? true : false
   );
+  const { data: tripsDataForRegion, refetch: refetchTripsForRegion } = useGetTripsForRegionQuery(
+    token,
+    selectedRegion ? selectedRegion.id : -1,
+    selectedRegion ? true : false
+  );
+  const { data: regionsThatHaveTrips, refetch: refetchRegionsThatHaveTrips } =
+    useGetRegionsThatHaveTripsQuery(true);
   const [trips, setTrips] = useState<TripsData[]>([]);
   const [statistics, setStatistics] = useState<{
     countries: {
@@ -65,6 +82,7 @@ const TripsScreen = ({ route }: { route: any }) => {
     dates_missing: 0 | 1;
   } | null>(null);
   const [isWarningModalVisible, setIsWarningModalVisible] = useState(false);
+  const [isModalVisible, setIsModalVisible] = useState(false);
 
   const flashListRef = useRef<FlashListRef<any> | null>(null);
 
@@ -99,11 +117,18 @@ const TripsScreen = ({ route }: { route: any }) => {
   }, [isWarningModalVisible]);
 
   useEffect(() => {
-    if (tripsData && tripsData.trips) {
+    if (tripsData && tripsData.trips && !selectedRegion) {
       setStatistics(tripsData.statistics ?? null);
       setTrips(tripsData.trips);
     }
-  }, [tripsData]);
+  }, [tripsData, selectedRegion]);
+
+  useEffect(() => {
+    if (tripsDataForRegion && tripsDataForRegion.trips && selectedRegion) {
+      setStatistics(null);
+      setTrips(tripsDataForRegion.trips ?? []);
+    }
+  }, [tripsDataForRegion, selectedRegion]);
 
   const renderItem = useCallback(
     ({ item }: { item: TripsData }) => <TripItem item={item} isNew={!!statistics} />,
@@ -146,7 +171,7 @@ const TripsScreen = ({ route }: { route: any }) => {
       </View>
       <View style={styles.tabContainer}>
         <TouchableOpacity style={styles.regionSelector} onPress={() => setDatePickerVisible(true)}>
-          <Text style={[styles.regionText]}>{selectedYear}</Text>
+          <Text style={[styles.regionText]}>{selectedYear ?? 'All years'}</Text>
           <ChevronIcon />
         </TouchableOpacity>
         <TouchableOpacity style={styles.addNewTab} onPress={onAddNewTripPress}>
@@ -155,6 +180,16 @@ const TripsScreen = ({ route }: { route: any }) => {
         </TouchableOpacity>
       </View>
 
+      <TouchableOpacity
+        style={[styles.regionSelector, { flex: 0 }]}
+        onPress={() => setIsModalVisible(true)}
+      >
+        <Text style={styles.regionText}>
+          {selectedRegion ? selectedRegion.name : 'All regions'}
+        </Text>
+        <ChevronIcon />
+      </TouchableOpacity>
+
       {trips.length === 0 ? (
         <View style={styles.noTripsContainer}>
           <View style={styles.noTripsIcon}>
@@ -256,6 +291,7 @@ const TripsScreen = ({ route }: { route: any }) => {
         <List
           itemObject={(object) => {
             setSelectedYear(object);
+            setSelectedRegion(null);
             setDatePickerVisible(false);
           }}
           initialData={years?.data}
@@ -283,6 +319,21 @@ const TripsScreen = ({ route }: { route: any }) => {
               : 'Trip was successfully updated'
         }
       />
+
+      <Modal
+        onRequestClose={() => setIsModalVisible(false)}
+        headerTitle={'Select Region'}
+        visible={isModalVisible}
+      >
+        <List
+          filteredRegions={regionsThatHaveTrips?.data}
+          itemObject={(object) => {
+            setIsModalVisible(false);
+            setSelectedRegion(object);
+            setSelectedYear(null);
+          }}
+        />
+      </Modal>
     </PageWrapper>
   );
 };

+ 6 - 2
src/types/api.ts

@@ -224,7 +224,9 @@ export enum API_ENDPOINT {
   SET_NOT_VISITED = 'set-not-visited',
   GET_GROUP_CONVERSATION_ALL = 'get-group-conversation-all',
   GET_CONVERSATION_WITH_ALL = 'get-conversation-with-all',
-  GET_USERS_WHO_TICKED_SERIES = 'get-users-who-ticked-series'
+  GET_USERS_WHO_TICKED_SERIES = 'get-users-who-ticked-series',
+  GET_TRIPS_FOR_REGION = 'get-trips-for-region',
+  GET_REGIONS_THAT_HAVE_TRIPS = 'get-regions-that-have-trips'
 }
 
 export enum API {
@@ -422,7 +424,9 @@ export enum API {
   SET_NOT_VISITED = `${API_ROUTE.QUICK_ENTER}/${API_ENDPOINT.SET_NOT_VISITED}`,
   GET_GROUP_CONVERSATION_ALL = `${API_ROUTE.CHAT}/${API_ENDPOINT.GET_GROUP_CONVERSATION_ALL}`,
   GET_CONVERSATION_WITH_ALL = `${API_ROUTE.CHAT}/${API_ENDPOINT.GET_CONVERSATION_WITH_ALL}`,
-  GET_USERS_WHO_TICKED_SERIES = `${API_ROUTE.SERIES}/${API_ENDPOINT.GET_USERS_WHO_TICKED_SERIES}`
+  GET_USERS_WHO_TICKED_SERIES = `${API_ROUTE.SERIES}/${API_ENDPOINT.GET_USERS_WHO_TICKED_SERIES}`,
+  GET_TRIPS_FOR_REGION = `${API_ROUTE.TRIPS}/${API_ENDPOINT.GET_TRIPS_FOR_REGION}`,
+  GET_REGIONS_THAT_HAVE_TRIPS = `${API_ROUTE.TRIPS}/${API_ENDPOINT.GET_REGIONS_THAT_HAVE_TRIPS}`
 }
 
 export type BaseAxiosError = AxiosError;