Selaa lähdekoodia

remove participants func + ui fix

Viktoriia 1 kuukausi sitten
vanhempi
commit
8ee26aaf46

+ 3 - 1
src/modules/api/events/events-api.ts

@@ -247,5 +247,7 @@ export const eventsApi = {
     request.postForm<PostGetEventForEditingReturn>(API.GET_EVENT_FOR_EDITING, { token, event_id }),
     request.postForm<PostGetEventForEditingReturn>(API.GET_EVENT_FOR_EDITING, { token, event_id }),
   updateEvent: (data: PostUpdateEvent) => request.postForm<ResponseType>(API.UPDATE_EVENT, data),
   updateEvent: (data: PostUpdateEvent) => request.postForm<ResponseType>(API.UPDATE_EVENT, data),
   cancelEvent: (token: string, event_id: number) =>
   cancelEvent: (token: string, event_id: number) =>
-    request.postForm<ResponseType>(API.CANCEL_EVENT, { token, event_id })
+    request.postForm<ResponseType>(API.CANCEL_EVENT, { token, event_id }),
+  removeParticipant: (token: string, event_id: number, user_id: number) =>
+    request.postForm<ResponseType>(API.REMOVE_PARTICIPANT, { token, event_id, user_id })
 };
 };

+ 1 - 0
src/modules/api/events/events-query-keys.tsx

@@ -16,4 +16,5 @@ export const eventsQueryKeys = {
   getEventForEditing: () => ['getEventForEditing'] as const,
   getEventForEditing: () => ['getEventForEditing'] as const,
   updateEvent: () => ['updateEvent'] as const,
   updateEvent: () => ['updateEvent'] as const,
   cancelEvent: () => ['cancelEvent'] as const,
   cancelEvent: () => ['cancelEvent'] as const,
+  removeParticipant: () => ['removeParticipant'] as const
 };
 };

+ 1 - 0
src/modules/api/events/queries/index.ts

@@ -13,3 +13,4 @@ export * from './use-post-add-event';
 export * from './use-post-get-event-for-editing';
 export * from './use-post-get-event-for-editing';
 export * from './use-post-update-event';
 export * from './use-post-update-event';
 export * from './use-post-cancel-event';
 export * from './use-post-cancel-event';
+export * from './use-post-remove-participant';

+ 26 - 0
src/modules/api/events/queries/use-post-remove-participant.tsx

@@ -0,0 +1,26 @@
+import { useMutation } from '@tanstack/react-query';
+
+import { eventsQueryKeys } from '../events-query-keys';
+import { type PostJoinEvent, eventsApi } from '../events-api';
+
+import type { BaseAxiosError } from '../../../../types';
+import { ResponseType } from '@api/response-type';
+
+export const usePostRemoveParticipantMutation = () => {
+  return useMutation<
+    ResponseType,
+    BaseAxiosError,
+    { token: string; event_id: number; user_id: number },
+    ResponseType
+  >({
+    mutationKey: eventsQueryKeys.removeParticipant(),
+    mutationFn: async (variables) => {
+      const response = await eventsApi.removeParticipant(
+        variables.token,
+        variables.event_id,
+        variables.user_id
+      );
+      return response.data;
+    }
+  });
+};

+ 15 - 1
src/screens/InAppScreens/MapScreen/UsersListScreen/Profile/index.tsx

@@ -14,6 +14,7 @@ import UN75Icon from 'assets/icons/un-75.svg';
 import UN100Icon from 'assets/icons/un-100.svg';
 import UN100Icon from 'assets/icons/un-100.svg';
 import UN150Icon from 'assets/icons/un-150.svg';
 import UN150Icon from 'assets/icons/un-150.svg';
 import NMIcon from 'assets/icons/nm_icon.svg';
 import NMIcon from 'assets/icons/nm_icon.svg';
+import DotsIcon from 'assets/icons/messages/dots-vertical.svg';
 
 
 import { useConnection } from 'src/contexts/ConnectionContext';
 import { useConnection } from 'src/contexts/ConnectionContext';
 import { NAVIGATION_PAGES } from 'src/types';
 import { NAVIGATION_PAGES } from 'src/types';
@@ -47,6 +48,8 @@ type Props = {
   badge_un_150: number;
   badge_un_150: number;
   auth: number;
   auth: number;
   userId: number;
   userId: number;
+  withMenu?: boolean;
+  onPressOption?: () => void;
 };
 };
 
 
 export const Profile: FC<Props> = ({
 export const Profile: FC<Props> = ({
@@ -69,7 +72,9 @@ export const Profile: FC<Props> = ({
   badge_un_100,
   badge_un_100,
   badge_un_150,
   badge_un_150,
   auth,
   auth,
-  userId
+  userId,
+  withMenu,
+  onPressOption
 }) => {
 }) => {
   const navigation = useNavigation();
   const navigation = useNavigation();
 
 
@@ -156,6 +161,15 @@ export const Profile: FC<Props> = ({
               {scoreNames[active_score]}
               {scoreNames[active_score]}
             </Text>
             </Text>
           </View>
           </View>
+          {withMenu ? (
+            <TouchableOpacity
+              style={{ padding: 6 }}
+              onPress={onPressOption}
+              hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
+            >
+              <DotsIcon />
+            </TouchableOpacity>
+          ) : null}
         </View>
         </View>
       </TouchableOpacity>
       </TouchableOpacity>
       {modalType && (
       {modalType && (

+ 46 - 42
src/screens/InAppScreens/MessagesScreen/Components/Profile.tsx

@@ -126,14 +126,48 @@ export const Profile: FC<Props> = ({
             />
             />
           ) : null}
           ) : null}
           <View style={adaptiveStyle(ProfileStyles.profileDataRoot, {})}>
           <View style={adaptiveStyle(ProfileStyles.profileDataRoot, {})}>
-            <Text
-              style={adaptiveStyle(
-                [ProfileStyles.profileFirstLastName, { fontSize: getFontSize(14), flex: 0 }],
-                {}
-              )}
+            <View
+              style={{
+                flexDirection: 'row',
+                flex: 1,
+                alignItems: 'center',
+                gap: 5,
+                maxWidth: '85%'
+              }}
             >
             >
-              {first_name ?? ''} {last_name ?? ''}
-            </Text>
+              <Text
+                style={adaptiveStyle(
+                  [ProfileStyles.profileFirstLastName, { fontSize: getFontSize(14), flex: 0 }],
+                  {}
+                )}
+              >
+                {first_name ?? ''} {last_name ?? ''}
+              </Text>
+              {admin === 1 && (
+                <View
+                  style={{
+                    justifyContent: 'center',
+                    alignItems: 'center',
+                    backgroundColor: Colors.ORANGE,
+                    height: 12,
+                    width: 38,
+                    borderRadius: 3
+                  }}
+                >
+                  <Text
+                    style={{
+                      color: Colors.WHITE,
+                      fontSize: getFontSize(10),
+                      fontWeight: '600',
+                      textAlign: 'center'
+                    }}
+                  >
+                    Admin
+                  </Text>
+                </View>
+              )}
+            </View>
+
             <View style={adaptiveStyle(ProfileStyles.profileDataContainer, {})}>
             <View style={adaptiveStyle(ProfileStyles.profileDataContainer, {})}>
               <View style={adaptiveStyle(ProfileStyles.profileDataWrapper, {})}>
               <View style={adaptiveStyle(ProfileStyles.profileDataWrapper, {})}>
                 <Text style={adaptiveStyle(ProfileStyles.profileAge, {})}>
                 <Text style={adaptiveStyle(ProfileStyles.profileAge, {})}>
@@ -164,39 +198,6 @@ export const Profile: FC<Props> = ({
               </View>
               </View>
             </View>
             </View>
           </View>
           </View>
-          {admin === 1 && (
-            <View
-              style={{
-                justifyContent: 'center',
-                alignItems: 'center',
-                width: 12,
-                height: '100%'
-              }}
-            >
-              <View
-                style={{
-                  transform: [{ rotate: '-90deg' }],
-                  justifyContent: 'center',
-                  alignItems: 'center',
-                  backgroundColor: Colors.ORANGE,
-                  height: 12,
-                  width: 38,
-                  borderRadius: 3
-                }}
-              >
-                <Text
-                  style={{
-                    color: Colors.WHITE,
-                    fontSize: getFontSize(10),
-                    fontWeight: '600',
-                    textAlign: 'center'
-                  }}
-                >
-                  Admin
-                </Text>
-              </View>
-            </View>
-          )}
 
 
           <View style={{ alignItems: 'center' }}>
           <View style={{ alignItems: 'center' }}>
             <Text style={adaptiveStyle(ScoreStyles.activeScoreRanking, {})}>
             <Text style={adaptiveStyle(ScoreStyles.activeScoreRanking, {})}>
@@ -207,7 +208,7 @@ export const Profile: FC<Props> = ({
             </Text>
             </Text>
           </View>
           </View>
 
 
-          {userId !== +currentUserId && canChangeAdmin && (
+          {userId !== +currentUserId && canChangeAdmin ? (
             <TouchableOpacity
             <TouchableOpacity
               style={{ padding: 6 }}
               style={{ padding: 6 }}
               onPress={() =>
               onPress={() =>
@@ -225,10 +226,13 @@ export const Profile: FC<Props> = ({
                   } as any
                   } as any
                 })
                 })
               }
               }
+              hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
             >
             >
               <DotsIcon />
               <DotsIcon />
             </TouchableOpacity>
             </TouchableOpacity>
-          )}
+          ) : canChangeAdmin ? (
+            <View style={{ width: 17 }} />
+          ) : null}
         </View>
         </View>
       </TouchableOpacity>
       </TouchableOpacity>
       {modalType && (
       {modalType && (

+ 124 - 0
src/screens/InAppScreens/TravelsScreen/Components/OptionsModal.tsx

@@ -0,0 +1,124 @@
+import React, { useState } from 'react';
+import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
+import ActionSheet, { SheetManager } from 'react-native-actions-sheet';
+import { Colors } from 'src/theme';
+import { getFontSize } from 'src/utils';
+import BanIcon from 'assets/icons/messages/ban.svg';
+import { usePostRemoveParticipantMutation } from '@api/events';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
+const OptionsModal = () => {
+  const insets = useSafeAreaInsets();
+  const [userData, setUserData] = useState<any>(null);
+  const { mutateAsync: removeParticipant } = usePostRemoveParticipantMutation();
+
+  const [shouldOpenWarningModal, setShouldOpenWarningModal] = useState<any>(null);
+
+  const handleSheetOpen = (payload: any) => {
+    setUserData(payload);
+  };
+
+  const handleRemoveParticipant = async () => {
+    if (!userData) return;
+
+    setShouldOpenWarningModal({
+      title: `Remove ${userData?.name}`,
+      message: `Are you sure you want to remove ${userData?.name} as a participant?`,
+      action: async () => {
+        await removeParticipant(
+          {
+            token: userData.token,
+            event_id: userData.eventId,
+            user_id: userData.userId
+          },
+          {
+            onSuccess: () => {
+              userData.filterParticipants();
+            }
+          }
+        );
+      },
+      buttonTitle: 'Remove',
+      isWarningVisible: true
+    });
+
+    setTimeout(() => {
+      SheetManager.hide('host-options-modal');
+      setShouldOpenWarningModal(null);
+    }, 300);
+  };
+
+  return (
+    <ActionSheet
+      id="host-options-modal"
+      gestureEnabled={true}
+      onBeforeShow={(sheetRef) => {
+        const payload = sheetRef || null;
+        handleSheetOpen(payload);
+      }}
+      onClose={() => {
+        if (shouldOpenWarningModal) {
+          userData?.setIsWarningVisible(shouldOpenWarningModal);
+        }
+      }}
+      onBeforeClose={() => SheetManager.hide('host-options-modal')}
+      containerStyle={styles.sheetContainer}
+      defaultOverlayOpacity={0.5}
+      indicatorStyle={{ backgroundColor: 'transparent' }}
+    >
+      {userData && (
+        <View style={[styles.container, { paddingBottom: insets.bottom + 8 }]}>
+          <View style={[styles.optionsContainer, { paddingVertical: 0, gap: 0 }]}>
+            <TouchableOpacity
+              style={[styles.option, styles.dangerOption]}
+              onPress={handleRemoveParticipant}
+            >
+              <Text style={[styles.optionText, styles.dangerText]}>Remove participant</Text>
+              <BanIcon fill={Colors.RED} />
+            </TouchableOpacity>
+          </View>
+        </View>
+      )}
+    </ActionSheet>
+  );
+};
+
+const styles = StyleSheet.create({
+  sheetContainer: {
+    borderTopLeftRadius: 15,
+    borderTopRightRadius: 15
+  },
+  container: {
+    backgroundColor: 'white',
+    paddingHorizontal: 16,
+    paddingTop: 8,
+    gap: 16
+  },
+  optionsContainer: {
+    paddingVertical: 10,
+    paddingHorizontal: 8,
+    gap: 8,
+    borderRadius: 8,
+    backgroundColor: Colors.FILL_LIGHT
+  },
+  option: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between'
+  },
+  optionText: {
+    fontSize: getFontSize(12),
+    fontWeight: '600',
+    color: Colors.DARK_BLUE
+  },
+  dangerOption: {
+    paddingVertical: 10,
+    borderBottomWidth: 1,
+    borderBlockColor: Colors.WHITE
+  },
+  dangerText: {
+    color: Colors.RED
+  }
+});
+
+export default OptionsModal;

+ 3 - 1
src/screens/InAppScreens/TravelsScreen/EventScreen/index.tsx

@@ -1037,7 +1037,9 @@ const EventScreen = ({ route }: { route: any }) => {
                             ...([
                             ...([
                               NAVIGATION_PAGES.PARTICIPANTS_LIST,
                               NAVIGATION_PAGES.PARTICIPANTS_LIST,
                               {
                               {
-                                participants: event.participants_full_data
+                                participants: event.participants_full_data,
+                                eventId: event.id,
+                                isHost: event.settings.host_profile === +currentUserId
                               }
                               }
                             ] as never)
                             ] as never)
                           )
                           )

+ 50 - 9
src/screens/InAppScreens/TravelsScreen/ParticipantsListScreen/index.tsx

@@ -1,7 +1,7 @@
 import React, { FC, useEffect, useState } from 'react';
 import React, { FC, useEffect, useState } from 'react';
 import { View, StyleSheet } from 'react-native';
 import { View, StyleSheet } from 'react-native';
 import { NavigationProp } from '@react-navigation/native';
 import { NavigationProp } from '@react-navigation/native';
-import { PageWrapper, Header, Input } from 'src/components';
+import { PageWrapper, Header, Input, WarningModal } from 'src/components';
 import { FlashList } from '@shopify/flash-list';
 import { FlashList } from '@shopify/flash-list';
 
 
 import SearchIcon from 'assets/icons/search.svg';
 import SearchIcon from 'assets/icons/search.svg';
@@ -10,6 +10,9 @@ import { RankingDropdown } from '../../TravellersScreen/utils/types';
 import { Profile } from '../../MapScreen/UsersListScreen/Profile';
 import { Profile } from '../../MapScreen/UsersListScreen/Profile';
 import { FilterButton, FilterModal } from '../../TravellersScreen/Components/FilterModal';
 import { FilterButton, FilterModal } from '../../TravellersScreen/Components/FilterModal';
 import { usePostGetCountriesRanking } from '@api/ranking';
 import { usePostGetCountriesRanking } from '@api/ranking';
+import OptionsModal from '../Components/OptionsModal';
+import { SheetManager } from 'react-native-actions-sheet';
+import { storage, StoreType } from 'src/storage';
 
 
 type Props = {
 type Props = {
   navigation: NavigationProp<any>;
   navigation: NavigationProp<any>;
@@ -17,24 +20,33 @@ type Props = {
 };
 };
 
 
 const ParticipantsListScreen: FC<Props> = ({ navigation, route }) => {
 const ParticipantsListScreen: FC<Props> = ({ navigation, route }) => {
-  const { participants } = route.params;
+  const { participants, eventId, isHost } = route.params;
+  const token = storage.get('token', StoreType.STRING);
   const { data: masterCountries } = usePostGetCountriesRanking();
   const { data: masterCountries } = usePostGetCountriesRanking();
 
 
+  const [allParticipants, setAllParticipants] = useState(participants);
   const [searchQuery, setSearchQuery] = useState('');
   const [searchQuery, setSearchQuery] = useState('');
   const [filteredData, setFilteredData] = useState<any[]>([]);
   const [filteredData, setFilteredData] = useState<any[]>([]);
   const [confirmedValueRanking, setConfirmedValueRanking] = useState<RankingDropdown | null>();
   const [confirmedValueRanking, setConfirmedValueRanking] = useState<RankingDropdown | null>();
   const [isModalVisible, setModalVisible] = useState(false);
   const [isModalVisible, setModalVisible] = useState(false);
+  const [modalState, setModalState] = useState({
+    isWarningVisible: false,
+    title: '',
+    buttonTitle: '',
+    message: '',
+    action: () => {}
+  });
 
 
   useEffect(() => {
   useEffect(() => {
-    if (participants) {
-      setFilteredData(participants);
+    if (allParticipants) {
+      setFilteredData(allParticipants);
     }
     }
-  }, [participants]);
+  }, [allParticipants]);
 
 
   const handleSearch = (text: string) => {
   const handleSearch = (text: string) => {
-    if (text && participants) {
+    if (text && allParticipants) {
       const searchData =
       const searchData =
-        participants?.filter((item: any) => {
+        allParticipants?.filter((item: any) => {
           const itemData = item.first_name
           const itemData = item.first_name
             ? item.first_name.toLowerCase() + ' ' + item.last_name?.toLowerCase()
             ? item.first_name.toLowerCase() + ' ' + item.last_name?.toLowerCase()
             : ''.toLowerCase();
             : ''.toLowerCase();
@@ -44,11 +56,28 @@ const ParticipantsListScreen: FC<Props> = ({ navigation, route }) => {
       setFilteredData(searchData);
       setFilteredData(searchData);
       setSearchQuery(text);
       setSearchQuery(text);
     } else {
     } else {
-      setFilteredData(participants ?? []);
+      setFilteredData(allParticipants ?? []);
       setSearchQuery(text);
       setSearchQuery(text);
     }
     }
   };
   };
 
 
+  const handleFilterParticipants = (userId: number) => {
+    setAllParticipants(allParticipants.filter((p: any) => p.user_id !== userId));
+  };
+
+  const handleOptionPress = (user: any) => {
+    SheetManager.show('host-options-modal', {
+      payload: {
+        eventId,
+        userId: user.user_id,
+        name: user.first_name + ' ' + user.last_name,
+        token,
+        filterParticipants: () => handleFilterParticipants(user.user_id),
+        setIsWarningVisible: setModalState
+      } as any
+    });
+  };
+
   return (
   return (
     <PageWrapper>
     <PageWrapper>
       <Header
       <Header
@@ -73,6 +102,8 @@ const ParticipantsListScreen: FC<Props> = ({ navigation, route }) => {
           data={filteredData || []}
           data={filteredData || []}
           renderItem={({ item, index }) => (
           renderItem={({ item, index }) => (
             <Profile
             <Profile
+              withMenu={isHost}
+              onPressOption={() => handleOptionPress(item)}
               userId={item.user_id}
               userId={item.user_id}
               key={index}
               key={index}
               index={index}
               index={index}
@@ -124,7 +155,7 @@ const ParticipantsListScreen: FC<Props> = ({ navigation, route }) => {
           setConfirmedValueRanking(filterRanking);
           setConfirmedValueRanking(filterRanking);
           setFilteredData(
           setFilteredData(
             applyModalSort(
             applyModalSort(
-              participants ?? [],
+              allParticipants ?? [],
               filterAge,
               filterAge,
               filterRanking ?? dataRanking[0],
               filterRanking ?? dataRanking[0],
               filterCountry
               filterCountry
@@ -134,6 +165,16 @@ const ParticipantsListScreen: FC<Props> = ({ navigation, route }) => {
         }}
         }}
         countriesData={masterCountries ? masterCountries.data : []}
         countriesData={masterCountries ? masterCountries.data : []}
       />
       />
+      <OptionsModal />
+      <WarningModal
+        type={'delete'}
+        isVisible={modalState.isWarningVisible}
+        buttonTitle={modalState.buttonTitle}
+        message={modalState.message}
+        action={modalState.action}
+        onClose={() => setModalState({ ...modalState, isWarningVisible: false })}
+        title={modalState.title}
+      />
     </PageWrapper>
     </PageWrapper>
   );
   );
 };
 };

+ 4 - 2
src/types/api.ts

@@ -203,7 +203,8 @@ export enum API_ENDPOINT {
   GET_MASTER = 'get-master',
   GET_MASTER = 'get-master',
   GET_EVENT_FOR_EDITING = 'get-event-for-editing',
   GET_EVENT_FOR_EDITING = 'get-event-for-editing',
   UPDATE_EVENT = 'update-event',
   UPDATE_EVENT = 'update-event',
-  CANCEL_EVENT = 'cancel-event'
+  CANCEL_EVENT = 'cancel-event',
+  REMOVE_PARTICIPANT = 'remove-participant'
 }
 }
 
 
 export enum API {
 export enum API {
@@ -379,7 +380,8 @@ export enum API {
   GET_MASTER = `${API_ROUTE.RANKING}/${API_ENDPOINT.GET_MASTER}`,
   GET_MASTER = `${API_ROUTE.RANKING}/${API_ENDPOINT.GET_MASTER}`,
   GET_EVENT_FOR_EDITING = `${API_ROUTE.EVENTS}/${API_ENDPOINT.GET_EVENT_FOR_EDITING}`,
   GET_EVENT_FOR_EDITING = `${API_ROUTE.EVENTS}/${API_ENDPOINT.GET_EVENT_FOR_EDITING}`,
   UPDATE_EVENT = `${API_ROUTE.EVENTS}/${API_ENDPOINT.UPDATE_EVENT}`,
   UPDATE_EVENT = `${API_ROUTE.EVENTS}/${API_ENDPOINT.UPDATE_EVENT}`,
-  CANCEL_EVENT = `${API_ROUTE.EVENTS}/${API_ENDPOINT.CANCEL_EVENT}`
+  CANCEL_EVENT = `${API_ROUTE.EVENTS}/${API_ENDPOINT.CANCEL_EVENT}`,
+  REMOVE_PARTICIPANT = `${API_ROUTE.EVENTS}/${API_ENDPOINT.REMOVE_PARTICIPANT}`
 }
 }
 
 
 export type BaseAxiosError = AxiosError;
 export type BaseAxiosError = AxiosError;